Last active
February 28, 2016 07:46
-
-
Save dgwaldo/5a842416d2db54502d77 to your computer and use it in GitHub Desktop.
Client Side Collection Editing with Unobtrusive Validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This is our Partial that utilizes the EditorTemplate. | |
@model Domain.Models.Themes.Asset | |
@Html.EditorFor(m=>m, "Asset") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Note: Just to show what script you need to load an in what order to get client side unobtrusive validation working in MVC5. | |
//Most likely you'll want to use the bundler to load these. | |
<script src="~/Scripts/jquery-1.10.2.min.js"></script> | |
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script> | |
<script src="~/Scripts/jquery.validate.min.js"></script> | |
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script> | |
<script src="~/Scripts/jquery.loadTemplate-1.5.0.min.js"></script> | |
<script src="~/Scripts/site.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This is the template for our collection item, it is a Shared/EditorTemplate file. | |
@model Domain.Models.Themes.Asset | |
@{ | |
var assetTypes = ViewData["AssetTypes"] as SelectList; | |
} | |
<div class="row"> | |
<div class="col-md-2"> | |
@Html.DropDownListFor(m => m.AssetType, assetTypes, new { @class = "form-control" }) | |
@Html.ValidationMessageFor(m => m.AssetType, "", new { @class = "text-danger" }) | |
</div> | |
<div class="col-md-2"> | |
@Html.CheckBoxFor(m => m.IsAsync, new { htmlAttributes = new { @class = "form-control" } }) | |
@Html.ValidationMessageFor(m => m.IsAsync, "", new { @class = "text-danger" }) | |
</div> | |
<div class="col-md-2"> | |
@Html.CheckBoxFor(m => m.IsDefer, new { htmlAttributes = new { @class = "form-control" } }) | |
@Html.ValidationMessageFor(m => m.IsDefer, "", new { @class = "text-danger" }) | |
</div> | |
<div class="col-md-2"> | |
@Html.CheckBoxFor(m => m.IsRequiredInHeader, new { htmlAttributes = new { @class = "form - control" } }) | |
@Html.ValidationMessageFor(m => m.IsRequiredInHeader, "", new { @class = "text-danger" }) | |
</div> | |
<div class="col-md-2"> | |
@Html.EditorFor(m => m.Url, new { htmlAttributes = new { @class = "form-control" } }) | |
@Html.ValidationMessageFor(m => m.Url, "", new { @class = "text-danger" }) | |
</div> | |
<div class="col-md-2"> | |
<button class="btn btn-sm btn-danger removeAssetBtn" type="button">Remove</button> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This is the edit form for our collection, it is a Shared/EditorTemplate file. | |
@model List<Domain.Models.Themes.Asset> | |
<div class="row"> | |
<div class="col-md-2"> | |
<strong>Type</strong> | |
</div>//This is our EditorTemplate | |
@model Domain.Models.Themes.Asset | |
@Html.EditorFor(m=>m, this.ViewData) | |
<div class="col-md-2"> | |
<strong>Async</strong> | |
</div> | |
<div class="col-md-2"> | |
<strong>Defer</strong> | |
</div> | |
<div class="col-md-2"> | |
<strong>Required in header</strong> | |
</div> | |
<div class="col-md-2"> | |
<strong>URL</strong> | |
</div> | |
<div class="col-md-2"> | |
<button class="btn btn-sm btn-default" type="button" id="AddAsset">Add Asset</button> | |
</div> | |
</div> | |
//Here all the current collection items are rendered out, if we don't have any collection items no worries. | |
<div id="assets"> | |
@for(var i=0; i< Model.Count; i++) { | |
@Html.EditorFor(m=>Model[i], "Asset", ViewData); | |
} | |
</div> | |
//This is the magical template that is hidden in the DOM ready at a moments notice to do what we tell it. | |
<div class="hidden"> | |
<script type="text/html" id="assetTemplateItem"> | |
@{ | |
Html.RenderPartial("_AssetEditorPartial", new Domain.Models.Themes.Asset(), this.ViewData); | |
} | |
</script> | |
</div> | |
//Here we are hooking up some elements using jQuery selectors. | |
<script type="text/javascript"> | |
DOMINOPROV.AddToCollectionOnClick('#AddAsset', '#assets', '#assetTemplateItem'); | |
DOMINOPROV.RemoveCollectionItemOnClick('div', '.removeAssetBtn', '#assets'); | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This is our top level view that pulls in and utilizes the Assets EditorTemplate | |
@model Provisioner.ViewModels.AddAssetsViewModel | |
@{ | |
ViewBag.Title = "Create Assets"; | |
} | |
<h2>Create</h2> | |
@{ | |
Html.EnableClientValidation(); | |
} | |
@using (Html.BeginForm()) { | |
@Html.AntiForgeryToken() | |
<div id="assetsForm" class="form-horizontal"> | |
<h4>Assets</h4> | |
<hr /> | |
@Html.ValidationSummary(true, "", new { @class = "text-danger" }) | |
@Html.EditorFor(model => model.Assets, "Assets", new { AssetTypes = Model.AssetTypes }) | |
@Html.ValidationMessageFor(model => model.Assets) | |
<div class="form-group"> | |
<div class="col-md-offset-2 col-md-10"> | |
<input type="submit" value="Create" class="btn btn-default" /> | |
</div> | |
</div> | |
</div> | |
} | |
<div> | |
@Html.ActionLink("Back to List", "Index") | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var DOMINOPROV = {}; | |
//btnElem: Jquery element selector that will be used to hook into the click event. | |
//domCollectionWrapperElem: JQuery element selector for element wrapping the element that holds the collection. | |
//domTemplateItemElem: The item to be copied and used when adding to the collection. | |
//simpleCollection: Bool, pass true if your using a simple type like int or string in a collection. | |
/*Relies on jQuery-Template */ | |
DOMINOPROV.AddToCollectionOnClick = function (btnElem, domCollectionWrapperElem, domTemplateItemElem, simpleCollection) { | |
$(btnElem).click(function () { | |
var collectionWrapper = $(domCollectionWrapperElem); | |
var count = collectionWrapper.children().length; | |
$(collectionWrapper).loadTemplate($(domTemplateItemElem), null, | |
{ append: true, afterInsert: function (el) { replaceMvcTagNamesForTemplate(count, el, simpleCollection); } }); | |
resetValidatorDynamicContent(); | |
}); | |
} | |
//staticWrapperElem: Jquery element static element that contains the item with a button. | |
//btnElem: Jquery element selector that will be used to hook into the click event. | |
//domCollectionWrapperElem: JQuery element selector for element wrapping the element that holds the collection. | |
//domTemplateItemElem: The item to be copied and used when adding to the collection. | |
DOMINOPROV.RemoveCollectionItemOnClick = function (staticWrapperElement, btnClass, parentCollectionDivId) { | |
$(staticWrapperElement).on('click', btnClass, function () { | |
$(this).parents('div')[1].remove(); | |
var rows = $(parentCollectionDivId).children(); | |
//ReIndex to not break MVC collection databinding:) | |
$(rows).each(function (index, pr) { | |
replaceMvcTagNamesOnDelete(index, pr); | |
}); | |
}); | |
} | |
//index: Current collection item index | |
//el: Element in which to look for and replace tag names | |
function replaceMvcTagNamesOnDelete(index, el) { | |
$(el).children().find("*").attr('id', function (i, id) { | |
if (id !== undefined) { | |
return id.replace(/_.?._/g, '_' + index + '__'); | |
} | |
}).attr('name', function (i, name) { | |
if (name !== undefined) { | |
return name.replace(/\[.?\]/g, '[' + index + ']'); | |
} | |
}).attr('data-valmsg-for', function (i, name) { | |
if (name !== undefined) { | |
return name.replace(/\[.?\]/g, '[' + index + ']'); | |
} | |
}); | |
} | |
//index: Current collection item index | |
//el: Element in which to look for and replace tag names | |
function replaceMvcTagNamesForTemplate(index, el, simpleCollection) { | |
var dotReg = /(\.)/gi; | |
$(el).children().find("*").attr('id', function (i, id) { | |
if (id !== undefined) { | |
var reg = /(_)/gi; | |
var groups = id.match(reg); | |
var nth = 0; | |
if (simpleCollection) { | |
return name + '_' + index + '_'; | |
} | |
return id.replace(reg, function (match) { | |
nth++; | |
return (nth === groups.length) ? '_' + index + '__' : match; | |
}); | |
} | |
}).attr('name', function (i, name) { | |
if (name !== undefined) { | |
return replaceIndexedStringPortion(name); | |
} | |
}).attr('data-valmsg-for', function (i, name) { | |
if (name !== undefined) { | |
return replaceIndexedStringPortion(name); | |
} | |
}); | |
function replaceIndexedStringPortion(name) { | |
var groups = name.match(dotReg); | |
if (simpleCollection) { | |
return name + '[' + index + ']'; | |
} | |
var nth = 0; | |
return name.replace(dotReg, function (match) { | |
nth++; | |
return (nth === groups.length) ? '[' + index + '].' : match; | |
}); | |
} | |
} | |
//Resets the unobtrusive validation | |
function resetValidatorDynamicContent() { | |
$('form').removeData("validator").removeData("unobtrusiveValidation"); | |
$.validator.unobtrusive.parse($('form')); | |
} | |
window.DOMINOPROV = DOMINOPROV; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment