Sort Kendo Multiselects selected Items by DataTextField - asp.net-mvc

We are using kendo multiselect with mvc wrappers. Everything on the setup works fine, but the selected items are sorted by the data value field. I simply want them to be sorted by the data text field, but nothing worked so far.
#(Html.Kendo().MultiSelectFor(m => m.SelectedPersonIds)
.HtmlAttributes(new { style = "width: 400px" })
.DataTextField("Name")
.DataValueField("PersonID")
.Filter("contains")
.Height(400)
.DataSource(ds =>
{
ds.Read(read =>
{
read.Action("GetPersons", "Person", new { area = "" });
});
})
.ItemTemplateId("detailTemplate")
.TagTemplateId("valueTemplate")
)
This is the working version. I tried adding
ds.Custom().Sort(s => s.Add("Name").Ascending());
and other approaches, but still no luck. The initial data, coming from the server, is sorted and therefore the list you select from is sorted perfectly (by Name).
How can i achieve that the selected items are also sorted by Name instead of by ID?
Thanks in advance.

Do not manually rearrange the DOM elements. Doing this will break the mapping between the displayed items and the internal data. If you were to manually rearrange the DOM elements, then deselecting an item will cause a different data item to be deselected in the underlying data. Binding back to the server will yield incorrect results.
Here is a correct way to order it, given that the value field is PersonID but you want it sorted by Name
function orderMultiSelect(multi) {
const dataItems = multi.dataItems();
dataItems.sort(
(a, b) => {
const textA = a.Name;
const textB = b.Name;
return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
}
);
const values = dataItems.map(di => di.PersonID);
multi.value(values);
}
function onMultiselectChange(e) {
orderMultiselect(e.sender);
}
You would then bind this handler function to the to the DataBound and Change events:
#(Html.Kendo().MultiSelectFor(m => m.SelectedPersonIds)
.HtmlAttributes(new { style = "width: 400px" })
.DataTextField("Name")
.DataValueField("PersonID")
.Filter("contains")
.Height(400)
.DataSource(
ds =>
{
ds.Read(
read =>
read.Action("GetPersons", "Person", new { area = "" })
);
}
).ItemTemplateId("detailTemplate")
.TagTemplateId("valueTemplate")
.Event(
e =>
e.DataBound("onMultiselectChange")
.Change("onMultiselectChange")
)
)
http://plnkr.co/edit/0gS9SDeccgOmfsGp?preview

I don't know is there is ASP.NET solution but i can give you JavaScript to solve it:
function onMultiselectChange(e) {
e.sender.tagList.find('> li').sort(function (a, b) {
return $(a).text() > $(b).text();
}).appendTo(e.sender.tagList);
}
});
You can apply it to your ASP.NET multiselect like this:
.Events(e =>
{
e.Change("onMultiselectChange")
})

Related

Change dropdownlist to autocomplete in scheduler popup editor in Kendo MVC

For example, I bind the resource as follows.
.Resources(resource =>
{
resource.Add(m => m.UserName)
.Title("Staff")
.Name("Staff")
.DataTextField("Text")
.DataValueField("Value")
.DataSource(source => source
.Read(read => read.Action("GetStaffList", "Scheduler"))
);
})
When the editor popups, the staff input has type of dropdown list which contains all the Staff in database.
Question: How to change it to an autocomplete widget with server filtering by staff's name for example.
Thank you.
I would jump into JavaScript by hooking up a event on edit to the grid, then you can do it there doing something like this
function onEdit(e) {
//Autocompleter
$("#Staff").kendoAutoComplete({
dataSource: {
serverFiltering: true,
transport: {
read: { url: "/Staff/GetListForAutocompleter" },
parameterMap: function (data, action) {
var newParams = { filter: $("#Staff").val(), filter2: 'something' };
return newParams;
}
}
}
});
}

Kendo Grid and one row of JSON data and a single row of data

