Available in the Full Version
Tree Grid - KnockoutJS Binding
This sample features remote loading of more than 10,000 records and demonstrates editing grid rows with KnockoutJS binding.
This sample uses CTP (Community Technical Preview) features. The API and behavior may change when these features are released with full support.
This sample is designed for a larger screen size.
On mobile, try rotating your screen, view full size, or email to another device.
Custom function is used to render the paging breadcrumb to display the picture and name of the parent employee. Other features enabled in the Tree Grid are selection and remote paging. Note that saving employee is done only in the local data source, not pushed back to the server.
Code View
Copy to Clipboard
@using Infragistics.Web.Mvc @using IgniteUI.SamplesBrowser.Models.Northwind @using System.Data <!DOCTYPE html> <html> <head> <title></title> <!-- Ignite UI for jQuery Required Combined CSS Files --> <link href="http://cdn-na.infragistics.com/igniteui/2024.1/latest/css/themes/infragistics/infragistics.theme.css" rel="stylesheet" /> <link href="http://cdn-na.infragistics.com/igniteui/2024.1/latest/css/structure/infragistics.css" rel="stylesheet" /> <script src="http://ajax.aspnetcdn.com/ajax/modernizr/modernizr-2.8.3.js"></script> <script src="http://code.jquery.com/jquery-1.11.3.min.js"></script> <script src="http://code.jquery.com/ui/1.11.1/jquery-ui.min.js"></script> <script src="http://igniteui.com/js/external/knockout-latest.js"></script> <script src="http://igniteui.com/js/external/knockout.mapping-latest.js"></script> <script src="http://cdn-na.infragistics.com/igniteui/2024.1/latest/js/extensions/infragistics.ui.editors.knockout-extensions.js"></script> <script src="http://cdn-na.infragistics.com/igniteui/2024.1/latest/js/extensions/infragistics.ui.combo.knockout-extensions.js"></script> <!-- Ignite UI for jQuery Required Combined JavaScript Files --> <script src="http://cdn-na.infragistics.com/igniteui/2024.1/latest/js/infragistics.core.js"></script> <script src="http://cdn-na.infragistics.com/igniteui/2024.1/latest/js/infragistics.lob.js"></script> <script src="http://igniteui.com/data-files/org-chart-available-positions.js"></script> <style type="text/css"> #sampleContainer fieldset { margin:0; } #editForm .ui-dialog-title{ text-transform:uppercase; } .employee-table { width:100%; overflow:hidden; border-bottom: 1px solid #ccc; border-top: 1px solid #ccc; margin-bottom:10px; padding-bottom:6px; padding-top:20px; } .employee-data { float:left; width:80%; } .employee-pic { float:right; width:20%; } .form-row { width:100%; overflow:hidden; margin-bottom:14px; } #sampleContainer fieldset .row-edit-dialog-container-head { font-size:16px; } #sampleContainer fieldset .row-edit-dialog-container-head label { display: inline !important; } .row-edit-dialog-container-head #idEditor { line-height:32px; display:inline; } .form-row label { float:left; margin:0 10px 0 0 !important; line-height:32px; width:30%; text-align: right; } .emp-pic { width: 50px; height: 50px; border-radius: 25px; -webkit-border-radius: 25px; -moz-border-radius: 25px; display: inline-block; } .emp-pic-big { width: 100px; height: 100px; border-radius: 50px; -webkit-border-radius: 50px; -moz-border-radius: 50px; float:right; } .emp-pic-breadcrumb { margin-right: 5px; float: left; width: 16px; height: 16px; border-radius: 8px; -webkit-border-radius: 8px; -moz-border-radius: 8px; } .breadcrumb-label { margin-right: 5px; float: left; } .breadcrumb-container { display: inline-block; } #orgChartGrid_container { float: left; } #org-chart-edit { float: left; } #org-chart-edit.label { display: block; margin-top: 5px; } .ui-dialog-buttonset { float: right; } .ui-dialog .ui-dialog-titlebar { padding:0.7em 1em; } .ui-dialog .ui-dialog-titlebar.ui-state-focus, .ui-dialog.ui-igdialog { border-color:#888 !important; } </style> </head> <body> <script type="text/javascript"> var contextRowFunc = function (dataRow, $textArea, parents, mode) { var contextRowText = "<div class=\"breadcrumb-container\">"; // Default text when there are no parents if (!parents || parents.length == 1) { contextRowText += "Organizational Chart"; } else { $(parents).slice(0, -1).each(function (index) { contextRowText += "<div class=\"emp-pic-breadcrumb\" style=\"background: url(" + this.row["Picture"].replace(/50/g, "16") + ") no-repeat;\"></div><div class=\"breadcrumb-label\">" + this.row["FirstName"] + " " + this.row["LastName"] + " > </div>"; }); } contextRowText += "</div>"; return contextRowText; } function formatReportsTo(val, record) { var manager = ko.utils.arrayFirst(orgChartVM.directors(), function (item) { return item.EmployeeID() === record.ReportsTo; }); if (manager) { return manager.FullName(); } else { return ''; } } var Employee = function (EmployeeID, FirstName, LastName, ReportsTo, Picture, Position, HireDate, AnnualSalary) { var self = this; self.EmployeeID = ko.observable(EmployeeID); self.FirstName = ko.observable(FirstName); self.LastName = ko.observable(LastName); self.ReportsTo = ko.observableArray([ReportsTo]); self.Picture = ko.observable(Picture); self.BigPicture = self.Picture() ? self.Picture().replace(/50/g, "100") : ''; self.Position = ko.observableArray([Position]); self.HireDate = ko.observable(HireDate); self.AnnualSalary = ko.observable(AnnualSalary); self.FullName = ko.computed(function () { return self.FirstName() + ' ' + self.LastName(); }); } function EmployeesViewModel() { var self = this; self.selectedEmployee = ko.observable(); self.employeeSelectionChanged = function (evt, ui) { if (!ui.row) { return; } var employee = $("#orgChartGrid").igTreeGrid("findRecordByKey", ui.row.id); self.selectedEmployee(new Employee( employee.EmployeeID, employee.FirstName, employee.LastName, employee.ReportsTo, employee.Picture, employee.Position, employee.HireDate, employee.AnnualSalary)); $('#editForm').igDialog("open"); }; self.availablePositions = orgChartAvailablePositions; self.directors = ko.observableArray(); $.get('@Url.Action("GetDirectors")', function (data) { $(data).each(function () { self.directors.push(new Employee(this.EmployeeID, this.FirstName, this.LastName, null, null, null, null, null)); }); }); self.save = function () { var empRecord = $("#orgChartGrid").igTreeGrid("findRecordByKey", self.selectedEmployee().EmployeeID()); empRecord.FirstName = self.selectedEmployee().FirstName(); empRecord.LastName = self.selectedEmployee().LastName(); empRecord.ReportsTo = self.selectedEmployee().ReportsTo()[0]; empRecord.Picture = self.selectedEmployee().Picture(); empRecord.Position = self.selectedEmployee().Position()[0]; empRecord.HireDate = self.selectedEmployee().HireDate(); empRecord.AnnualSalary = self.selectedEmployee().AnnualSalary(); $("#orgChartGrid").igTreeGrid("commit"); $("#editForm").igDialog("close"); }; self.cancel = function () { $("#editForm").igDialog("close"); }; } orgChartVM = new EmployeesViewModel(); $(function () { ko.applyBindings(orgChartVM); $("#editForm").igDialog({ headerText: "Edit Employee", state: "closed", modal: true, draggable: false, resizable: false, height: "460px", width: "600px" }); }); </script> @(Html.Infragistics().TreeGrid<IgniteUI.SamplesBrowser.Models.OrgChartEmployee>() .ID("orgChartGrid") .Width("815px") .Height("600px") .DataSourceUrl(Url.Action("editing-knockout")) .AutoGenerateColumns(false) .PrimaryKey("EmployeeID") .ChildDataKey("Subs") .Columns(c => { c.For(x => x.EmployeeID).HeaderText("Employee ID").DataType("number").Hidden(true); c.For(x => x.Picture).HeaderText("Picture").DataType("string").Width("100px").Template("<div class=\"emp-pic\" style=\"background: url(${Picture}) no-repeat;\"></div>"); c.For(x => x.FirstName).HeaderText("Name").DataType("string").Template("${FirstName} ${LastName}"); c.For(x => x.Position).HeaderText("Position").DataType("string"); c.For(x => x.HireDate).HeaderText("Hire Date").DataType("date").Format("yyyy-MM-dd"); c.For(x => x.AnnualSalary).HeaderText("Annual Salary").DataType("number").Format("currency"); c.For(x => x.ReportsTo).Hidden(true); c.For(x => x.LastName).HeaderText("Reports To").DataType("string").FormatterFunction("formatReportsTo"); }) .DefaultColumnWidth("100px") .InitialExpandDepth(0) .InitialIndentationLevel(2) .RenderExpansionIndicatorColumn(true) .Features(f => { f.Selection() .Mode(SelectionMode.Row) .ClientEvents(new Dictionary<string, string>() { { GridSelectionClientEvents.RowSelectionChanged, "orgChartVM.employeeSelectionChanged(evt,ui);" } }); f.Paging() .Type(OpType.Remote) .Mode(TreeGridPagingMode.AllLevels) .ContextRowMode(TreeGridPagingContextRowMode.Breadcrumb) .RenderContextRowFunc("contextRowFunc") .PageSize(100); }) .Render() ) <div id="editForm"> <fieldset class="org-chart-edit" data-bind="visible: selectedEmployee(), with: selectedEmployee"> <div class="row-edit-dialog-container-head"> <label for="idEditor">Employee ID:</label> <div id="idEditor" data-bind="text: EmployeeID"></div> </div> <div class="employee-table"> <div class="employee-data"> <div class="form-row"> <label for="firstNameEditor">First Name:</label> <input id="firstNameEditor" type="text" data-bind="igTextEditor: { value: FirstName, selectionOnFocus: 'atEnd' }" /> </div> <div class="form-row"> <label for="lastNameEditor">Last Name:</label> <input id="lastNameEditor" type="text" data-bind="igTextEditor: { value: LastName, selectionOnFocus: 'atEnd' }" /> </div> <div class="form-row"> <label for="positionEditor">Position:</label> <select id="positionEditor" data-bind="igCombo: { dataSource: $root.availablePositions, selectedItems: Position, enableClearButton: false, mode: 'dropdown' }"></select> </div> <div class="form-row"> <label for="hireDateEditor">Hire Date:</label> <input id="hireDateEditor" data-bind="igDateEditor: { value: HireDate }" /> </div> <div class="form-row"> <label for="reportsToEditor">Reports To:</label> <select id="reportsToEditor" data-bind="igCombo: { dataSource: $root.directors, valueKey: 'EmployeeID', textKey: 'FullName', selectedItems: ReportsTo, placeHolder: 'Choose manager...' }"></select> </div> <div class="form-row"> <label for="annualSalaryEditor">Annual Salary:</label> <input id="annualSalaryEditor" type="number" step="10" data-bind="igCurrencyEditor: { value: AnnualSalary, selectionOnFocus: 'atStart' }" /> </div> </div> <div class="employee-pic"> <div class="emp-pic-big" data-bind="style: { background: 'url(\'' + BigPicture + '\') no-repeat' }"></div> </div> </div> <div class="ui-dialog-buttonset"> <button class="ui-button-text-only ui-button ui-igbutton ui-widget ui-widget-content ui-corner-all ui-state-default" data-bind="click: $root.save"><span id="grid_updating_dialog_container_footer_buttonok_lbl" class="ui-button-text">Save Employee</span></button> <button class="ui-button-text-only ui-button ui-igbutton ui-widget ui-widget-content ui-corner-all ui-state-default" data-bind="click: $root.cancel"><span id="grid_updating_dialog_container_footer_buttoncancel_lbl" class="ui-button-text">Cancel</span></button> </div> </fieldset> </div> </body> </html>
using IgniteUI.SamplesBrowser.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Infragistics.Web.Mvc; using IgniteUI.SamplesBrowser.Models.Repositories; using IgniteUI.SamplesBrowser.Models.Northwind; using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Text; namespace IgniteUI.SamplesBrowser.Controllers { public class TreeGridController : Controller { // // GET: /TreeGrid/ [ActionName("aspnet-mvc-helper")] public ActionResult AspMvcHelper() { var files = new List<FileExplorer>(); files.Add(new FileExplorer { ID = "1", Name = "Documents", DateModified = new DateTime(2013, 9, 12), Type = "File Folder", Size = 4480, Files = new List<FileExplorer> { new FileExplorer { ID = "4", Name = "To do list.txt", DateModified = new DateTime(2013,11,30), Type = "TXT File", Size = 4448 }, new FileExplorer { ID = "5", Name = "To do list.txt", DateModified = new DateTime(11/30/2013), Type = "TXT File", Size = 4448 } } }); files.Add(new FileExplorer { ID = "2", Name = "Music", DateModified = new DateTime(2014, 6, 10), Type = "File Folder", Size = 5594, Files = new List<FileExplorer> { new FileExplorer { ID = "6", Name = "AC/DC", DateModified =new DateTime(2014,6,10), Type = "File Folder", Size = 2726 , Files = new List<FileExplorer> { new FileExplorer { ID = "8", Name = "Stand Up.mp3", DateModified = new DateTime(2014,6,10), Type = "MP3 File", Size = 456 }, new FileExplorer { ID = "9", Name = "T.N.T.mp3", DateModified = new DateTime(2014,6,10), Type = "MP3 File", Size = 1155 }, new FileExplorer { ID = "10", Name = "The Jack.mp3", DateModified = new DateTime(2014,6,10), Type = "MP3 File", Size = 1115 } } }, new FileExplorer { ID = "7", Name = "WhiteSnake", DateModified = new DateTime(2014,6,11), Type = "File Folder", Size = 2868, Files = new List<FileExplorer> { new FileExplorer { ID = "11", Name = "Trouble.mp3", DateModified = new DateTime(2014,6,11), Type = "MP3 File", Size = 1234 }, new FileExplorer { ID = "12", Name = "Bad Boys.mp3", DateModified = new DateTime(2014,6,11), Type = "MP3 File", Size = 522 }, new FileExplorer { ID = "13", Name = "Is This Love.mp3", DateModified = new DateTime(2014,6,11), Type = "MP3 File", Size = 1112 } } } } }); files.Add(new FileExplorer { ID = "3", Name = "Pictures", DateModified = new DateTime(2014, 1, 20), Type = "File Folder", Size = 1825, Files = new List<FileExplorer> { new FileExplorer { ID = "14", Name = "Jack's Birthday", DateModified = new DateTime(2014,6,21), Type = "File Folder", Size = 631, Files = new List<FileExplorer> { new FileExplorer { ID = "16", Name = "Picture1.png", DateModified = new DateTime(2014,6,21), Type = "PNG image", Size = 493 }, new FileExplorer { ID = "17", Name = "Picture2.png", DateModified = new DateTime(2014,6,21), Type = "PNG image", Size = 88 }, new FileExplorer { ID = "18", Name = "Picture3.gif", DateModified = new DateTime(2014,6,21), Type = "GIF File", Size = 50 } } }, new FileExplorer { ID = "15", Name = "Trip to London", DateModified = new DateTime(2014,3,11), Type = "File Folder", Size = 1194, Files = new List<FileExplorer> { new FileExplorer { ID = "19", Name = "Picture1.png", DateModified = new DateTime(2014,3,11), Type = "PNG image", Size = 974 }, new FileExplorer { ID = "20", Name = "Picture2.png", DateModified = new DateTime(2014,3,11), Type = "PNG image", Size = 142 }, new FileExplorer { ID = "21", Name = "Picture3.png", DateModified = new DateTime(2014,3,11), Type = "PNG image", Size = 41 }, new FileExplorer { ID = "22", Name = "Picture4.png", DateModified = new DateTime(2014,3,11), Type = "PNG image", Size = 25 }, new FileExplorer { ID = "23", Name = "Picture5.png", DateModified = new DateTime(2014,3,11), Type = "PNG image", Size = 12 } } } } }); return View("aspnet-mvc-helper", files.AsQueryable()); } [ActionName("load-on-demand")] public ActionResult LoadOnDemand() { return View(); } [ActionName("remote-features")] public ActionResult RemoteFeatures() { return View(); } [ActionName("updating")] public ActionResult Updating() { return View(); } [ActionName("editing-knockout")] [TreeGridDataSourceAction] public ActionResult EditingKnockout() { var employees = OrgChartEmployeesRepository.GetEmployees(); return View(employees.AsQueryable()); } public JsonResult GetDirectors() { var directors = OrgChartEmployeesRepository.GetDirectors(); return Json(directors, JsonRequestBehavior.AllowGet); } #region Data [TreeGridDataSourceAction] public ActionResult ChildEmployeesOnDemand() { IQueryable allData = RepositoryFactory.GetHierarchicalEmployeeData().AsQueryable(); return View("load-on-demand", allData); } [TreeGridDataSourceAction] public ActionResult GetTreeData() { IQueryable allData = RepositoryFactory.GetHierarchicalEmployeeData().AsQueryable(); return View("remote-features", allData); } [TreeGridDataSourceAction] public ActionResult GetTreeGridData() { IQueryable allData = RepositoryFactory.GetTreeGridRepository().Get().AsQueryable(); return View("updating", allData); } #endregion //Data public ActionResult EmployeeSaveData() { TreeGridModel treeGridModel = new TreeGridModel(); List<Transaction<EmployeeData>> transactions = treeGridModel.LoadTransactions<EmployeeData>(HttpContext.Request.Form["ig_transactions"]); var employees = RepositoryFactory.GetTreeGridRepository(); foreach (Transaction<EmployeeData> t in transactions) { if (t.type == "newrow") { employees.Add(t.row); } else if (t.type == "deleterow") { employees.Delete(o => o.ID == Int32.Parse(t.rowId)); } else if (t.type == "row") { var employee = FindElementEmployees(employees.Get(), Int32.Parse(t.rowId)); if (t.row.FirstName != null) { employee.FirstName = t.row.FirstName; } if (t.row.LastName != null) { employee.LastName = t.row.LastName; } if (t.row.Title != null) { employee.Title = t.row.Title; } if (t.row.Email != null) { employee.Email = t.row.Email; } if (t.row.HireDate != null) { employee.HireDate = t.row.HireDate; } employees.Update(employee, o => o.ID == Int32.Parse(t.rowId)); } else if (t.type == "insertnode") { var parentEmployee = FindElementEmployees(employees.Get(), Int32.Parse(t.parentRowId)); if (parentEmployee.Employees == null) { parentEmployee.Employees = new List<EmployeeData>() as IEnumerable<EmployeeData>; } var temp = parentEmployee.Employees.ToList(); temp.Add(t.row); parentEmployee.Employees = temp as IEnumerable<EmployeeData>; } } employees.Save(); JsonResult result = new JsonResult(); Dictionary<string, bool> response = new Dictionary<string, bool>(); response.Add("Success", true); result.Data = response; return result; } private EmployeeData FindElementEmployees(IEnumerable<EmployeeData> data, int id) { EmployeeData employee = null; for (int i = 0; i < data.Count(); i++) { if (employee != null) { break; } employee = GetNode(data.ElementAt(i), id); } return employee; } public static EmployeeData GetNode(EmployeeData parent, int id) { if (parent != null) { if (parent.ID.Equals(id)) { return parent; } } if (parent.Employees != null) foreach (var child in parent.Employees) { if (child.ID.Equals(id)) { return child; } var employee = GetNode(child, id); if (employee != null) { return employee; } } return null; } } }