Hi,
I've successfully created a custom widget and can use it as the editor in a kendo grid but I can't figure out how to databind it. I want the widget to have a value property that is bound to the datasource of the grid so that when the grid is loaded, this widget will be filled with the correct value for that record and when the value is changed in the widget it will update the value of the bound field in that record of the datasource. Can you please let me know how to achieve this? Is there a simple example?
Please help.
Thanks
I've successfully created a custom widget and can use it as the editor in a kendo grid but I can't figure out how to databind it. I want the widget to have a value property that is bound to the datasource of the grid so that when the grid is loaded, this widget will be filled with the correct value for that record and when the value is changed in the widget it will update the value of the bound field in that record of the datasource. Can you please let me know how to achieve this? Is there a simple example?
Please help.
Thanks
8 Answers, 1 is accepted
0
Hi Lance,
Please note that providing assistance related to development of custom widgets is out of the scope of our support service. The standard support service - the one included in the support package that goes with the controls, cover only the built-in functionality and features of the framework.
In case you would like to create your own dataSource aware custom widget please check this tutorial.
Regards,
Alexander Valchev
Telerik
Please note that providing assistance related to development of custom widgets is out of the scope of our support service. The standard support service - the one included in the support package that goes with the controls, cover only the built-in functionality and features of the framework.
In case you would like to create your own dataSource aware custom widget please check this tutorial.
Regards,
Alexander Valchev
Telerik
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
0
Lance
Top achievements
Rank 1
answered on 10 Jul 2014, 05:46 AM
Hi Alexander,
This seems a bit mean-spirited, especially as I'm a paying customer. I've got a custom widget that works. The value of the widget is updated with the value of the field in the external datasource that is bound to it which is good, but I can't get the bound field in this datasource to be updated with the new value in the widget when there is a change. I feel I'm close but have spend a frustrating lot of time to get this far. Can you please help me finish this last piece of the puzzle?
Below is my widget code:
define(
[
'kendo',
'text!plugins/usersearch/userselection.html',
'text!plugins/usersearch/usersearch.html',
'models/userinformation'
], function (kendo, userSelectionTemplate, userSearchTemplate, userinformation)
{
//turn off autocomplete (remembering previous values) for all textboxes
$("input:text,form").attr("autocomplete", "off");
//Select all text on focus for all textboxes
$('input:text,textarea, form').on('focus', function ()
{
//use .one to only stop the first mouseup event
$(this).one('mouseup', function (event)
{
event.preventDefault();
}).select();
});
var wnd;
var wind;
var kendo = window.kendo,
ui = kendo.ui,
Widget = ui.Widget;
var CHANGE = "change";
var mainLabelElement;
var mainDivElement;
var gridDataSource = new kendo.data.DataSource({
schema: {
data: function (data)
{ //specify the array that contains the data
return data || [];
},
model: userinformation
},
transport: {
read: {
url: CommonInformationWcfServiceUrl.value + "/rest/SearchUsers",
dataType: "jsonp",
type: "GET"
},
parameterMap: function (data, operation)
{
if (operation == "read")
{
data["searchText"] = $("#searchText").val();
return data;
}
}
},
error: function (e)
{
this.set("validSearch", true);
var xhr = e.xhr;
var statusCode = e.status;
var errorThrown = e.errorThrown;
var msg = xhr.responseText;
JL().error(errorThrown + ' ' + msg);
},
change: function (data)
{
}
});
var viewModel = kendo.observable({
title: 'Select User',
gridSource: gridDataSource,
validSearch: false,
searchTextChange: function (e)
{
this.set("validSearch", $("#searchText").val().length > 2);
},
userSearchOpen: function (e)
{
},
searchUser: function (e)
{
this.set("validSearch", false);
$('#grid').data('kendoGrid').dataSource.read();
},
rowSelect: function (e)
{
var gview = e.sender.element.getKendoGrid();
var selectedItem = gview.dataItem(gview.select());
this.set("selectedUser", selectedItem.FullName);
//UserSearch.fn.value(selectedItem.BemsId);
if (mainLabelElement)
{
mainLabelElement.text(selectedItem.FullName);
}
gview.dataSource.data([]);
wind.close();
UserSearch.fn._change(selectedItem.BemsId);
},
selectedUser: "",
selectUser: function (e)
{
e.preventDefault();
wnd = $(document.createElement('div'));
// Apply template to the placeholder element, and bind the viewmodel.
wnd.html(kendo.template(userSearchTemplate)(viewModel));
kendo.bind(wnd, viewModel);
// Add window placeholder to the body.
$('body').append(wnd);
// Turn placeholder into a Window widget.
wnd.kendoWindow({
width: 500,
title: "Find User",
resizable: false,
iframe: false,
close: function ()
{
// When the window is closed, remove the element from the document.
wnd.parents(".k-window").remove();
wind.destroy();
wnd = null;
wind = null;
},
open: viewModel.userSearchOpen
});
// Centre and show the Window.
wind = wnd.data("kendoWindow").center().open();
}
});
var UserSearch = Widget.extend({
init: function (element, options)
{
var that = this;
Widget.fn.init.call(this, element, options);
that._create();
if (options)
{
if (options.value)
{
that.value(options.value);
}
}
},
options: {
name: "UserSearch",
value: null
},
events: [CHANGE],
_templates: {
label: '<label id="selectedUser" class="k-label" style="padding-right: 10px; min-width: 100px">#: value #</label>',
button: '<button class="k-button k-button-icontext" style="padding: 2px; margin: 2px; line-height:1em">...</button>',
div: '<div id="divUserSelect" data-bind="events: { keyup: searchTextChange }"></div>'
},
_create: function ()
{
var that = this;
var template = kendo.template(that._templates.label);
that.label = $(template(that.options));
template = kendo.template(that._templates.button);
that.button = $(template(that.options));
template = kendo.template(that._templates.div);
that.div = $(template(that.options));
that.div.append(that.label)
.append(that.button);
that.button.click(viewModel.selectUser);
that.element.append(that.div);
mainLabelElement = that.label;
mainDivElement = that.div;
//kendo.data.binders.value = kendo.data.Binder.extend({
// refresh: function ()
// {
// // get the value of the view model field to which the element is bound
// var value = this.bindings["value"].get();
// $(this.element).value(value);
// }
//});
////listen for the change event of the element
//$(mainDivElement).on(CHANGE, function ()
//{
// that._change(); //call the change function
//});
//bind widget to viewmodel
kendo.bind(that.element, viewModel);
},
//set: function (value)
//{
// var that = this;
// if (that._old != value)
// {
// that._update(value);
// that._old = value;
// that.trigger(CHANGE);
// }
//},
value: function (value)
{
var that = this;
if (value === undefined)
{
return that._value;
}
that._update(value);
//that._change(value);
that._old = that._value;
},
_update: function (value)
{
if (value)
{
//Update the internals of 'value'
var that = this;
that._value = value;
that.options.value = value;
//Determine if the value is different than it was before
if (that._old != value)
{
//set value
$.ajax({
contentType: 'application/json; charset=utf-8',
type: "GET",
dataType: "jsonp",
url: CommonInformationWcfServiceUrl.value + "/rest/SearchUsers?searchText=" + value,
success: function (data)
{
if (data.length > 0)
{
var name;
that.options.value = data[0].BemsId;
name = data[0].FullName;
viewModel.set("selectedUser", name);
if (mainLabelElement)
{
mainLabelElement.text(name);
}
}
},
error: function (e, xhr, opt)
{
JL().error(e.status + " " + e.statusText);
}
});
}
}
},
_change: function (value)
{
var that = this;
//Determine if the value is different than it was before
if (that._old != value)
{
//It is different, update the value
that._update(value);
//Capture the new value for future change detection
that._old = value;
//trigger the external change
that.trigger(CHANGE, { field: "value" });
//that.trigger(CHANGE);
}
}
});
ui.plugin(UserSearch);
});
This seems a bit mean-spirited, especially as I'm a paying customer. I've got a custom widget that works. The value of the widget is updated with the value of the field in the external datasource that is bound to it which is good, but I can't get the bound field in this datasource to be updated with the new value in the widget when there is a change. I feel I'm close but have spend a frustrating lot of time to get this far. Can you please help me finish this last piece of the puzzle?
Below is my widget code:
define(
[
'kendo',
'text!plugins/usersearch/userselection.html',
'text!plugins/usersearch/usersearch.html',
'models/userinformation'
], function (kendo, userSelectionTemplate, userSearchTemplate, userinformation)
{
//turn off autocomplete (remembering previous values) for all textboxes
$("input:text,form").attr("autocomplete", "off");
//Select all text on focus for all textboxes
$('input:text,textarea, form').on('focus', function ()
{
//use .one to only stop the first mouseup event
$(this).one('mouseup', function (event)
{
event.preventDefault();
}).select();
});
var wnd;
var wind;
var kendo = window.kendo,
ui = kendo.ui,
Widget = ui.Widget;
var CHANGE = "change";
var mainLabelElement;
var mainDivElement;
var gridDataSource = new kendo.data.DataSource({
schema: {
data: function (data)
{ //specify the array that contains the data
return data || [];
},
model: userinformation
},
transport: {
read: {
url: CommonInformationWcfServiceUrl.value + "/rest/SearchUsers",
dataType: "jsonp",
type: "GET"
},
parameterMap: function (data, operation)
{
if (operation == "read")
{
data["searchText"] = $("#searchText").val();
return data;
}
}
},
error: function (e)
{
this.set("validSearch", true);
var xhr = e.xhr;
var statusCode = e.status;
var errorThrown = e.errorThrown;
var msg = xhr.responseText;
JL().error(errorThrown + ' ' + msg);
},
change: function (data)
{
}
});
var viewModel = kendo.observable({
title: 'Select User',
gridSource: gridDataSource,
validSearch: false,
searchTextChange: function (e)
{
this.set("validSearch", $("#searchText").val().length > 2);
},
userSearchOpen: function (e)
{
},
searchUser: function (e)
{
this.set("validSearch", false);
$('#grid').data('kendoGrid').dataSource.read();
},
rowSelect: function (e)
{
var gview = e.sender.element.getKendoGrid();
var selectedItem = gview.dataItem(gview.select());
this.set("selectedUser", selectedItem.FullName);
//UserSearch.fn.value(selectedItem.BemsId);
if (mainLabelElement)
{
mainLabelElement.text(selectedItem.FullName);
}
gview.dataSource.data([]);
wind.close();
UserSearch.fn._change(selectedItem.BemsId);
},
selectedUser: "",
selectUser: function (e)
{
e.preventDefault();
wnd = $(document.createElement('div'));
// Apply template to the placeholder element, and bind the viewmodel.
wnd.html(kendo.template(userSearchTemplate)(viewModel));
kendo.bind(wnd, viewModel);
// Add window placeholder to the body.
$('body').append(wnd);
// Turn placeholder into a Window widget.
wnd.kendoWindow({
width: 500,
title: "Find User",
resizable: false,
iframe: false,
close: function ()
{
// When the window is closed, remove the element from the document.
wnd.parents(".k-window").remove();
wind.destroy();
wnd = null;
wind = null;
},
open: viewModel.userSearchOpen
});
// Centre and show the Window.
wind = wnd.data("kendoWindow").center().open();
}
});
var UserSearch = Widget.extend({
init: function (element, options)
{
var that = this;
Widget.fn.init.call(this, element, options);
that._create();
if (options)
{
if (options.value)
{
that.value(options.value);
}
}
},
options: {
name: "UserSearch",
value: null
},
events: [CHANGE],
_templates: {
label: '<label id="selectedUser" class="k-label" style="padding-right: 10px; min-width: 100px">#: value #</label>',
button: '<button class="k-button k-button-icontext" style="padding: 2px; margin: 2px; line-height:1em">...</button>',
div: '<div id="divUserSelect" data-bind="events: { keyup: searchTextChange }"></div>'
},
_create: function ()
{
var that = this;
var template = kendo.template(that._templates.label);
that.label = $(template(that.options));
template = kendo.template(that._templates.button);
that.button = $(template(that.options));
template = kendo.template(that._templates.div);
that.div = $(template(that.options));
that.div.append(that.label)
.append(that.button);
that.button.click(viewModel.selectUser);
that.element.append(that.div);
mainLabelElement = that.label;
mainDivElement = that.div;
//kendo.data.binders.value = kendo.data.Binder.extend({
// refresh: function ()
// {
// // get the value of the view model field to which the element is bound
// var value = this.bindings["value"].get();
// $(this.element).value(value);
// }
//});
////listen for the change event of the element
//$(mainDivElement).on(CHANGE, function ()
//{
// that._change(); //call the change function
//});
//bind widget to viewmodel
kendo.bind(that.element, viewModel);
},
//set: function (value)
//{
// var that = this;
// if (that._old != value)
// {
// that._update(value);
// that._old = value;
// that.trigger(CHANGE);
// }
//},
value: function (value)
{
var that = this;
if (value === undefined)
{
return that._value;
}
that._update(value);
//that._change(value);
that._old = that._value;
},
_update: function (value)
{
if (value)
{
//Update the internals of 'value'
var that = this;
that._value = value;
that.options.value = value;
//Determine if the value is different than it was before
if (that._old != value)
{
//set value
$.ajax({
contentType: 'application/json; charset=utf-8',
type: "GET",
dataType: "jsonp",
url: CommonInformationWcfServiceUrl.value + "/rest/SearchUsers?searchText=" + value,
success: function (data)
{
if (data.length > 0)
{
var name;
that.options.value = data[0].BemsId;
name = data[0].FullName;
viewModel.set("selectedUser", name);
if (mainLabelElement)
{
mainLabelElement.text(name);
}
}
},
error: function (e, xhr, opt)
{
JL().error(e.status + " " + e.statusText);
}
});
}
}
},
_change: function (value)
{
var that = this;
//Determine if the value is different than it was before
if (that._old != value)
{
//It is different, update the value
that._update(value);
//Capture the new value for future change detection
that._old = value;
//trigger the external change
that.trigger(CHANGE, { field: "value" });
//that.trigger(CHANGE);
}
}
});
ui.plugin(UserSearch);
});
0
Accepted
Hello Lance,
First of all let me apologize for the late reply.
In order value binding to work the widget should:
1. Have a value method that sets or gets the current widget value.
2. Change event that is fired when the user changes the widget's value. Value method should not fire the change event as this will lead to a never ending loop. Change event should be fired when the user modified widget's value through the UI. The MVVM value binding listens for the change event of the widget.
I created a very basic example that demonstrates a working value binding for custom widget. Please see it and pay attention to the comments:
trykendoui.telerik.com link
Note that the widget is not being bound from within one of its internal methods. Calling kendo.bind from within the internal _create method is not recommended, calling kendo.bind from within one of the ViewModel methods is not recommended either.
Also in your case the change event is fired from the _change method of the widget which as far as I saw is not called when some event (blur, click, focus) occurs.
Please use the sample code above as a starting point for creating a custom widget that fits in your requirements.
Regards,
Alexander Valchev
Telerik
First of all let me apologize for the late reply.
In order value binding to work the widget should:
1. Have a value method that sets or gets the current widget value.
2. Change event that is fired when the user changes the widget's value. Value method should not fire the change event as this will lead to a never ending loop. Change event should be fired when the user modified widget's value through the UI. The MVVM value binding listens for the change event of the widget.
I created a very basic example that demonstrates a working value binding for custom widget. Please see it and pay attention to the comments:
<input id=
"foo"
type=
"text"
data-role=
"customwidget"
data-bind=
"value: value"
/>
<br />
ViewModel value: <span data-bind=
"text: value"
></span>
<script>
var
CustomWidget = kendo.ui.Widget.extend({
init:
function
(element, options) {
var
that =
this
;
kendo.ui.Widget.fn.init.call(that, element, options);
//call the base widget constructor
that.element.addClass(
"k-textbox"
);
//enchange the widget
//trigger the change event of the widget on blur
that.element.on(
"blur"
,
function
() {
that.trigger(
"change"
);
});
},
options: {
name:
"CustomWidget"
},
events: [
"change"
],
value:
function
(value) {
//change event that gets or sets the value
if
(value === undefined) {
return
this
.element.val();
}
this
.element.val(value);
}
});
kendo.ui.plugin(CustomWidget);
//export the widget as plug-in
var
viewModel = kendo.observable({
value:
"foo"
});
kendo.bind($(document.body), viewModel);
//bind the UI
</script>
trykendoui.telerik.com link
Note that the widget is not being bound from within one of its internal methods. Calling kendo.bind from within the internal _create method is not recommended, calling kendo.bind from within one of the ViewModel methods is not recommended either.
Also in your case the change event is fired from the _change method of the widget which as far as I saw is not called when some event (blur, click, focus) occurs.
Please use the sample code above as a starting point for creating a custom widget that fits in your requirements.
Regards,
Alexander Valchev
Telerik
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
0
Lance
Top achievements
Rank 1
answered on 16 Jul 2014, 01:08 AM
Hi Alex,
Thanks very much for your answer. The key in my case was storing the value in .Val() of an element in the widget. I was storing it in a local private variable and the value was being lost so the datasource wasn't updated with the new value.
You said that it is not recommended to call kendo bind from within the internal _create method or from within one of the ViewModel methods but you didn't explain why. I've moved one call to kendo bind as you suggested but I couldn't see an alternative to having it in the method of my viewModel as I need to bind my dynamically created Window to it. I am doing this to communate values and events between the widget and the dynamic window.
Can you please explain why this isn't recommended and what would be the recommended way of achieving this?
Thanks
0
Hi Lance,
Kendo widgets does not depended on the ViewModel. In other words the widgets can exist with or without MVVM.
In case you insist to call kendo.bind from within the widget methods, please be sure that container you will bind is not already bound.
Regards,
Alexander Valchev
Telerik
Kendo widgets does not depended on the ViewModel. In other words the widgets can exist with or without MVVM.
In case you insist to call kendo.bind from within the widget methods, please be sure that container you will bind is not already bound.
Regards,
Alexander Valchev
Telerik
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
0
Lance
Top achievements
Rank 1
answered on 31 Jul 2014, 05:23 AM
Hi Alex,
Thanks for your reply. How do I ensure that the container is not already bound? Do I call kendo.unbind against it first?
Thanks for your reply. How do I ensure that the container is not already bound? Do I call kendo.unbind against it first?
0
Hi Lance,
Basically you can call the "unbind" method first, however this can break existing logic on the page - that why Alexander already recommended to not call the "bind" method internally.
Regards,
Vladimir Iliev
Telerik
Basically you can call the "unbind" method first, however this can break existing logic on the page - that why Alexander already recommended to not call the "bind" method internally.
Regards,
Vladimir Iliev
Telerik
Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
0
Gal
Top achievements
Rank 2
answered on 19 Sep 2015, 04:29 PM
This is a critical piece of information regarding the creation of Custom widgets!
Without this, the widget doesnt work in a grid!
This was real handy!
10x Lance for insisting on a proper reply!
10x Alexander ​for the reply!
With this information I was able to extend the kendo date/dateTime picker to include a mask.
(
function
($) {
var
kendo = window.kendo,
ui = kendo.ui,
Widget = ui.Widget;
CHANGE =
"change"
,
BLUR =
"blur"
,
[ { type:
'DateTime'
, defaultMask:
'00/00/0000 00:00'
, container:
'.k-datetimepicker'
},
{ type:
'Date'
, defaultMask:
'00/00/0000'
, container:
'.k-datepicker'
}]
.forEach(
function
(x) {
var
baseName =
'kendo'
+ x.type +
'Picker'
;
var
newName =
"Masked"
+ x.type +
"Picker"
;
var
_basePlugin = $.fn[baseName];
var
maskedPicker = Widget.extend({
init:
function
(element, options) {
var
that =
this
;
Widget.fn.init.call(
this
, element, options);
$(element).kendoMaskedTextBox({ mask: options.mask || x.defaultMask });
_basePlugin.call($(element), options)
.closest(x.container)
.add(element)
.removeClass(
"k-textbox"
);
that.element.on(
"blur"
,
function
() {
that.trigger(
"change"
);
});
},
options: {
name: newName,
dateOptions: {}
},
destroy:
function
() {
var
that =
this
;
Widget.fn.destroy.call(that);
kendo.destroy(that.element);
},
value:
function
(value) {
var
datepicker =
this
.element.data(baseName);
if
(value === undefined) {
return
datepicker.value();
}
datepicker.value(value);
},
//Export the events the control can fire
events: [CHANGE]
});
ui.plugin(maskedPicker);
$.fn[baseName] = $.fn[
'kendo'
+ newName];
});
})(jQuery);
Enjoy !