Kendo UI Treeview data bind to XML: how does it bind to "id"? - kendo-ui

I have difficulties understanding the inner workings of the TreeView widget. I am referring to the Kendo code library example, specifically the Ajax loading snippet:
//Ajax binding data
public JsonResult Employees(string id)
{
XElement element = XElement.Load((Server.MapPath("~/App_Data/employees.xml")));
IEnumerable<Employee> result;
if (!string.IsNullOrEmpty(id))
{
//search for id and return it's children
result = FindByID(id, element.Element("Employee")).Element("items").Elements("Employee").Select(e => ToEmployee(e));
}
else
{
//return first level nodes
result = element.Elements("Employee").Select(e => ToEmployee(e)) ;
}
return Json(result, JsonRequestBehavior.AllowGet);
}
//Find the XML element by Id
private XElement FindByID(string id, XElement element)
{...}
//Convert XML element to Object
private Employee ToEmployee(XElement element)
{
return new Employee()
{
id = int.Parse(element.Element("employeeId").Value),
name = element.Element("name").Value,
hasChildren = element.Element("items") != null
};
}
This is the Model used, which corresponds to the actual XML structure:
public class Employee
{
public int id { get; set; }
public string name { get; set; }
public bool hasChildren { get; set; }
public List<Employee> items { get; set; }
}
The View executes the following code:
#(Html.Kendo().TreeView()
.Name("ajaxTree")
.DataTextField("name")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("Employees", "Home");
});
})
)
What bothers me is the fact that the Model needs to be implemented exactly as in this example. Specifically, the "id" and "hasChildren" properties need to be specified exactly in this manner. Modifying, for example, "id" into "Id" would render this example ineffective and the TreeView would not load. Can somebody help me with the following?
How is the binding actually accomplished?
Why must I design my model with lower-case properties? (I know it sound weird, but it conflicts with the rest of my (group) project's formatting...)
Is there a way to bind the Kendo required "id" and "hasChildren" to other properties (same function, different name)?

Not sure if you got an answer for your question.
This link http://demos.kendoui.com/web/treeview/remote-data.html will help you understand how your model data is bound to treeview. Please go through below links.
HierarchicalDataSource - http://docs.kendoui.com/api/framework/hierarchicaldatasource
DataSource - http://docs.kendoui.com/api/framework/datasource
Model - http://docs.kendoui.com/api/framework/model
For question #2, #3
Yes, it is possible to configure your model properties as below:
schema: {
model: {
id: "EmployeeId",
hasChildren: "HasEmployees",
children: "EmployeeArray"
}
}

Related

How to pass a parameter to the Read method of signalr transport for Kendo UI grid data source

I was looking at the demo:
Telerik's Demo
and this
Telerik's Example
I still cannot figure out how, using this technique, to pass a parameter
to the Read method, so instead of reading ALL games or products, it reads ONLY a
subset of games or products, by category ID lest say. If I change the Read method in
the server-side code to take a parameter, it never gets hit anymore,
and I cannot figure out how to pass a parameter from the transport
client-side definition definition... Any help would be highly
appreciated!
I posted the question to the Telerik's forums and got this which should work!
Telerik's Answer
In case anyone else comes along in the future and the link in the other answer is gone, or for those who don't want to download a project file and sift through it themselves, here's more detail from the answer given on the Telerik forums:
They use Kendo.DynamicLinq to "bind the request parameters."
They created a custom class that mirrors the structure of the existing DataSourceRequest class typically used in AJAX grid actions.
using System.Collections.Generic;
using Kendo.DynamicLinq;
namespace SignalRLocalHub.Models
{
public class MyDataSourceRequest
{
public int Take { get; set; }
public int Skip { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public Filter Filter { get; set; }
public IEnumerable<Sort> Sort { get; set; }
public IEnumerable<Aggregator> Aggregates { get; set; }
}
}
In the ProductHub (SignalR hub) class, this is the Read method:
public DataSourceResult Read(MyDataSourceRequest request)
{
return productService.Read()
.ToDataSourceResult(
request.Take,
request.Skip,
request.Sort,
request.Filter,
request.Aggregates);
}
The productService.Read method it calls, for reference, looks like this:
//int take, int skip, IEnumerable<Sort> sort, Filter filter, IEnumerable<Aggregator> aggregates
public IQueryable<ProductViewModel> Read()
{
return entities.Products
.OrderBy(p => p.ProductID)
.Select(product => new ProductViewModel
{
ProductID = product.ProductID,
ProductName = product.ProductName,
UnitPrice = product.UnitPrice.HasValue ? product.UnitPrice.Value : default(decimal),
UnitsInStock = product.UnitsInStock.HasValue ? product.UnitsInStock.Value : default(short),
Discontinued = product.Discontinued
});
}
And finally, here is the grid's DataSource configuration:
.DataSource(d => d
.SignalR()
.AutoSync(true)
.Transport(tr => tr
.Promise("hubStart")
.Hub("hub")
.Client(c => c.Read("read").Create("create").Update("update").Destroy("destroy"))
.Server(s => s.Read("read").Create("create").Update("update").Destroy("destroy"))
)
.ServerFiltering(true)
.Filter(f => f.Add(m => m.UnitPrice).IsEqualTo(10))
.ServerPaging(true)
.PageSize(10)
.Schema(s => s
.Data("Data")
.Total("Total")
.Aggregates("Aggregates")
.Model(m =>
{
m.Id(e => e.ProductID);
m.Field(e => e.ProductID).Editable(false);
})
)
)
This allows sorting, paging, and filtering by any field that is part of the grid view model. So as long as your "category ID" is a property of the grid view model and the grid is configured to be able to filter by that field, this method should work.

Assigning Key from Kendo Grid row to main Model within a View

Using Kendo Grid in an MVC application.
The primary model for the View (PlanViewModel) contains a property which is a foreign key to another entity (BuildingId) For instance,
public class PlanViewModel
{
public int PlanId { get; set; }
public string PlanName { get; set; }
public int BuildingId { get; set; }
...
}
The Building ID is to be populated by the selected row of a BuildingGrid on the View.
So with the BuildingGrid, I'm using the .Selectable setting to invoke an onChange event. How in the following event would I update the model.BuildingId
function onChange(arg) {
var selected = $.map(this.select(), function (item) {
return $(item).text();
});
/// UPDATE MODEL BuildingId here
}
Thanks!
Figured it out. First, I associated an Html attribute to the model property like so:
#Html.HiddenFor(m => m.BuildingID, new {id = "Id"})
Then I added the following jquery script:
$('#BuildingGrid').click(function() {
var gview = $(this).data("kendoGrid");
var selectedItem = gview.dataItem(gview.select());
var BuildingId = selectedItem.BuildingId;
$("#Id").val(BuildingId);
});
That set the Model.BuildingId perfectly.
Thanks to a tip I got from here: set a value to model using jQuery

Single property not getting bound on HttpPost

I'm working on the first MVC3 project at our company, and I've hit a block. No one can seem to figure out what's going on.
I have a complex Model that I'm using on the page:
public class SpaceModels : List<SpaceModel> {
public bool HideValidation { get; set; }
[Required(ErrorMessage=Utilities.EffectiveDate + Utilities.NotBlank)]
public DateTime EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
}
In the Controller, I create a SpaceModels object with blank SpaceModels for when Spaces get combined (this would be the destination Space).
// Need a list of the models for the View.
SpaceModels models = new SpaceModels();
models.EffectiveDate = DateTime.Now.Date;
models.DisplayEffectiveDate = true;
models.Add(new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
return View("CombineSpaces", models);
Then in the View, I am using that SpaceModels object as the Model, and in the form making a TextBox for the Effective Date:
#model Data.SpaceModels
#using (Html.BeginForm("CombineSpaces", "Space")) {
<div class="EditLine">
<span class="EditLabel LongText">
New Space Open Date
</span>
#Html.TextBoxFor(m => m.EffectiveDate, new {
size = "20",
#class = "datecontrol",
// Make this as a nullable DateTime for Display purposes so we don't start the Calendar at 1/1/0000.
#Value = Utilities.ToStringOrDefault(Model.EffectiveDate == DateTime.MinValue ? null : (DateTime?)Model.EffectiveDate, "MM/dd/yyyy", string.Empty)
})
#Html.ValidationMessageFor(m => m.EffectiveDate)
</div>
<hr />
Html.RenderPartial("_SpaceEntry", Model);
}
The Partial View that gets rendered iterates through all SpaceModels, and creates a containing the Edit fields for the individual SpaceModel objects. (I'm using the List to use the same Views for when the Spaces get Subdivided as well.)
Then on the HttpPost, the EffectiveDate is still back at it's DateTime.MinValue default:
[HttpPost]
public ActionResult CombineSpaces(SpaceModels model, long siteID, long storeID, DateTime? effectiveDate) {
// processing code
}
I added that DateTime? effectiveDate parameter to prove that the value when it gets changed does in fact come back. I even tried moving the rendering of the TextBox into the _SpaceEntry Partial View, but nothing worked there either.
I did also try using the #Html.EditorFor(m => m.EffectiveDate) in place of the #Html.TextBoxFor(), but that still returned DateTime.MinValue. (My boss doesn't like giving up the control of rendering using the #Html.EditorForModel by the way.)
There has to be something simple that I'm missing. Please let me know if you need anything else.
Looking at the source code for DefaultModelBinder, specifically BindComplexModel(), if it detects a collection type it will bind the individual elements but will not attempt to bind properties of the list object itself.
What model binding does is attempt to match the names of things or elements in the view to properties in your model or parameters in your action method. You do not have to pass all of those parameters, all you have to do is add them to your view model, then call TryUpdateModel in your action method. I am not sure what you are trying to do with SpaceModel or List but I do not see the need to inherit from the List. Im sure you have a good reason for doing it. Here is how I would do it.
The view model
public class SpacesViewModel
{
public DateTime? EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
public List<SpaceModel> SpaceModels { get; set; }
}
The GET action method
[ActionName("_SpaceEntry")]
public PartialViewResult SpaceEntry()
{
var spaceModels = new List<SpaceModel>();
spaceModels.Add(
new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
var spacesVm = new SpacesViewModel
{
EffectiveDate = DateTime.Now,
DisplayEffectiveDate = true,
SpaceModels = spaceModels
};
return PartialView("_SpaceEntry", spacesVm);
}
The POST action method
[HttpPost]
public ActionResult CombineSpaces()
{
var spacesVm = new SpacesViewModel();
// this forces model binding and calls ModelState.IsValid
// and returns true if the model is Valid
if (TryUpdateModel(spacesVm))
{
// process your data here
}
return RedirectToAction("Index", "Home");
}
And the view
<label>Effective date: </label>
#Html.TextBox("EffectiveDate", Model.EffectiveDate.HasValue ?
Model.EffectiveDate.Value.ToString("MM/dd/yyyy") : string.empty,
new { #class = "datecontrol" })
Sometimes you need to explicitly bind form data using hidden fields such as
#Html.HiddenField("EffectiveDate", Model.EfectiveDate.)
In order to bind the properties of the SpaceModel object you can add individual properties such as SiteID to the view model or add a SpaceModel property for a single SpaceModel. If you want to successfully bind a complex model, add it as a Dictionary populated with key-value pairs rather than a List. You should then add the dictionary to the view model. You can even add a dictionary of dictionaries for hierarchical data.
I hope this helps :)

MVC 3 Unobtrusive validation of a list

Question
I have created a server-side property level validation attribute. But instead of applying it to an individual field I've applied it to a List. This allows me to validate the model as a whole.
I now need to know how to convert this to work using the unobtrusive client-side validation built into MVC 3.
My current code is below to illustrate my issue...
Scenario
The basic scenario was the ability total up all the Quantity values for every row in a List grouped by the GroupNo field. If the sum of any of the groups was more than 10 then an error should be displayed.
I was kindly given an answer in a previous post to make this work server-side using a validation attribute against a List...
The model:
public class ItemDetails
{
public int SerialNo { get; set; }
public string Description { get; set; }
public int GroupNo { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class MyViewModel
{
[EnsureMaxGroupItems(10, ErrorMessage = "You cannot have more than 10 items in each group")]
public IList<ItemDetails> Items { get; set; }
}
and the validation attribute itself:
[AttributeUsage(AttributeTargets.Property)]
public class EnsureMaxGroupItemsAttribute : ValidationAttribute
{
public int MaxItems { get; private set; }
public EnsureMaxGroupItemsAttribute(int maxItems)
{
MaxItems = maxItems;
}
public override bool IsValid(object value)
{
var items = value as IEnumerable<ItemDetails>;
if (items == null)
{
return true;
}
return items
.GroupBy(x => x.GroupNo)
.Select(g => g.Sum(x => x.Quantity))
.All(quantity => quantity <= MaxItems);
}
}
and finally your controller actions will work with the view model:
public ActionResult ListItems()
{
var model = new MyViewModel
{
Items = ItemsRepository.GetItems()
};
return View(model);
}
[HttpPost]
public ActionResult ListItems(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
...
}
and next the corresponding strongly typed view:
#model MyViewModel
#Html.ValidationSummary()
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Items)
<button type="submit">Go go go</button>
}
and the last bit is the corresponding editor template that will automatically be rendered for each element of the Items collection so that you don't even need to write for loops (~/Views/Shared/EditorTemplates/ItemDetails.cshtml):
#model ItemDetails
#Html.HiddenFor(x => x.SerialNo)
#Html.LabelFor(x => x.Description)
#Html.HiddenFor(x => x.GroupNo)
#Html.LabelFor(x => x.Price)
#Html.TextBoxFor(x => x.Quantity)
Client-side unobtrusive validation possible?
I would like it all to validate using unobtrusive MVC validation. But I cannot figure out how to unobtrusively validate the EnsureMaxGroupItemsAttribute attribute against the list as a whole.
I've implemented IClientValidatable in this way:
Public Function GetClientValidationRules(metadata As System.Web.Mvc.ModelMetadata, context As System.Web.Mvc.ControllerContext) As System.Collections.Generic.IEnumerable(Of System.Web.Mvc.ModelClientValidationRule) Implements System.Web.Mvc.IClientValidatable.GetClientValidationRules
Dim result = New List(Of ModelClientValidationRule)
Dim rule = New ModelClientValidationRule() With { _
.ErrorMessage = "You cannot have more than 10 items in each group", _
.ValidationType = "itemscheck"}
result.Add(rule)
Return result
End Function
Note: the mix of VB and C# is only because the previous question I asked was answered in C#. The project is in VB but I don't mind an answer in C#.
I've created the adaptor in my JS file:
jQuery.validator.unobtrusive.adapters.addBool("itemscheck");
... and ...
jQuery.validator.addMethod("itemscheck", function (value, element, params) {
// The check has been omitted for the sake of saving space.
// However this method never gets called
return false;
});
Is there a way to hook this up to work unobtrusively?
This is not possible because your custom attribute is placed in the collection property and there are no HTML5 data-* attributes emitted at all. It is not a supported scenario by the unobtrusive client validation framework. You could write directly a custom jquery validate rule to handle this scenario if you need client validation for it.

Passing data from View to Controller action

I have a View that gets some bits of data via Action methods that return JSON data.
Depending on the combination of selected options, the user can fill some fields in a page.
What is the best way to pass the data back to a controller, in order to be saved?
The fields that contain data vary on the options selected;
I don't have a ViewModel object with all fields bound to the View.
At the moment I have this:
#Ajax.BeginForm("MyAction", null, new AjaxOptions
{
}, new { #id = "SaveForm" } )
{
.....
#Html.RadioButton("SomeRadioButton", "bla", false, new { #id = "SomeRadioButton" })
.....
#Html.TextArea("SomeTextArea", new { #id = "SomeTextArea" })
.....
Save
}
How do I get all of those control values in the Action?
I can add something like:
public void MyAction(FormCollection form)
{
.........
}
But I don't really like this option.
What's the cleanest way to implement this?
Thanks in advance
You could define a view model:
public class MyViewModel
{
public string SomeRadioButton { get; set; }
public string SomeTextArea { get; set; }
...
}
and then have your controller action take this view model as argument and leave the default model binder do its job:
[HttpPost]
public void MyAction(MyViewModel model)
{
...
}
I would also take advantage of this view model in the view in order to use strongly typed versions of the helpers:
#Ajax.BeginForm("MyAction", null, new AjaxOptions { }, new { #id = "SaveForm" })
{
#Html.RadioButtonFor(x => x.SomeRadioButton)
...
#Html.TextAreaFor(x => x.SomeTextArea)
...
<button type="submit">Save</button>
}
You can (and mostly should) use custom class for this, which will hold all fields. Read further about Model Binding - that's the way to do it with MVC.

Resources