I feed JSON results to My Kendo Grid; seems like when I have one row of data, the grid can't function?
Following works:
<DocumentElement>
<ResultXml>
<NoData>1</NoData>
</ResultXml>
<ResultXml>
<NoData>2</NoData>
</ResultXml>
</DocumentElement>";
and this doesn't work:
<DocumentElement>
<ResultXml>
<NoData>1</NoData>
</ResultXml>
</DocumentElement>";
EDIT to clear out above xml is converted to json before being fed to the grid (I included the xml version of the json for readability purposes)
Obviously above is xml; after using Newtonsoft.Json.JsonConvert, I get the following JSON results to feed to my grid (none of which works):
{"DocumentElement":{"ResultXml":"Nothing to display"}}
{"DocumentElement":{"ResultXml":{"field":"Nothing to display"}}}
Following works, but I want to avoid sending an empty field if I don't have to
{"DocumentElement":{"ResultXml":[{"NoData":null},{"NoData":"Nothing to display"}]}}
End of Edit
here's my Kendo Grid:
$("#grid").kendoGrid({
sortable: true,
groupable: true,
scrollable: true,
height: "600px",
pageable: { pageSizes: 9 },
dataSource:
{
transport:
{
read: function (options) {
$.ajax("/Controller/Action?param=" + paramVal,
success: function (result) {
var jResult = $.parseJSON(result);
options.success(jResult.DocumentElement.ResultXml);
});
}
}
},
});
I guess I can hardcode the json string when sending it back to client; I would've liked to have the xml version of saying I have empty dataset; but I guess this'll do, unless someone can suggest something better;
if (string.IsNullOrEmpty(xmlResult))
{
//No data:
jsonData = "{\"DocumentElement\":{\"ResultXml\":[{\"NoData\":\"Nothing to display\"}]}}";
}
else
{
//Turn xml data to Json:
var doc = new XmlDocument();
doc.LoadXml(xmlResult);
jsonData = JsonConvert.SerializeXmlNode(doc);
}
I believe the field name does not need double quotes. Ex/ name\value pair as in {name: "Bob Mazzo"}
Are you sure that there are 2 definition (one for View,the other for retrieving data) in your controller and the View calls the other one as below?
Controller:
//!!! This is for calling "CustomAjaxBinding" view
public ActionResult CustomAjaxBinding()
{
return View();
}
//!!! This is for retrieving data called from "CustomAjaxBinding" View
public ActionResult CustomAjaxBinding_Read([DataSourceRequest] DataSourceRequest request)
{
var dataContext = new SampleEntities();
//Convert to view model to avoid JSON serialization problems due to circular references.
IQueryable<OrderViewModel> result = dataContext.Orders.Select(o => new OrderViewModel
{
OrderID = o.OrderID,
ShipCity = o.ShipCity,
ShipCountry = o.ShipCountry,
ShipName = o.ShipName
});
return Json(result, JsonRequestBehavior.AllowGet);
}
View (name is "CustomAjaxBinding")
#(Html.Kendo().Grid<Kendo.Mvc.Examples.Models.Order>()
.Name("Grid")
.Columns(columns => {
columns.Bound(o => o.OrderID).Groupable(false);
columns.Bound(o => o.ShipCity);
columns.Bound(o => o.ShipCountry);
columns.Bound(o => o.ShipName);
})
.Pageable()
.Sortable()
.Filterable()
.Scrollable()
.Groupable()
.DataSource(dataSource => dataSource
.Ajax()
//!!! Call "CustomAjaxBinding_Read" not CustomAjaxBinding
.Read(read => read.Action("CustomAjaxBinding_Read", "Grid"))
)
)
Regards...

Should I query the database as user types or left the job to Bloodhund?

I'm using Typeahead.js to search products in our webapp.
We are currently using Bloodhound to do a remote fetch of suggestions.
In the server, I'm doing a database query using the string entered by the user.
But I suspect, that the idea of using Bloodhound is not to do that in the server...
My JS code is as follow:
var products = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 205,
prefetch: 'Product/GetData?q=&ft=true',
remote: 'Product/GetData?q=%QUERY'
});
products.initialize();
$('#the-basics .typeahead').typeahead(null, {
name: 'products',
displayKey: 'name',
source: products.ttAdapter(),
minLength: 3,
templates: {
empty: [
'<div class="empty-message">',
'No products for current query',
'</div>'
].join('\n'),
suggestion: Handlebars.compile('<p><strong>{{name}}</strong></p>')
}
});
I have a Action in my Product controller to retrieve a list of products... so that on Prefetch I'm only returning the ACTIVE products... but then is you write something in the text box the remote option is triggered and returns all that matches the query.
My GetData action code:
public virtual JsonResult GetData(string q, bool ft = false)
{
if (ft == true)
{
var fetchTag = db.Products.AsNoTracking()
.Where(x => x.Active == true && x.idTenant == CurrentTenantID)
.OrderByDescending(x => x.Active).ThenBy(x => x.Nombre)
.Take(20)
.Select(x => new TagSys() { tag = x.idCaballo, name = x.Nombre }).ToList();
return Json(fetchTag, JsonRequestBehavior.AllowGet);
}
else
{
var fetchTag = db.Products.AsNoTracking()
.Where(x => x.idTenant == CurrentTenantID && x.Nombre.StartsWith(q.ToLower()))
.OrderByDescending(x => x.Active).ThenBy(x => x.Nombre)
.Take(20)
.Select(x => new TagSys() { tag = x.idCaballo, name = x.Nombre }).ToList();
return Json(fetchTag, JsonRequestBehavior.AllowGet);
}
}
First issue:[Solved] despite the fact that the query entered matched values in the Prefetch data, it returns values from the remote data. Why is this? ANSWER: The bloodhound tokenizer need a datum field/property as parameter. "value" was not a property in my datum... changing to "name" in my caso solved the issue.
Second: Should I query the database using the query value entered by user in the remote method, or should I return a big list and let Bloodhound do the work?
Third:[solved] I used the minLength: 3 option, hoping that it will start suggesting/searching just after 3 chars in the input, but I'm seeing that it is triggered after the first key pressed. ANSWER: this option is top level, not dataset related, so in my code, I replaced the null parameter in typeahead init with { minLength: 3} and it worked.

