KendoUI for jQuery - Chart dataBound event does not work - kendo-ui

Here's a simplified example of my data:
[{"Name": "Bob", "Count":3}, {"Name": "Steve", "Count":5}]
What I want is a chart title of: Total FOO: 8. So the title must be based on the data. The data is AJAX and this is an ASP.NET MVC application.
In my CSHTML I have:
.DataSource(ds => ds.Read(read => read.Action("MeMethodName", "MyControllerName")))
.Events(events => events.DataBound("setChartTitle('chartName', 'Total FOO')"))
Here's the crazy hack I had to do:
function setChartTitle(name, title) {
let chart = $("#" + name).data("kendoChart");
if (chart) {
let ds = chart.dataSource;
let total = 0;
for (let i = 0; i < ds.data().length; i++) {
total += ds.data()[i].Count;
}
chart.options.title.text = title + ": " + total;
chart.refresh();
} else if (arguments.length < 3) {
// Data source was not found and this was initiated through Kendo. Wait and try again but only once
setTimeout(function () {
sumEntityCount(name, title, "stop");
}, 500);
}
}
This is really bad.
Accessing the kendoChart returns undefined, yet the chart itself called this. This is why I need to check if (chart) above.
This leads to a hacky ELSE block I added where I can call this again with a 500 ms delay. This alone is a bug as 500ms is a random number and may not be enough. I can't ship like this.
To prevent recursion I call the same function with a different parameter.
If the values are found, then I can't just set the chart options. I need to call refresh which redraws everything.
Questions:
Why is the kendoChart data undefined initially? Why has Telerik called dataBound when there's nothing there?!
Isn't there a dataBinding event? I don't want to do this after the fact nor do I want to refresh the whole thing.

The chart itself is passed in when you declare a basic function without calling it:
.events(events => events.Render("someFunction"))
Then declare your function:
function someFunction(sender) {
// sender.chart is what I want
}
But you cannot pass any arguments here. Which means I can't use it.
The hack is to do the following:
.Events(events => events.Render("function(sender) { someFunction(sender, 'param1', 'param2', 'param3'); }"))
This gives it an actual function instead of calling a function. Kendo passes in the sender as expected and you can pass it along with new parameters to your JavaScript.
I also switched to using Render instead of DataBound.

Related

How to determine which row or control is making a DropDownList Read Data call

I have a DropDownListFor control inside a Kendo grid, and I need the value of another element in that row in order to pass a parameter to the DropDownList's server-side read method. It's a similar setup to the example here.
Here is the code which defines the DropDownListFor:
#(Html.Kendo().DropDownListFor(m => m)
.Name("LinkIdentifier")
.OptionLabel("---Select Form, etc.---")
.DataValueField("ID")
.DataTextField("Name")
.AutoBind(false)
.DataSource(source =>
{
source.Read(read =>
{
read.Action("LinkTypeIdentifierDdl", "Alerts").Type(HttpVerbs.Post).Data("getCurrentLinkType");
}).ServerFiltering(true);
})
)
And here is the javascript function which is called in .Data:
function getCurrentLinkType() {
var grid = $("#linkGrid").data("kendoGrid");
var data = grid.dataSource.data();
var dataItem = data[0];
var valueForParameter = dataItem.SomeValue
//--snipped for brevity
}
The problem above is data[0]. It only points to the first row in the grid, which won't be correct if editing any other row. If I use a javascript debugger within this method and look at "this", it's the AJAX call which is referenced, not the dropdown control. I can't specify ".Data("getCurrentLinkType(this)")" as the method.
So, how can I determine which row/control has made the call to getCurrentLinkType?
There is no context passed from the Grid to the DropDownList to the Data function, so you need to figure it out yourself.
I have found 2 ways.
1:
// If your grid is single Selectable(), use the current selection
var dataItem = grid.dataItem(grid.select());
2:
// If your grid is not Selectable or is multi Selectable, get the row/tr closest to the editor and then get the dataItem from that
var dataItem = grid.dataItem($("#LinkIdentifier").closest("tr"));

Add or trigger event after inner content to page

