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

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

7 Answers 397 Views
ComboBox
This is a migrated thread and some comments may be shown as answers.
Dave
Top achievements
Rank 1
Dave asked on 28 Aug 2014, 09:00 PM
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




7 Answers, 1 is accepted

Sort by
0
Georgi Krustev
Telerik team
answered on 29 Aug 2014, 09:01 AM
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.

 
0
Dave
Top achievements
Rank 1
answered on 02 Sep 2014, 03:45 PM
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.
0
Dave
Top achievements
Rank 1
answered on 02 Sep 2014, 07:57 PM
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.

0
Georgi Krustev
Telerik team
answered on 03 Sep 2014, 11:39 AM
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.

 
0
Dave
Top achievements
Rank 1
answered on 04 Sep 2014, 02:11 PM
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
0
Dave
Top achievements
Rank 1
answered on 04 Sep 2014, 02:27 PM
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
0
Georgi Krustev
Telerik team
answered on 05 Sep 2014, 03:16 PM
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.

 
Tags
ComboBox
Asked by
Dave
Top achievements
Rank 1
Answers by
Georgi Krustev
Telerik team
Dave
Top achievements
Rank 1
Share this question
or