How do I update a KendoUI chart via javascript JSON?

So I have a chart which is configured using the MVC style configuration...
#(Html.Kendo().Chart<DIMVC.ViewModel.CompanyProduction>(Model.CompanyProduction)
.Name("Chart")
.Title("Files sent")
.Legend(legend => legend
.Position(ChartLegendPosition.Bottom)
)
.ChartArea(chartArea => chartArea
.Background("transparent")
)
.SeriesDefaults(seriesDefaults =>
seriesDefaults.Line().Style(ChartLineStyle.Smooth)
)
.Series(series => {
series.Line(model => model.SentFiles).Name("Sent Files");
... { lots more series added here }
}
.CategoryAxis(axis => axis
.Categories(model => model.MonthDisplay)
.Labels(labels => labels.Rotation(-90))
)
.ValueAxis(axis => axis.Numeric()
.Labels(labels => labels.Format("{0:N0}"))
.MajorUnit(10000)
)
.Tooltip(tooltip => tooltip
.Visible(true)
.Format("{0:N0}")
)
.Events(e => e
.SeriesClick("onSeriesClick")
)
)
I also have a slider on the page. When the slider value is changed I handle this event.
#(Html.Kendo().RangeSlider()
.Name("yearRange")
.Min(2000)
.Max(DateTime.Today.Year)
.SmallStep(1)
.LargeStep(5)
.Values(Model.MinYear, Model.MaxYear)
.Events(e => e.Change("yearRangeChange"))
)
javascript method
function yearRangeChange(e)
{
var url = "/FetchData/";
$.ajax({
type: "GET",
url: url,
data: { startYear: e.values[0], endYear: e.values[1] },
dataType: "json",
success: function (json) {
$("#DINETChart").kendoChart({
dataSource: {
data: json
}
});
var chart = $("#DINETChart").data("kendoChart");
chart.refresh();
}
});
}
now when the chart is updated the grid is just blank.
The json request is successfully called and the data is retrieved.
but after the chart is populated the chart is blank.
has anyone got any suggestions?
* EDIT *
adding a sample of the JSON returned
"[{\"CompanyID\":1,\"Year\":2011,\"Month\":8,\"SentFiles\":1666,\"ReceivedFiles\":1632,\"SentData\":12803.674593292486,\"ReceivedData\":11908.047586546765,\"Note\":null,\"MonthDisplay\":\"Aug\",\"CompanyDisplay\":null},{\"CompanyID\":1,\"Year\":2013,\"Month\":10,\"SentFiles\":21004,\"ReceivedFiles\":20387,\"SentData\":157376.825542573,\"ReceivedData\":152878.87845794103,\"Note\":null,\"MonthDisplay\":\"Oct\",\"CompanyDisplay\":null},{\"CompanyID\":1,\"Year\":2013,\"Month\":4,\"SentFiles\":9989,\"ReceivedFiles\":9880,\"SentData\":74913.53277995327,\"ReceivedData\":75145.16331588416,\"Note\":null,\"MonthDisplay\":\"Apr\",\"CompanyDisplay\":null},{\"CompanyID\":1,\"Year\":2013,\"Month\":11,\"SentFiles\":25956,\"ReceivedFiles\":25249,\"SentData\":196155.8977337967,\"ReceivedData\":189320.44546897494,\"Note\":null,\"MonthDisplay\":\"Nov\",\"CompanyDisplay\":null}]"
I would also like to point out that if I add this
.DataSource(ds => ds.Read(read =>
read.Action("FetchData", "Home", new { startYear = 2012, endYear = 2013 })
))
to my chart configuration, this will populate the chart fine, without using the page Model.
i.e. The data is correct for the chart.
The data source expects an array as value for its data, but it looks like the json variable you're assigning contains a JSON string (at least if the string you added to the question is what you're seeing on the client in the json var). jQuery should normally parse that for you if you set dataType to "json" (not sure why that is not happening for you - you should double check that the dataType param is set correctly).
You can try parsing it yourself; apart from that, you should also use chart.setDataSource() instead of creating a new chart. They way you're doing it now, you're simply replacing your original chart with its configuration with a chart that has no configuration. Try something like this in your callback:
var data = JSON.parse(json);
var chart = $("#DINETChart").data("kendoChart");
var dataSource = new kendo.data.DataSource({
data: data
});
chart.setDataSource(dataSource);
Note that in your yearRangeChange function, you're trying to call refresh on the chart outside of your ajax success call. You don't need to refresh when you use setDataSource, but even if you needed to do that, it would have to be in the callback. Otherwise it happens before the ajax call completes.