I have links on a table to edit or delete elements, that elements can be filtered. I filtered and get the result using ajax and get functions. After that I added (display) the result on the table using inner.html, the issue here is that after filtering the links on the elements not work, cause a have the dojo function like this
dojo.ready(function(){
dojo.query(".delete-link").onclick(function(el){
var rowToDelete = dojo.attr(this,"name");
if(confirm("Really delete?")){
.......
}
});
I need to trigger the event after filtering, any idea?
(I'm assuming that you're using Dojo <= 1.5 here.)
The quick answer is that you need to extract the code in your dojo.ready into a separate function, and call that function at the end of your Ajax call's load() callback. For example, make a function like this:
var attachDeleteEvents = function()
dojo.query(".delete-link").onclick(function(el){
var rowToDelete = dojo.attr(this,"name");
if(confirm("Really delete?")){
.......
}
});
};
Then you call this function both in dojo.ready and when your Ajax call completes:
dojo.ready(function() { attachDeleteEvents(); });
....
var filter = function(someFilter) {
dojo.xhrGet({
url: "some/url.html?filter=someFilter",
handleAs: "text",
load: function(newRows) {
getTableBody().innerHTML = newRows;
attachDeleteEvents();
}
});
};
That was the quick answer. Another thing that you may want to look into is event delegation. What happens in the code above is that every row gets an onclick event handler. You could just as well have a single event handler on the table itself. That would mean there would be no need to reattach event handlers to the new rows when you filter the table.
In recent versions of Dojo, you could get some help from dojo/on - something along the lines of:
require(["dojo/on"], function(on) {
on(document.getElementById("theTableBody"), "a:click", function(evt) {...});
This would be a single event handler on the whole table body, but your event listener would only be called for clicks on the <a> element.
Because (I'm assuming) you're using 1.5 or below, you'll have to do it a bit differently. We'll still only get one event listener for the whole table body, but we have to make sure we only act on clicks on the <a> (or a child element) ourselves.
dojo.connect(tableBody, "click", function(evt) {
var a = null, name = null;
// Bubble up the DOM to find the actual link element (which
// has the data attribute), because the evt.target may be a
// child element (e.g. the span). We also guard against
// bubbling beyond the table body itself.
for(a = evt.target;
a != tableBody && a.nodeName !== "A";
a = a.parentNode);
name = dojo.attr(a, "data-yourapp-name");
if(name && confirm("Really delete " + name + "?")) {
alert("Will delete " + name);
}
});
Example: http://fiddle.jshell.net/qCZhs/1/

I want to display the applied filter criteria on the Kendo UI Grid

How can I display any applied filter criteria on the Kendo UI Grid.
I would like to have a readonly display, of the applied criteria.
Current functionality does allow user to apply filter, but that the user has to go to the filter menu to look for the filter details.
The Kendo UI data source doesn't have a filter event, so you'd need to implement that yourself. Then when the event is triggered, you can get the current filter and format it in whatever way you want it displayed.
For example:
var grid = $("#grid").kendoGrid(...);
// override the original filter method in the grid's data source
grid.dataSource.originalFilter = grid.dataSource.filter;
grid.dataSource.filter = function () {
var result = grid.dataSource.originalFilter.apply(this, arguments);
if (arguments.length > 0) {
this.trigger("afterfilter", arguments);
}
return result;
}
// bind to your filter event
grid.dataSource.bind("afterfilter", function () {
var currentFilter = this.filter(); // get current filter
// create HTML representation of the filter (this implementation works only for simple cases)
var filterHtml = "";
currentFilter.filters.forEach(function (filter, index) {
filterHtml += "Field: " + filter.field + "<br/>" +
"Operator: " + filter.operator + "<br/>" +
"Value: " + filter.value + "<br/><br/>";
if (currentFilter.filters.length > 1 && index !== currentFilter.filters.length - 1) {
filterHtml += "Logic: " + currentFilter.logic + "<br/><br/>";
}
});
// display it somewhere
$("#filter").html(filterHtml);
});
See demo here.
Note that filters can be nested, so once that happens, this example code won't be enough - you'll have to make the code that converts the filters to HTML recursive.
In order to augment all data sources with the "afterfilter" event, you have to change the DataSource protototype instead of changing it on your instance:
kendo.data.DataSource.fn.originalFilter = kendo.data.DataSource.fn.filter;
kendo.data.DataSource.fn.filter = function () {
var result = this.originalFilter.apply(this, arguments);
if (arguments.length > 0) {
this.trigger("afterfilter", arguments);
}
return result;
}
If you want to integrate the whole thing into all grid widgets, you could create a new method filtersToHtml which gets you the HTML represenatation and add it to kendo.data.DataSource.fn like demonstrated above (or you could create your own widget derived from Kendo's grid); in the same way you could add a method displayFilters to kendo.ui.Grid.fn (the grid prototype) which displays this HTML representation in a DOM element whose selector you could pass in with the options to your widget (you could ultimately also create this element within the grid widget). Then instead of triggering "afterfilter" in the filter method, you could call displayFilters instead.
Considering the complexity of the complete implementation which always displays filters, I'd suggest extending the Kendo grid instead of simply modifying the original code. This will help keep this more maintainable and gives it a bit of structure.
how about combining two filters of grid.
this way the user can see the selected filter in text box and even remove it by hitting the 'x' button on filtered column text box.
you can do this by setting grid filterable like this
filterable: {
mode: "menu, row"
}
the documentation and example is in here

Kendo UI MVC Grid row selecting

I need to execute a History push state to some Action on Grid click...
I looked the API, and I think the Events Change its the right place to do that...
So, thats the code i´d like to put in there
"window.History.pushState(null, null," + "'" + Url.Action("Edit", "MyController") + "/' + Id);"
I used that on a similar JS Grid and works fine, but I dont know how to do that with Kendo UI MVC Wrapper...
I intend to use that directly on Grid definition, so I dont have to create any JS method... Something like that :
.Events(events => events.Change(x => "window.History.pushState..."))
Is that possible? How get the ID and declare Url.Action there?
Thanks
The docs for the "change" event have an example of how to get the data item for the selected row(s).
The MVC helper expects the string name of a JS function to be passed to events.Change() but I think you can define a function there too.
So something like:
#{
var url = Url.Action("Edit", "MyController");
}
...
.Events(events => events.Change(x => #"function (changeEvent) {
var selectedRows = this.select();
var dataItem;
var numSelectedRows = selectedRows.length;
for (var i = 0; i < numSelectedRows ; i++) {
dataItem = this.dataItem(selectedRows[i]);
selectedDataItems.push(dataItem);
window.History.pushState(null, null, """ + url + #"/"" + dataItem.Id);
}
}"))
I don't have a Kendo MVC project open in front of me to verify the syntax, but that should be pretty close, or at least get you pointed in the right direction.

Backbone.js: avoid view→model→view double conversion

I’m trying to build this:
When I edit field on the left it should update the one on the right and vice-versa.
Editing a value in an input field causes the text cursor to jump at the end of it.
Typing "2" in the fahrenheit field gets replaced with 1.999999999999, as you can see on the screenshot. This happens because of the double conversion:
view’s Fº → model’s Cº → view’s Fº.
How can I avoid that?
Update:
I want to know the elegant way of dealing with two-way bindings in MVC frameworks such as Backbone.js.
MVC
var Temperature = Backbone.Model.extend({
defaults: {
celsius: 0
},
fahrenheit: function(value) {
if (typeof value == 'undefined') {
return this.c2f(this.get('celsius'));
}
value = parseFloat(value);
this.set('celsius', this.f2c(value));
},
c2f: function(c) {
return 9/5 * c + 32;
},
f2c: function(f) {
return 5/9 * (f - 32);
}
});
var TemperatureView = Backbone.View.extend({
el: document.body,
model: new Temperature(),
events: {
"input #celsius": "updateCelsius",
"input #fahrenheit": "updateFahrenheit"
},
initialize: function() {
this.listenTo(this.model, 'change:celsius', this.render);
this.render();
},
render: function() {
this.$('#celsius').val(this.model.get('celsius'));
this.$('#fahrenheit').val(this.model.fahrenheit());
},
updateCelsius: function(event) {
this.model.set('celsius', event.target.value);
},
updateFahrenheit: function(event) {
this.model.fahrenheit(event.target.value);
}
});
var temperatureView = new TemperatureView();
No MVC
celsius.oninput = function(e) {
fahrenheit.value = c2f(e.target.value)
}
fahrenheit.oninput = function(e) {
celsius.value = f2c(e.target.value)
}
function c2f(c) {
return 9/5 * parseFloat(c) + 32;
}
function f2c(f) {
return 5/9 * (f - 32);
}
Not only it fixes the problem, it’s also reduces the code 3.5⨉. Clearly I’m doing MVC wrong.
Here's my take on this; instead rendering the whole view on every change, in interactive views, use the view's jQuery or plain JS contexts just like your non-MVC example.
http://jsbin.com/fomugixe/1/edit
As the Backbone docs say:
"Two way data-binding" is avoided. While it certainly makes for a nifty demo, and works for the most basic CRUD, it doesn't tend to be terribly useful in your real-world app. Sometimes you want to update on every keypress, sometimes on blur, sometimes when the panel is closed, and sometimes when the "save" button is clicked.
Two methods come to mind. As Kinakuta mentioned you can do something like the following so you're math works on integers, instead of decimals:
temp = ((oldTemp * 100) * conversion stuff) / 100
Depending on how complex you want your app to be you can also use something like Backbone.ModelBinder. It automatically binds your view to your model so when one updates, the other updates automatically. You can then attach a converter function to the binding so when your value goes view -> model or model -> view it's run through the converter. I can elaborate more if that idea interests you.
Update: With a simple temp converter it's not surprising that Backbone requires 3.5x as much code. An MVC framework can reduce bloat in a large project, but for a small app it might be overkill. e.g. imagine using Backbone to display "Hello World".
As for your issue, how about only rendering the other input value when one is changed, instead of both? If F input changes, re-render value in C box. With ModelBinder I would do this by having two attributes in my model: tempF and tempC. When one is modified, I re-calculate the other and ModelBinder automatically displays it. Or you can go without MB and just listen for the change event.
set a variable at the view level where you hold the input field that started the conversion, so you don't call the conversion function on that field.

Resources