6
votes

I'm working on Android with Jquery Mobile with PhoneGap using Knockout for loading data.

I'm getting the data all right and loading it on the HTML page accordingly to the for each data-bind I have on the tags.

When I want to refresh the data, it just doesn't do it. It returns just an HTML without bound data or throwing dom exception not found.

My applyBinding happens inside pagecreate event of the page.

I've posted a simple example describing the problem on my SkyDrive - http://sdrv.ms/LpUdLt It's a public example reproducing the problem. the viewmodel holds an array that holds array. refreshed with randomal values. trying to reload the page in jquery mobile with changepage reload with new data by pressing the navbar button fails with the dom object mistake. I do agree not to that I shouldn't create an instance of VM every page create, just can't find a way to implement it, so that the data will be rerendered on HTML.

   //indexPage.js
   var wAViewModelInst ;
   var viewPageIndexContent;
   $('#pageIndex').live('pagecreate', function (event) { 
        viewPageIndexContent = document.getElementById("pageIndexContent");
        wAViewModelInst = new WAViewModel(true);
        ko.applyBindings(wAViewModelInst, viewPageIndexContent);

        waHeaderVM.refreshContentData = function () {

              // wAViewModelInst.updateRowList();
              // ko.cleanNode(viewPageIndexContent);
              // viewPageIndexContent = document.getElementById("pageIndexContent");
              //wAViewModelInst = new WAViewModel(true);
             //ko.applyBindings(wAViewModelInst, viewPageIndexContent);
              $.mobile.changePage("index.html", { allowSamePageTransition: true, reloadPage: true });
              $.mobile.hidePageLoadingMsg();
       }
   }

    //WAViewModel
    self.WARowList = ko.observableArray();
    self.updateRowList = function () {
        self.WARowList(self.GetWA());
    }
    //tried the exteding 
    //ko.observableArray.fn.WARowListUpdate = function () {
    //    //create a sub-observable
    //    this.hasItems = ko.observable();

    //    //update it when the observableArray is updated
    //    this.subscribe(function (newValue) {
    //        this.hasItems(newValue && newValue.length ? true : false);
    //    }, this);

    //    //trigger change to initialize the value
    //    this.valueHasMutated();

    //    //support chaining by returning the array
    //    return this;
    //};            

is there any way to update the html after the first rendering ?

adding the html code:

<div id="pageIndex" data-role="page" data-transition="flip" data-theme="e" data-dom-cache="true">
    <div id="indexHeader" data-role="header" data-theme="e">
        <div data-role="navbar" data-iconpos="right">
            <ul>
                <li><a href="login.html" data-role="tab" data-icon="back" data-bind="click: loadingHandler"
                    class="brighter-text">חזרה</a></li>
                <li><a href="#" data-role="tab" data-icon="refresh" data-bind ="click: refreshContentData" >רענן</a></li>
                <li><a href="researchEvent.html" data-role="tab" data-icon="check" id="navBarResearchEventPage"   data-bind="click: loadResearchEvent" class="brighter-text">ביצוע חקר</a></li>
                <li><a href="#" data-role="tab" data-icon="grid" class="ui-btn-active brighter-text">
                    סידור עבודה</a></li>
            </ul>
        </div>
    </div>
    <div id="pageIndexContent" data-role="content" data-theme="e" style="padding-bottom: 52px;
        height: 570px;">
        <h2 data-bind="text:Title" class="brighter-text" style="font-size:22pt;">
        </h2>
        <div data-bind="foreach: WARowList" style="width: 99%; text-align: center">
            <div>
                <table style="float: right; width: 20%; height: 60px;" cellpadding="0" cellspacing="0">
                    <tr data-bind="visible : FirstRow " style="height: 31px;">
                        <th class="AlignedHeader">
                            <label>
                                שעה / שילוט</label>
                        </th>
                    </tr>
                    <tr data-bind="style: { backgroundColor: Odd() ? '#8CC63F' : '#AFC493' }">
                        <td style="width: 20%;" data-bind="style: { backgroundColor: IsNew() ? 'yellow' : 'transparent' }">
                            <input  type="button" data-bind="click: ShowSampleDetails, value: ShilutTime , jQueryButtonUIEnableDisable:$data"
                                data-icon="plus" data-iconpos="right"/>
                        </td>
                    </tr>
                </table>
                <table style="height: 60px; width: 80%; background-color: #8CC63F;" data-bind="style: { backgroundColor: Odd() ? '#8CC63F' : '#AFC493' }"
                    cellpadding="0" cellspacing="0">
                    <thead data-bind="if : FirstRow">
                        <tr data-bind="foreach: CellList">
                            <th class="AlignedHeader">
                                <label data-bind="text: Date">
                                </label>
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr data-bind="foreach: CellList">
                            <td style="width: 11.5%;">
                                <div data-bind="visible:IsPopulated ">
                                    <div data-bind="visible: HasDrivers">
                                        <input type="button" data-role="button" data-bind="click: ShowBusDriverList.bind($data , $root) , jQueryButtonUIEnableDisable: $data "
                                            data-icon="search" data-iconpos="right" value="נהגים" class="brighter-text" />
                                    </div>
                                    <div data-bind="visible: !HasDrivers()">
                                        <input type="button" data-role="button" id="btnNoDriver" disabled="disabled" data-icon="info"
                                            data-iconpos="right" value="אין נהג" class="brighter-text" />
                                    </div>
                                </div>
                                <div data-bind="visible: !IsPopulated">
                                </div>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>

and so on ..

the GetWA function returns an observableArray of warow list . it works the first time the trouble is rerendering the dom object. the dom element is contaminated with ko and fails ..

I tried the answer of Luffy :

var lVM = new loginViewModel();
var  footerViewModelLogin = {
        IsOnline: ko.observable(globalContext.Network()),
        IsSync: ko.observable(globalContext.Sync())
    };
$('#login').live('pagecreate', function (event) {


     viewLoginContent = document.getElementById("loginContent");

    ko.applyBindingsToNode(viewLoginContent, lVM);


    viewLoginFooter= document.getElementById("footerLogin");

    ko.applyBindingsToNode(viewLoginFooter, footerViewModelLogin);


});


$('#login').live('pagehide', function (event, ui) {
    $.mobile.hidePageLoadingMsg();

});



function loginViewModel() {
    var self = this;

    try {

        self.userName = ko.observable("");
        self.password = ko.observable("");
        self.message = ko.observable("");

        self.CleanGlobalContext = function () {
          ...

        };

        self.Validate = function () {

            ...

        };
    }
    catch (e) {
        if (IsDebug) alert("GlobalContext.prototype.SetMapOverlay  " + e.message);
        if (typeof (console) != 'undefined' && console) console.log("GlobalContext.prototype.SetMapOverlay "     + e.message);
    }
}

ko.applyBindings(lVM);
ko.applyBindings(footerViewModelLogin);

The knockout fails without the element predefined event to bind .

3
Can you show us a little more code, such as the HTML, or the GetWA method? When you say 'it returns just empty HTML', what is being called that returns the empty HTML? - CodeThug
the question is there a way to reapply the bindings ? the reinitialize of the observabeArray in the viewModel doesn't reorganize and repaint the view. - Tzvi Gregory Kaidanov
i've corrected the post - it doesn't return emty html - it returns an html without the data-bind information of the refreshed viewmodel. - Tzvi Gregory Kaidanov
I've added a simple reproducing problem example . Please advise .. - Tzvi Gregory Kaidanov
I found your example solution a little hard to follow. In your code above I'm confused by the lines in the commented out section where you recreate a new WAViewModel and reapply bindings. If GetWA() returns JSON data for new rows, don't you want to just update the observable array with this data? Why recreate the parent WAViewModel? Or am I misunderstanding the view model structure? - Tom W Hall

3 Answers

2
votes

I have not understood well you difficulty. However, I don't understand why you need to reapply bindings. Notice what follows:

  1. If you change an observable array the needed templates are re-instantiated to updated coherently the UI. However you have to update the observalble array either by using the observable array functions, or by passing to the observable a new array: obs(newArray).
  2. If you need to perform actions after the ko re-rendering you just need to use the afterRender, or afterAdd parameters of the template binding
  3. if you add new jquery mobile code you have to parse it after having created it otherwise jquery mobile will not work...again you can do this in the afterRender function.
  4. it is problematic to load html containing ajax ko bindings via ajax. The right approach is to create that content as a template, that you render immediately (not with ajax). Then you query the server just to get new json content. Then you can instantiate the template by using for instance the with binding.
0
votes

Yes you can reapply bindings with ko.applyBindingsToNode function to the specific dom element.

Check these examples

  1. http://jsfiddle.net/rniemeyer/gYk6f/ ---> With templating
  2. http://jsfiddle.net/rniemeyer/BnDh6/ ---> With data - value pair
0
votes

The solution was to add afterRender on the foreach table data-bind. as – Francesco Abbruzzese suggested. The ajax request has been turned to async:false though. Each time I update the list I perform some dat-bind = "click:updateRowList" which updates the table. Thanks a lot to Elad Katz for help with bounty .