Init combobox with current value, retrieve entire list if search attempted

8 posts, 0 answers
  1. Dave
    Dave avatar
    15 posts
    Member since:
    Aug 2013

    Posted 28 Aug 2014 Link to this post

    Is there a way to configure a combobox and a datasource using MVVM to have an initial value, but if the user starts typing in the box to go to the server and fetch all the remaining items from an MVC controller action that returns a JSON result?

    I've tried doing an add to the datasource for the initial item, but that just keeps it from retrieving stuff from the server.  There also doesn't seem to be a built in event that would let me know that the user is trying to search (open doesn't fire if the user tabs in and starts typing unless the text is ALREADY in the data source).  I've also played with serverFiltering, but I just don't seem to have the right combination get what I'm after.  It would seem this is a common scenario where you put some data in a combobox, but avoid bringing down a list of things till a user really needs them.  Considering the number of look ups on  my screen, I do not want to load data for look ups that aren't going to be used by the user. 

    In view:
    <input id="assetCategoryId" name="assetCategoryId" elite-remaining-field="AssetCategory"
      data-role="combobox"
      data-auto-bind="false"
      data-placeholder="Select a status"
      data-value-primitive="true"
      data-text-field="text"
      data-value-field="id"
      data-bind="value: assetCategoryId, source: assetCategoryList" />

    In the viewmodel:
    assetCategoryId : 1,
    assetCagetoryList:  null;

    intialize : function(data) {
        viewModel.set('assetCategoryId', data.assetCategoryId);

        var ds = new kendo.data.DataSource({
               //serverFiltering: true,
               transport: {
              read: {
                    url: "/dropdown/AssetCategory",
                    dataType: "json"
               }
            }
        }); 

       // ds.add({ id: data.assetCategoryId,, text: data.assetCategoryText});
       viewModel.set('assetCategoryList', ds);
    },


    I fear that I'm going to have to wire all this up myself:
    -- Ditch the datasource and use an array
    -- Populate the array with my initial data
    -- Tie into the keydown event of the combobox
    -- Upon getting a keydown, check the length of the array, if it is 1 or less, go get the rest of my data; otherwise, do nothing.


    d




  2. Georgi Krustev
    Admin
    Georgi Krustev avatar
    3707 posts

    Posted 29 Aug 2014 Link to this post

    Hello Dave,

    In general, widget support such behavior - request data only on popup open or typing. This, however, changes when MVVM value binding is used, because it sets the model value via widget value method, which on the other hand will trigger Ajax request if the widget is empty. In order to overcome this behavior, you will need to use a custom MVVM value binding, which will implement this deferred value setting and data  binding. I would suggest you check this code library, which shows exactly how to accomplish this task (ComboBox is an editor template in the Grid).

    Regards,
    Georgi Krustev
    Telerik
     

    Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

     
  3. UI for ASP.NET MVC is VS 2017 Ready
  4. Dave
    Dave avatar
    15 posts
    Member since:
    Aug 2013

    Posted 02 Sep 2014 in reply to Georgi Krustev Link to this post

    Georgi, 

    Why does't something like this work:

    <h2>Combobox Server Side Filter</h2>

    <input id="stateId" name="stateId"
           data-role="combobox"
           data-auto-bind="true"
           data-placeholder="Select a state"
           data-value-primitive="true"
           data-text-field="text"
           data-value-field="id"
           data-min-lenght="3"
           data-bind="value: stateId, source: stateList" />

    @section scripts
    {
        <script>
            // Viewmodel
            var viewModel = kendo.observable({
                stateId:  1,
                stateList: null,

                initialize: function (stateId, stateName) {
                    viewModel.set('stateId', stateId);

                    var ds = new kendo.data.DataSource({
                        serverFiltering: true,
                        transport: {
                            read: {
                                url: "/combobox/FilterStates",
                                dataType: "json",
                                data: function () { return { text: $('#stateId').data('kendoComboBox').text() } }
                            }
                        }
                    });

                    // Add in my initial value
                    ds.add({ id: stateId, text: stateName });

                    viewModel.set('stateList', ds);
                },
            });

            // Initialize
            viewModel.initialize(1, 'Texas');

            // bind it all up
            kendo.bind(document.body, viewModel);
        </script>
    }


    This causes the control to do a call to the server with text to an empty string, which doesn't return anything since I don't want to give the user all the data (every state in the database).  If I DO return all the data, it works fine.
  5. Dave
    Dave avatar
    15 posts
    Member since:
    Aug 2013

    Posted 02 Sep 2014 Link to this post

    Georgi, 

    I think I have something that will work.  It requires that I set server side filtering to false, load my data, bind the viewmodel to the view and then turn server side filtering back on again. It's a pain, but it avoids touching the server a second time:


    <h2>Combobox Server Side Filter</h2>

    <input id="stateId" name="stateId"
           data-role="combobox"
           data-auto-bind="false"
           data-placeholder="Select a state"
           data-filter="contains"
           data-text-field="text"
           data-value-field="id"
           data-min-length="3"
           data-bind="value: stateId, source: stateList" />

    @section scripts
    {
        <script>
            // Warning:  I removed the filter above (set it to none instead of contains),
            //           server side filtering stopped working.
            
           // Viewmodel
            var viewModel = kendo.observable({
                stateId: null,
                stateList: null,

                initialize: function(stateId, stateName) {
                    viewModel.set('stateId', stateId);
                    viewModel.set('initialStateName', stateName);

                    var ds = new kendo.data.DataSource({
                        serverFiltering: false,
                        transport: {
                            read: {
                                url: "/combobox/FilterStates",
                                dataType: "json",
                                data: function () { return { text: $('#stateId').data('kendoComboBox').text() }; }
                            }
                        }
                    });

                    ds.add({ id: stateId, text: stateName });

                    viewModel.set('stateList', ds);
                },
            });

            // Initialize
            viewModel.initialize(45, 'Virginia');

            // bind it all up
            kendo.bind(document.body, viewModel);

            // You can't trun on server side filtering till after you bind to the viewModel because 
            // the initial value in stateId is not null and will cause it to do a query to the server 
            // this will cause the text to be incorrect (blank because it doesn't have a value till after
            // the bind is complete).
            viewModel.get('stateList').options.serverFiltering = true;

        </script>
    }



    I did find another option, but it hit the server a second time.  It involved replacing the data function with an external function that would pass in my initial text for the first time it was called and then whatever was actually IN the control the second time.

    I still think this is an extremely common scenario that should be handled by the control itself.  

    FYI, I could not get your custom binder to work.  It would put the state name in the input box, but would never do a server side query.

  6. Georgi Krustev
    Admin
    Georgi Krustev avatar
    3707 posts

    Posted 03 Sep 2014 Link to this post

    Hello Dave,

    Your approach will work, but I still suggest custom value binding as a preferable way to handle this scenario. I prepared a Dojo demo based on your code snippets demonstrating how to use custom value binding. Please note that widget will not request server on open if data source has an item in it.

    In general, the widget does not know about MVVM and actually MVVM value binding is responsible for widget binding. Currently default MVVM value binding does not honor the widgets with deferred binding capability (autoBind: false). That is why a custom value binding should be used to handle such specific cases.

    Regards,
    Georgi Krustev
    Telerik
     

    Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

     
  7. Dave
    Dave avatar
    15 posts
    Member since:
    Aug 2013

    Posted 04 Sep 2014 in reply to Georgi Krustev Link to this post

    Georgi,

    Thank you for the reply and example.  It was very helpful and allowed me to find my problem within the code I originally tried to get working.   My main problem was that I forgot to add a data-filter='contains' to my binding and it just doesn't work without it.   As I said, I went back to your original example and it now looks like this and works:


    <h2>Combobox example (custom binder -- defferred retrieval)</h2>

    <input type="text" value="before" />
    <br /><br />
    <input id="stateId" name="stateId"
           data-role="combobox"
           data-auto-bind="false"
           data-placeholder="Select a state"
           data-filter="contains"
           data-value-primitive="true"
           data-text-field="text"
           data-value-field="id"
           data-min-length="3"
           data-bind="defferedValue: stateId, source: stateList" />
    <br /><br />
    <input type="text" value="after" />


    @section scripts
    <script>
        // Custom binder
        // create a custom binder that works only with Objects and honours "autoBind:false" state
        kendo.data.binders.widget.defferedValue = kendo.data.Binder.extend({
            init: function (widget, bindings, options) {
                kendo.data.Binder.fn.init.call(this, widget.element[0], bindings, options);
                this.widget = widget;
                this._change = $.proxy(this.change, this);
                this.widget.bind("change", this._change);
            },
            refresh: function () {
                if (!this._initChange) {
                    var widget = this.widget;
                    var value = this.bindings.defferedValue.get();

                    if (value) {
                        if (widget.options.autoBind === false) {
                            //Bind the widget with single item if deffered binding is used
                            widget.dataSource.data([value]);
                            widget.value(value[widget.options.dataValueField]);
                        } else {
                            //set widget value directly
                            this.widget.value(value[widget.options.dataValueField]);
                        }
                    }
                }
            },
            change: function () {
                this._initChange = true;
                this.bindings.defferedValue.set(this.widget.dataItem() || null);
                this._initChange = false;
            },
            destroy: function () {
                this.widget.unbind("change", this._change);
            }
        });

        // Viewmodel
        var viewModel = kendo.observable({
            stateId: null, // This will be an object id and text of the state
            stateList: null,

            initialize: function (stateId, stateText) {
                viewModel.set('stateId', { id: stateId, text: stateText });
                var ds = new kendo.data.DataSource({
                    serverFiltering: true,
                    transport: {
                        read: {
                            url: "/combobox/FilterStates",
                            dataType: "json",
                            data: function () {
                                return { text: $('#stateId').data('kendoComboBox').text() }
                            }
                        }
                    }
                });

                viewModel.set('stateList', ds);

                viewModel.bind('change', function (e) { viewModel.dataChange(e); });

            },

            dataChange: function (e) {
                //console.log(e);
                if (e.field === 'stateId') {
                    var currentStateIdValue = viewModel.get('stateId');
                    console.log(currentStateIdValue);
                }

            },
        });


        // Initialize
        viewModel.initialize(1, 'Texas');

        // bind it all up
        kendo.bind(document.body, viewModel);
    </script>
    }



    Statements
    - I think there is a typo in your example in the change function. I see deffered value spelled "defferedValue" and "deferredValue" in different places.  Please correct me if I'm wrong since I've updated it in the code above so that they match.

    Questions?
    - When do you think this will be built into the dropdownlist and combobox controls?
    - Why is it that I have to specify text when specifying a control action that returns json, but you don't have to when it is a OData end point?


    Dave
  8. Dave
    Dave avatar
    15 posts
    Member since:
    Aug 2013

    Posted 04 Sep 2014 in reply to Dave Link to this post

    Georgi,

    To clarify my question about specifying the text....I'm talking about the text the datasource sends to the server as part of the query string when you are trying to do server side filtering.  So, your OData datasource, you didn't have to specify text:
    var ds = new kendo.data.DataSource({
        type: "odata",
        serverFiltering: true,
        transport: {
             read: {
                  url: "http://demos.telerik.com/kendo-ui/service/Northwind.svc/Products",
                    }
             }
    });

    However, to target an MVC controller action, I have to put in text using the data function or it doesn't show up in the query string and inevitably in the controller's action so that I can use it to do a db query.
    var ds = new kendo.data.DataSource({
       serverFiltering: true,
       transport: {
               read: {
                     url: "/combobox/FilterStates",
                    dataType: "json",
                    data: function () {
                           return { text: $('#stateId').data('kendoComboBox').text() }
                     }
                }
           }
    });

    I'd rather not use JQuery in my datasource, how can I fix this to be more like the OData example?

    Dave
  9. Georgi Krustev
    Admin
    Georgi Krustev avatar
    3707 posts

    Posted 05 Sep 2014 Link to this post

    Hi Dave,

    Yes, "defferedValue" is definitely a typo. Thanks for updating the code snippet.

    As to the questions:

    - When do you think this will be built into the dropdownlist and combobox controls?

    Widgets have a built-in functionality regarding value method and autoBind: false state (emphasized in the docs) - they will request the server if they are not bound. This functionality cannot be changed as it will be a breaking change. 
    The only feasible solution is to introduce a MVVM value binding that is aware of the autoBind option and this particular value method behavior (this is what "deferredValue" binding does). I will log your request for further consideration. If other users want it then we will log it for investigation.

    - Why is it that I have to specify text when specifying a control action that returns json, but you don't have to when it is a OData end point?

    There is a special ODATA transport and schema type. They are responsible for the communication and query conversion with regards to the ODATA service. If you would like to omit the data callback then you can define your own transport and schema type.

    Regards,
    Georgi Krustev
    Telerik
     

    Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

     
Back to Top
UI for ASP.NET MVC is VS 2017 Ready