This is a migrated thread and some comments may be shown as answers.

Databound custom widget

8 Answers 297 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
Lance
Top achievements
Rank 1
Lance asked on 22 Jun 2014, 10:14 PM
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

8 Answers, 1 is accepted

Sort by
0
Alexander Valchev
Telerik team
answered on 24 Jun 2014, 02:23 PM
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
 
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);
    });
0
Accepted
Alexander Valchev
Telerik team
answered on 14 Jul 2014, 08:01 AM
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:
<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
Alexander Valchev
Telerik team
answered on 17 Jul 2014, 02:50 PM
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
 
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?
0
Vladimir Iliev
Telerik team
answered on 04 Aug 2014, 06:48 AM
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
 
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 ! 

Tags
General Discussions
Asked by
Lance
Top achievements
Rank 1
Answers by
Alexander Valchev
Telerik team
Lance
Top achievements
Rank 1
Vladimir Iliev
Telerik team
Gal
Top achievements
Rank 2
Share this question
or