jQuery UI Widgets Forums Lists ListBox using Knockout and Groups…

This topic contains 7 replies, has 2 voices, and was last updated by  Dimitar 9 years, 9 months ago.

Viewing 8 posts - 1 through 8 (of 8 total)
  • Author
  • using Knockout and Groups… #55337

    dijitald
    Participant

    I’m desperately trying to figure out how to use Groups with the Listbox widget. I’m using knockout to bind the listbox to my viewmodel. The field is an observable array and the binding works just fine. I can even use a custom renderer, but i’m trying to figure out how to use the Group feature. Part of the problem is that my data coming back from the webapi calls the field Category. So I’ve tried a few different approaches:

    * in the datafields where I wire up the data, I tried to use the Map functionality to map Category to Group like the below snippet. Didn’t work.

    var daMap =
        {
            datatype: "observablearray",
            datafields: [
                { name: 'Group', map: 'Category' },
                { name: 'Activity' },
            ],
            id: 'ActivityId',
            localdata: model.SearchResults
        }; 

    * I also tried to implement the beforeLoadComplete event to massage the data a bit on the dataAdapter like this, but it didn’t work either

    beforeLoadComplete: function (records) 
    {
        var data = new Array();
        for (var i = 0; i < records.length; i++) 
        {
            var act = records[i];
            act.group = act.Category;
            data.push(act);
        }
        return data;
    }

    So i’m out of ideas. The only other option I found was to override the LoadComplete event which is essentially brute force by injecting the generated HTML directly into the Div. I manually track the cateogries and output a category header and format the list as I like, but loses all listbox functionality (e.g. selecting an item). If I take this path, the listbox is providing no help at all and I should just rip it out because I have to do everything manually anyhow.

    I’m hoping I’m missing something obvious or easy to fix this issue, but I can’t find much detail on how the Group feature works other than a simple example.

    Thanks in advance for any help you can provide.

    using Knockout and Groups… #55379

    Dimitar
    Participant

    Hello dijitald,

    Your second approach is the right way. However, your code needs some modification:

    beforeLoadComplete: function (records) 
    {
        var data = new Array();
        for (var i = 0; i < records.length; i++) 
        {
            var act = records[i];
            act.group = act.Group;
            data.push(act);
        }
        return data;
    }

    You will also need to bind the lisbox not to dataAdapter directly but to dataAdapter.records.

    Best Regards,
    Dimitar

    jQWidgets team
    http://www.jqwidgets.com/

    using Knockout and Groups… #55450

    dijitald
    Participant

    Unfortunately, that did not work for me. When I bind to the .records property of the dataAdapter, it doesn’t populate the listbox at all. I do see it running through the records in the beforeLoadComplete event, but nothing is displayed. Here’s my structure…

    I get a list of values from a webapi call that looks like this. Note that model is an instance of my viewmodel and SearchResults is my observable array. This function gets called whenever the user types something into the search box.

            function GetActivities()
            {
                $.ajax({
                    url: "/api/activitysearch",
                    type: "POST",
                    contentType: 'application/json; charset=utf-8',
                    dataType: "json",
                    data: "{ Query : '" + model.ActivityFilter() + "' }",
                    error: function (request, status, error)
                    {
                        Notify("error", error);
                    },
                    success: function (data)
                    {
                        model.SearchResults(new Array());
                        model.SearchResults(data);
                    }
                });
            }
    

    I then have the following code to map out the fields in my object, wire up the dataAdapter with the onBeforeLoad event, and then bind that to the listbox. If I bind the listbox to the dataadapter, all of the records are displayed but without groups. If I bind it to the dataAdapter.records property, then nothing is displayed whatsoever.

    Any other ideas? or maybe a working demo?

    
                var daMap =
                    {
                        datatype: "observablearray",
                        datafields: [
                            { name: 'Category' },
                            { name: 'Activity' },
                            { name: 'ActivityId', type: 'int' },
                            { name: 'METS', type: 'float' },
                            { name: 'Favorite', type: 'bool' }
                        ],
                        id: 'ActivityId',
                        localdata: model.SearchResults
                    };
                var da = new $.jqx.dataAdapter(daMap, 
                    {
                        beforeLoadComplete: function (records) 
                        {
                            var data = new Array();
                            var act = records[i];
                            for (var i = 0; i < records.length; i++) 
                            {
                                var act = records[i];
                                act.group = act.Category;
                                data.push(act);
                            }
                            return data;
                        }
                    });
                
                $("#ActivityList").jqxListBox(
                {
                    width: '95%',
                    height: 450,
                    source: da.records,
                    displayMember: 'Activity',
                    valueMember: 'ActivityId' 
                });
    
                ko.applyBindings(model);
    
    using Knockout and Groups… #55453

    dijitald
    Participant

    Just to do a sanity check, I went ahead and eliminated the ajax calls to my webapi and just pushed some test data into my model.SearchResults() array. In the test data I used I created a ‘group’ property and that worked, but only if I bind directly to my viewmodel (model.SearhcResults()) instead of binding to the dataAdapter. So after going through lots of trial and error, I now realize that I can’t get the group functionality to work at all if i’m binding to a dataAdapter. I can see that the group property does exist, but for some reason the listbox doesn’t render the groupings if it’s bound to a dataAdapter. I’m hoping i’m just missing something stupid, so I went ahead and created a sample project out of your demos and posted it here (http://1drv.ms/1nUC4gi). Let me know if you can spot what’s going on here.

    One helpful hint that I learned the looong slooow way is that when your renderer callback is called to render an item and the Index is -1, that’s a group header it’s asking you to render. I must have missed that in the docs 🙂

    using Knockout and Groups… #55463

    Dimitar
    Participant

    Hi dijitald,

    Your data adapter should be bound before you set the listbox source to da.records. Before the listbox initialization, call:

    da.dataBind();

    Best Regards,
    Dimitar

    jQWidgets team
    http://www.jqwidgets.com/

    using Knockout and Groups… #55534

    dijitald
    Participant

    eureka.. that was it. thanks! in fact, now that I do that, I can even use the Map feature. So this works

                    var daMapNoGroup =
                        {
                            datatype: "observablearray",
                            localdata: model.SearchResultsNoGroup,
                            datafields: [
                                { name: 'group', map: 'Category' },
                                { name: 'Title' }
                            ],
                            id: 'Id'
                        };
                    var daNoGroup = new $.jqx.dataAdapter(daMapNoGroup);
                    daNoGroup.dataBind();
    using Knockout and Groups… #55552

    dijitald
    Participant

    It was too good to be true 🙁 unfortunately, this did fix the group issue, but it broke something else. Now when I add/remove items from my ObservableCollection (model.SearchResults) it does not update the listbox. I figured out I was because i’m now binding to the dataAdapter.records property instead of the dataAdapter directly as you do in this demo (http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxknockout/listboxwithdataadapter.htm).

    So now that i’m binding to the dataAdapter.records property, how can I get it to pick up changes to the underlying collection?

    using Knockout and Groups… #55570

    Dimitar
    Participant

    Hi dijitald,

    You just have to re-set the listbox source on the addition/removal of a new item, i.e.:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <link rel="stylesheet" href="../../jqwidgets/styles/jqx.base.css" type="text/css" />
        <script type="text/javascript" src="../../scripts/jquery-1.10.2.min.js"></script>
        <script type="text/javascript" src="../../scripts/json2.js"></script>
        <script type="text/javascript" src="../../scripts/knockout-3.0.0.js"></script>
        <script type="text/javascript" src="../../jqwidgets/jqxcore.js"></script>
        <script type="text/javascript" src="../../jqwidgets/jqxbuttons.js"></script>
        <script type="text/javascript" src="../../scripts/demos.js"></script>
        <script type="text/javascript" src="../../jqwidgets/jqxscrollbar.js"></script>
        <script type="text/javascript" src="../../jqwidgets/jqxlistbox.js"></script>
        <script type="text/javascript" src="../../jqwidgets/jqxdata.js"></script>
        <script type="text/javascript" src="../../jqwidgets/jqxcheckbox.js"></script>
        <script type="text/javascript" src="../../jqwidgets/jqxknockout.js"></script>
        <script type="text/javascript">
            $(document).ready(function () {
                var getData = function () {
                    var initialData = [
                        { name: "Well-Travelled Kitten", sales: 352, price: 75.95 },
                        { name: "Speedy Coyote", sales: 89, price: 190.00 },
                        { name: "Furious Lizard", sales: 152, price: 25.00 },
                        { name: "Indifferent Monkey", sales: 1, price: 99.95 },
                        { name: "Brooding Dragon", sales: 0, price: 6350 },
                        { name: "Ingenious Tadpole", sales: 39450, price: 0.35 },
                        { name: "Optimistic Snail", sales: 420, price: 1.50 }
                    ];
                    return initialData;
                }
                var viewModel = function (items) {
                    this.items = ko.observableArray(items);
                    this.index = ko.observable(0);
    
                    this.addItem = function () {
                        // add a new item.
                        var data = getData();
                        this.items.push({ name: data[Math.floor(Math.random() * data.length)].name, sales: Math.round(Math.random() * 100), price: Math.round(Math.random() * 100) });
                        $("#list").jqxListBox({ source: dataAdapter.records });
                    };
    
                    this.removeItem = function () {
                        // remove the last item.
                        this.items.pop();
                        $("#list").jqxListBox({ source: dataAdapter.records });
                    };
    
                    this.updateItem = function () {
                        // update the selected item.
                        var item = {};
                        var oldItem = this.items()[this.index()];
                        if (oldItem) {
                            var data = getData();
                            item.name = data[Math.floor(Math.random() * data.length)].name;
                            item.sales = Math.floor(Math.random() * 500);
                            item.price = Math.floor(Math.random() * 200);
                            this.items.replace(oldItem, item);
                            $("#list").jqxListBox({ source: dataAdapter.records });
                        }
                    };
                };
    
                var model = new viewModel(getData());
    
                var source = {
                    localdata: model.items,
                    datatype: 'observablearray'
                }
    
                var dataAdapter = new $.jqx.dataAdapter(source);
                dataAdapter.dataBind();
                // create widgets.
                $("#list").jqxListBox({ source: dataAdapter.records, height: 350, width: 300, itemHeight: 50, displayMember: 'name', valueMember: 'sales',
                    renderer: function (index, label, value) {
                        var table = '<table style="min-width: 150px;"><tr><td>' + label + '</td></tr><tr><td>Sales: $' + value + '</td></tr></table>';
                        return table;
                    }
                });
                $("#addButton").jqxButton();
                $("#removeButton").jqxButton();
                $("#updateButton").jqxButton();
    
                ko.applyBindings(model);
            });
        </script>
    </head>
    <body class='default'>
        <div data-bind="jqxListBox: {selectedIndex: index}" id="list">
        </div>
        <div style="margin-top: 10px;">
            <input id="addButton" type="button" data-bind="click: addItem" value="Add Item" />
            <input id="removeButton" type="button" data-bind="click: removeItem" value="Remove Item" />
            <input id="updateButton" type="button" data-bind="click: updateItem" value="Update Item" />
        </div>
    </body>
    </html>

    Best Regards,
    Dimitar

    jQWidgets team
    http://www.jqwidgets.com/

Viewing 8 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic.