Editor templates are really handy tool when we want to create controls for a specific type.
Recently I had a requiremnt to show winform styled ListBox control with lists for Available/Selected and allowing user to move items from one collection to other and move items up/down in selected collection.
It was similar to this:
Step 1: Considering this as a pretty common functionality in our application,I created a ListSource class to support this UI.
MvcLists\Models\ListSource.cs
- using System.Collections.Generic;
-
- namespace MvcLists.Models
- {
- public class ListSource
- {
- public List<Item> Available { get; set; }
- public List<Item> Selected { get; set; }
- }
-
- public class Item
- {
- public int Value { get; set; }
- public string Text { get; set; }
- }
- }
Step 2: Add a property of type ListSource to Model.
MvcLists\Models\Person.cs
- public class Person
- {
- [Tooltip("Enter First Name")]
- public string FirstName { get; set; }
- [Tooltip("Enter Last Name")]
- public string LastName { get; set; }
- [Tooltip("Enter SSN")]
- [Mask("999-99-9999")]
- public string SSN { get; set; }
- [Tooltip("Enter Age")]
- [Mask("99")]
- public string Age { get; set; }
- [Mask("(999)-999-9999")]
- [Tooltip("Enter Phone")]
- public string Phone { get; set; }
- [Mask("99999?-9999")]
- [Tooltip("Enter Zip Code")]
- public string ZipCode { get; set; }
- [Mask("9999-9999-9999-9999")]
- [Tooltip("Enter Credit Card")]
- public string CreaditCard { get; set; }
- [AutoComplete("Person", "GetStates", "state")]
- public string State { get; set; }
- [AutoComplete("Person", "GetStates", "state")]
- public string USState { get; set; }
- public ListSource Country { get; set; }
- }
Step 3: Created an EditorTemplate for this type and added knockoutJS functions for move items left/right/up/down.
MvcLists\Views\Shared\EditorTemplates\ListSource.cshtml
- @model MvcLists.Models.ListSource
- <style type="text/css">
- .divItem
- {
- float: none;
- border-bottom-width: .2em;
- border-bottom-style: solid;
- border-bottom-color: #000000;
- padding: 5px;
- cursor: pointer;
- }
- .rightBoxItem
- {
- padding-left: 10px;
- }
- .indexCell
- {
- /*border-right-width: .2em;
- border-right-style: solid;
- border-right-color: #900;*/
- padding: 5px;
- padding-right: 10px;
- }
- .selectedItem
- {
- background: gold;
- }
- .header
- {
- font-family: arial;
- color: #003469;
- background-color: #dcdcdc;
- margin: 0 0 10px 0;
- padding: 2px 5px 2px 5px;
- }
- </style>
- <script src="../../../Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
- <script src="../../../Scripts/knockout-2.0.0.js" type="text/javascript"></script>
- <script>
-
- function ListViewModel() {
- var listItem = function(text,id,itemIndex) {
- this.Text = text;
- this.Id = id;
- };
- var self = this;
- var listA = @Html.Raw(Json.Encode(Model.Available.ToList()));
- var listB = @Html.Raw(Json.Encode(Model.Selected.ToList()));
- self.listAArray= ko.observableArray(ko.utils.arrayMap(listA,function(list) {
- return new listItem(list.Text, list.Value,index);
- }));
- var index = 0;
- self.listBArray= ko.observableArray(ko.utils.arrayMap(listB,function(list) {
- index++;
- return new listItem(list.Text, list.Value,index);
- }));
- self.changeClass = function(item) {
- $(item).closest("divItem").toggleClass("selectedItem");
- };
- self.moveRight = function() {
- $(".leftBox .selectedItem").each(function() {
- var item=ko.dataFor(this);
- self.listBArray.push(item);
- self.listAArray.remove(item);
- });
- };
- self.moveLeft = function() {
- $(".rightBox .selectedItem").each(function() {
- var item = ko.dataFor(this);
- self.listAArray.push(item);
- self.listBArray.remove(item);
-
- });
- };
- self.moveUp = function() {
- $(".rightBox .selectedItem").each(function() {
- var item = ko.dataFor(this);
- var index = self.listBArray.indexOf(item);
- if(index>0) {
- self.listBArray.remove(item);
- self.listBArray.splice(index - 1, 0, item);
- $(".rightBox .divItem:eq(" + (index -1) + ")").toggleClass("selectedItem");
- } else {
- return false;
- }
-
- });
- };
- self.moveDown = function() {
- $(".rightBox .selectedItem").each(function() {
- var item = ko.dataFor(this);
- var index = self.listBArray.indexOf(item);
- if(index<self.listBArray.length-1) {
- self.listBArray.remove(item);
- self.listBArray.splice(index + 1, 0, item);
- $(".rightBox .divItem:eq(" + (index +1) + ")").toggleClass("selectedItem");
- } else {
- return false;
- }
-
- });
- };
-
- self.afterRender = function() {
- var i = 0;
- $(".rightBox .divItem").each(function() {
- i++;
- $(this).find(".indexCell")[0].text(i);
- });
- };
- ko.bindingHandlers.Index = {
- update:function (element,valueAccessor) {
- var i = 0;
- $(".rightBox .indexCell").each(function() {
- i++;
- $(this).text(i);
- });
- $(".divItem").unbind("click").bind("click", function() {
- $(this).toggleClass("selectedItem");
- });
- }
- };
- }
-
- $(document).ready(function () {
- ko.applyBindings(new ListViewModel());
- });
- </script>
- @Html.LabelForModel()
- <div style="margin-left: 5px; width: 100%;">
- <div style="margin: auto; width: 90%;">
- <div>
- <div style="float: left">
- <div>
- <span>Available</span>
- </div>
- <div data-bind="foreach:listAArray" style="color: black; background-color: #dcdcdc;
- display: inline-block; border-style: solid; border-width: 2px; border-bottom-width: 0px;
- border-color: #000000" class="leftBox">
- <div class="divItem">
- <span data-bind="text:Text"></span>
- </div>
- </div>
- </div>
- <div style="float: left; vertical-align: middle; padding: 5px; display: inline-block;
- margin-top: 50px; width: 75px;">
- <div>
- <div>
- <input type="button" data-bind="click:moveRight" value=">" /></div>
- <div>
- <input type="button" data-bind="click:moveLeft" value="<" /></div>
- </div>
- </div>
- <div style="float: left">
- <div>
- <span>Selected</span>
- </div>
- <div class="rightBox" data-bind="foreach:listBArray,Index:listBArray" style="
- background-color: #FFFFAA; display: inline-block; border-style: solid; border-width: 2px;
- border-color: #000000; border-bottom-width: 0px">
- <span class="indexCell" style="float: left;"></span>
- <div class="divItem rightBoxItem">
- <span data-bind="text:Text"></span>
- </div>
- </div>
- </div>
- <div style="float: left; vertical-align: middle; padding: 5px; display: inline-block;
- margin-top: 50px; width: 75px;">
- <div style="background-color: bisque;">
- <div>
- <input type="button" data-bind="click:moveUp" value="Move Up" /></div>
- <div>
- <input type="button" data-bind="click:moveDown" value="Move Down" /></div>
- </div>
- </div>
- <div style="clear: both">
- </div>
- </div>
- </div>
- </div>
Step 4: In controller/viewModel set DataSource for list box.
MvcLists\Controllers\PersonController.cs
- public ListSource Countries
- {
- get
- {
- return new ListSource
- {
- Available = new List<Item>
- {
- new Item {Text = "US", Value = 1},
- new Item {Text = "UK", Value = 2},
- new Item {Text = "Canada", Value = 3},
- new Item {Text = "France", Value = 4}
- },
- Selected = new List<Item> {new Item {Text = "India", Value = 5}}
- };
- }
- }
Step 5: Render this listbox(Country)
MvcLists\Views\Person\Index.cshtml
- @using MvcLists.Common.HtmlHelpers
- @model MvcLists.Models.Person
- @{
- ViewBag.Title = "Index";
- Layout = "~/Views/Shared/_Layout.cshtml";
- }
- <h2>
- Index</h2>
- <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
- <script src="../../Scripts/jquery.maskedinput.js" type="text/javascript"></script>
- @using (Html.BeginForm("Index","Person"))
- {
- <fieldset>
- <legend>Person</legend>
- <div class="editor-label">
- @Html.LabelFor(model => model.FirstName)
- </div>
- <div class="editor-field">
- @Html.TextBoxFor(model => model.FirstName,new{title=@Html.TooltipFor(x=>x.FirstName)})
- </div>
- <div class="editor-label">
- @Html.LabelFor(model => model.LastName)
- </div>
- <div class="editor-field">
- @Html.TextBoxFor(model => model.LastName,new{title=Html.TooltipFor(x=>x.LastName)})
- </div>
- @Html.EditorFor(x => x.SSN)
- @Html.EditorFor(x => x.Age)
- @Html.EditorFor(x => x.Phone)
- @Html.EditorFor(x => x.CreaditCard)
- @Html.EditorFor(x => x.ZipCode)
- @Html.EditorFor(x=>x.State)
- @Html.EditorFor(x=>x.USState)
- @Html.EditorFor(x=>x.Country)
-
- <p>
- <input type="submit" value="Create" />
- </p>
- </fieldset>
- }
- <div>
- @Html.ActionLink("Back to List", "Index")
- </div>