How to bind kendo mvc ui dropdownlist dynamically

I am working on asp.net mvc with Kendo UI mvc. I have two kendo dropdown lists. one for list of clinics and another of list of patients in selected clinic. But there is no direct relationship between clinic and patient to use the cascading dropdownlist. for that i have used ajax calls in dropdownlist change event and get list of patients. and this is my first dropdownlist for list clinics
#(
Html.Kendo().DropDownList()
.Name("ddlClinics")
.Events(e=>e.Change("ChangeClinic"))
.BindTo(new SelectList((List<Account.Entities.Clinic>)ViewBag.lstClinic,
"ClinicID", "ClinicName")))
and this is my second dropdownlist for listpatients
#(
Html.Kendo().DropDownList()
.Name("ddlPatients")
.BindTo(new SelectList((List<Patient>)ViewBag.Patients,
"PatId", "PatName"))))
I want to dynamically bind the list of patients to second dropdownlist when the first dropdownlist changes,
function ChangeClinic()
{
$.ajax({
url: '/Messages/GetPatient',
type: 'Post',
data: { email: '#User.Identity.Name' },
cache: false,
success: function (result) {
var ddlPatients = $('#ddlPatients').data('kendoDropDownList');
var main = [];
$.each(result, function (k, v) {
main.push({ "PatId": v.PatId, "PatName": v.PatName });
});
ddlPatients.dataTextField = "PatName";
ddlPatients.dataValueField = "PatId";
ddlPatients.dataSource.data(main);
ddlPatients.reload();
}
});
}
i am able to bind the list to dropdownlist but all items are shows as 'undefined'. so please guide me.
From what I can tell, there is a relationship between clinics and patients so you should be able to use the CascadeFrom("DropDownList1") provided in the wrappers. We use a cascading dropdownlist in a similar fashion for the relationship between school districts and schools:
#(Html.Kendo().DropDownList()
.Name("District")
.HtmlAttributes(new { style = "width:300px;" })
.BindTo(ViewBag.districts)
.DataTextField("DistrictName")
.DataValueField("DistrictID")
.OptionLabel("Select District")
)
#(Html.Kendo().DropDownList()
.Name("School")
.HtmlAttributes(new { style = "width:300px;" })
.CascadeFrom("District")
.BindTo(ViewBag.schools)
.DataTextField("SchoolName")
.DataValueField("SchoolID")
.OptionLabel("Select School")
)
If you want fill second DropDown on basis of first DropDown value.
Telerik Provided,
.CascadeTo("DropDownList2")
Please see following link for detailed information.
Cascading of Dropdown in Telerik dropdownlist
Instead of creating such array which is useless to the dataSource use:
success: function (result) {
var ddlPatients = $('#ddlPatients').data('kendoDropDownList');
var main = [];
$.each(result, function (k, v) {
main.push({ "text": v.PatId, "value": v.PatName });
});
ddlPatients.dataSource.data(main);
}
});
If you are not using
.DataSource(source =>
{
source.Read(read =>
{
read.Action ("FunctionName", "ControllerName").Data("filterDropdown1");
}).ServerFiltering(true);
})
.CascadeFrom("Dropdown1")
properties in the definition of second dropdown and you are using the definition mentioned in question above. i.e:-
#(
Html.Kendo().DropDownList()
.Name("ddlPatients")
.BindTo(new SelectList((List<Patient>)ViewBag.Patients,"PatId", "PatName"))
)
then you can bind the data to the 2nd dropdown directly in the success function of ajax post.
function ChangeClinic()
{
$.ajax({
url: '/Messages/GetPatient',
type: 'Post',
data: { email: '#User.Identity.Name' },
cache: false,
success: function (result) {
$('#ddlPatients').data('kendoDropDownList').dataSource.data(result);
//ddlPatients.reload();
}
});
}
#Note:- 1) The result value should contain the list of new patients with properties "PatId" and "PatName" based on the parameter email passed to the function "Messages" in GetPatient controller, and there will be no need for ddlpatients.reload(), infact .reload() is not supported, it will break the execution, so don't use .reload().

Resources