locked
proper way to update properties in WinJS.Binding object?

    Question

  • I have a template in my html file, similar to this. (In my actual code, it's a div with many bound properties instead of just one.)

    <div id="someTemplate" data-win-control="WinJS.Binding.Template">
        <h2 data-win-bind="textContent: someProperty"></h2>
    </div>

    I have a WinJS.Binding object. (In my actual code, I have several 'new Data()' objects.)

    var Data = WinJS.Binding.define({
        someProperty: ''
        /* some other properties also */
    });
    
    var myData = new Data({
        someProperty: 3
    });

    I bind and render my object to an instantiated template, and add it to the DOM. (In my actual code, I have many of these templates added to the DOM each with a different Data object.)

    var renderHere = document.createElement('div');
    
    var someTemplate = document.getElementById('someTemplate');
    
    WinJS.UI.process(someTemplate).then(function (st) {
        st.render(myData, renderHere);
    });
    
    /* add renderHere to document */
    /* ... */

    In my code, I change the properties of my binded objects repeatedly, sometimes very quickly (the timing varies).

    myData.someProperty = 5;

    My problem is that sometimes, the templates seem to get unbound from their data source. For instance, in the above code, I would have many (say somewhere between 10-15) h2 tags each bound to a different Data object, and sometimes 1 or 2 will stop updating their innerText, and be stuck. When I see this happens, I have a debug button that changes the myData.someProperty to some known value, which I verified actually changed the property value. However, the DOM is not updated. In the DOM Explorer, I see the stuck value, even though in the code, the binded object shows the correct value.

    What am I doing wrong?


    PS: each of the templates instantiated is added to a separate page of a FlipView, if that matters.

    • Edited by poker421 Wednesday, May 16, 2012 1:41 AM
    Wednesday, May 16, 2012 1:37 AM

Answers

  • The problem with in this repro is that:

    - WinJS finds data bound elements by id.

    -The flipView internally maintains a 5-page buffer, and it re-uses the page containers and releases what is not using.
    - After they leave the DOM because you flip several pages away, and the flipView shows new content, WinJS can't find them anymore with that id.
    -Once WinJS can't find them anymore, it cleans up the binding. This happens the next time a binding occurs which looks for the element
    -However this means that if you pre-render all your elements and are changing them while they are not in the DOM (e.g. they aren't in the 5-element flipper window) the bindings get cleaned up and once the bindings are cleaned up things stop changing,  and the button stops working because the repro uses data binding to propagate the action.

    This is by design to be able to scale to very large data sets. Our recommended pattern is as follows: 

    // default.js:// For an introduction to the Blank template, see the following documentation:

    // http://go.microsoft.com/fwlink/?LinkId=232509

    (function () {

        "use strict";

        var app = WinJS.Application;

        app.onactivated = function (eventObject) {

            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {

                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {

                    // TODO: This application has been newly launched. Initialize

                    // your application here.

                } else {

                    // TODO: This application has been reactivated from suspension.

                    // Restore application state here.

                }

                WinJS.UI.processAll();

            }

        };

        app.oncheckpoint = function (eventObject) {

            // TODO: This application is about to be suspended. Save any state

            // that needs to persist across suspensions here. You might use the

            // WinJS.Application.sessionState object, which is automatically

            // saved and restored across suspension. If you need to complete an

            // asynchronous operation before your application is suspended, call

            // eventObject.setPromise().

        };

        app.start();

        var myDatas;

        var Data = WinJS.Binding.define({

            someProperty: ''

        });

        function initialize() {

            WinJS.UI.processAll().then(function () {

                var flipView = document.getElementById('myFlipView').winControl;

                myDatas = new WinJS.Binding.List();

                flipView.itemDataSource = myDatas.dataSource;

                flipView.itemTemplate = document.getElementById('someTemplate');

                for (var i = 0; i < 10; i++) {

                    addPage();

                }

                setInterval(changeRandom, 100);

            });

        }

        function changeRandom() {

            var index = Math.floor(Math.random() * myDatas.length);

            var value = Math.floor(Math.random() * 100);

            myDatas.getAt(index).someProperty = value;

        }

        function addPage() {

            myDatas.push(new Data({ someProperty: 3 }));

        }

        document.addEventListener('DOMContentLoaded', initialize);

       

    })();

    Default.html:

    <!DOCTYPE html>

    <html>

    <head>

        <meta charset="utf-8" />

        <title>App2</title>

        <!-- WinJS references -->

        <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet" />

        <script src="//Microsoft.WinJS. 0.6/js/base.js"></script>

        <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>

        <!-- App references -->

        <link href="/css/default.css" rel="stylesheet" />

        <script src="/js/default.js"></script>

    </head>

    <body>

        <p>Content goes here</p>

        <div id="myFlipView" data-win-control="WinJS.UI.FlipView" style="width500px; height500px; background-color: red;"></div>

        <div id="someTemplate" data-win-control="WinJS.Binding.Template">

            <h2 data-win-bind="textContent: someProperty"></h2>

            <button onclick="myData.someProperty = 'changed'" data-win-bind="myData: this">change</button>

        </div>

    </body>

    </html>

    -Jeff


    Jeff Sanders (MSFT)

    Thursday, May 17, 2012 6:13 PM
    Moderator
  • You can define a function for the flipview template and do whatever you need:

    // default.js:// For an introduction to the Blank template, see the following documentation:

    // http://go.microsoft.com/fwlink/?LinkId=232509

    (function () {

        "use strict";

        var app = WinJS.Application;

        app.onactivated = function (eventObject) {

            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {

                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {

                    // TODO: This application has been newly launched. Initialize

                    // your application here.

                } else {

                    // TODO: This application has been reactivated from suspension.

                    // Restore application state here.

                }

                WinJS.UI.processAll();

            }

        };

        app.oncheckpoint = function (eventObject) {

            // TODO: This application is about to be suspended. Save any state

            // that needs to persist across suspensions here. You might use the

            // WinJS.Application.sessionState object, which is automatically

            // saved and restored across suspension. If you need to complete an

            // asynchronous operation before your application is suspended, call

            // eventObject.setPromise().

        };

        app.start();

        var myDatas;

        var Data = WinJS.Binding.define({

            someProperty: ''

        });

        function initialize() {

            WinJS.UI.processAll().then(function () {

                var flipView = document.getElementById('myFlipView').winControl;

                myDatas = new WinJS.Binding.List();

                flipView.itemDataSource = myDatas.dataSource;

                flipView.itemTemplate = fvTemplate;

                for (var i = 0; i < 10; i++) {

                    addPage();

                }

                setInterval(changeRandom, 100);

            });

        }

        function fvTemplate(itemPromise) {

            return itemPromise.then(function (currentItem) {

                // You can pick whatever template you want here

                var someTemplate = document.getElementById('someTemplate');

                return WinJS.UI.process(someTemplate).then(function (st) {

                    var element = document.createElement('div');

                    return st.render(currentItem.data, element).then(function () {

                        return element;

                    });

                });

            });

        }

        function changeRandom() {

            var index = Math.floor(Math.random() * myDatas.length);

            var value = Math.floor(Math.random() * 100);

            myDatas.getAt(index).someProperty = value;

        }

        function addPage() {

            myDatas.push(new Data({ someProperty: 3 }));

        }

        document.addEventListener('DOMContentLoaded', initialize);

       

    })();

    Default.html:

    <!DOCTYPE html>

    <html>

    <head>

        <meta charset="utf-8" />

        <title>App2</title>

        <!-- WinJS references -->

        <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet" />

        <script src="//Microsoft.WinJS.0.6/js/base.js"></script>

        <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>

        <!-- App2 references -->

        <link href="/css/default.css" rel="stylesheet" />

        <script src="/js/default.js"></script>

    </head>

    <body>

        <p>Content goes here</p>

        <div id="myFlipView" data-win-control="WinJS.UI.FlipView" style="width500px; height500px; background-color: red;"></div>

        <div id="someTemplate" data-win-control="WinJS.Binding.Template">

            <h2 data-win-bind="textContent: someProperty"></h2><br />

            <button onclick="myData.someProperty = 'changed'" data-win-bind="myData: this">change</button>

        </div>

    </body>

    </html>

    -Jeff


    Jeff Sanders (MSFT)

    Friday, May 18, 2012 12:34 PM
    Moderator
  • Thanks, I think I figured out the issue. It turned out to be a problem with the FlipView control. Not sure if I used the control improperly or it is that way by design, but what was happening was I would add pages to the FlipView, but the pages that were further away from the selected page would be (perhaps?) going out of memory, and when the binding data object was updated and tried to update the pages, they wouldn't be able to. While debugging, the property in the _listeners property of the binding data that had tried to update would be changed to 'undefined', making any further changes to that property impossible, resulting in the stuck value.

    I suppose I can only update the data when I know for sure that that page in the FlipView is selected, and wait to do all the updates only when the page gets selected.

    For now, I got rid of the FlipView and everything works as it's supposed to. I will try later to get it to work with a FlipView but it's not very important right now.

    Wednesday, May 16, 2012 8:05 PM

All replies

  • Hi Poker,

    No idea!  Can you put together a repro the shows this problem for me?

    -Jeff


    Jeff Sanders (MSFT)

    Wednesday, May 16, 2012 12:57 PM
    Moderator
  • Thanks, I think I figured out the issue. It turned out to be a problem with the FlipView control. Not sure if I used the control improperly or it is that way by design, but what was happening was I would add pages to the FlipView, but the pages that were further away from the selected page would be (perhaps?) going out of memory, and when the binding data object was updated and tried to update the pages, they wouldn't be able to. While debugging, the property in the _listeners property of the binding data that had tried to update would be changed to 'undefined', making any further changes to that property impossible, resulting in the stuck value.

    I suppose I can only update the data when I know for sure that that page in the FlipView is selected, and wait to do all the updates only when the page gets selected.

    For now, I got rid of the FlipView and everything works as it's supposed to. I will try later to get it to work with a FlipView but it's not very important right now.

    Wednesday, May 16, 2012 8:05 PM
  • // default.js:

    // For an introduction to the Blank template, see the following documentation: // http://go.microsoft.com/fwlink/?LinkId=232509 (function () { "use strict"; var app = WinJS.Application; app.onactivated = function (eventObject) { if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) { if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) { // TODO: This application has been newly launched. Initialize // your application here. } else { // TODO: This application has been reactivated from suspension. // Restore application state here. } WinJS.UI.processAll(); } }; app.oncheckpoint = function (eventObject) { // TODO: This application is about to be suspended. Save any state // that needs to persist across suspensions here. You might use the // WinJS.Application.sessionState object, which is automatically // saved and restored across suspension. If you need to complete an // asynchronous operation before your application is suspended, call // eventObject.setPromise(). }; app.start(); /* bug demo */ var myPagesList = []; var Data = WinJS.Binding.define({ someProperty: '' }); var myDatas = []; function initialize() { WinJS.UI.processAll().then(function () { var flipView = document.getElementById('myFlipView').winControl; myPagesList = new WinJS.Binding.List(); flipView.itemDataSource = myPagesList.dataSource; flipView.itemTemplate = fvTemplate; for (var i = 0; i < 10; i++) { addPage(); } setInterval(changeRandom, 100); }); } function changeRandom() { var index = Math.floor(Math.random() * myDatas.length); var value = Math.floor(Math.random() * 100); myDatas[index].someProperty = value; } function fvTemplate(itemPromise) { return itemPromise.then(function (currentItem) { return renderFlipViewSlide(currentItem.data); }); } function renderFlipViewSlide(dataObject) { return dataObject.element; } function addPage() { var renderHere = document.createElement('div'); var someTemplate = document.getElementById('someTemplate'); var myData = new Data({ someProperty: 3 }); WinJS.UI.process(someTemplate).then(function (st) { st.render(myData, renderHere); }); myDatas.push(myData); var debugButton = document.createElement('button'); debugButton.innerText = 'change'; debugButton.onclick = function () { myData.someProperty = 'changed'; }; renderHere.appendChild(debugButton); myPagesList.push({ element: renderHere }); } document.addEventListener('DOMContentLoaded', initialize); })();

    default.html:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>BugDemo</title>
    
        <!-- WinJS references -->
        <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
        <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
        <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
    
        <!-- BugDemo references -->
        <link href="/css/default.css" rel="stylesheet">
        <script src="/js/default.js"></script>
    </head>
    <body>
        <p>Content goes here</p>
    
        <div id="myFlipView" data-win-control="WinJS.UI.FlipView" style="width:  500px; height:  500px; background-color: red;"></div>
    
        <div id="someTemplate" data-win-control="WinJS.Binding.Template">
            <h2 data-win-bind="textContent: someProperty"></h2>
        </div>
    
    </body>
    </html>
    

    The above is just a blank application with the changed default.html and default.js that shows my problem.

    Wednesday, May 16, 2012 8:38 PM
  • Thanks!

    Jeff Sanders (MSFT)

    Thursday, May 17, 2012 1:06 PM
    Moderator
  • The problem with in this repro is that:

    - WinJS finds data bound elements by id.

    -The flipView internally maintains a 5-page buffer, and it re-uses the page containers and releases what is not using.
    - After they leave the DOM because you flip several pages away, and the flipView shows new content, WinJS can't find them anymore with that id.
    -Once WinJS can't find them anymore, it cleans up the binding. This happens the next time a binding occurs which looks for the element
    -However this means that if you pre-render all your elements and are changing them while they are not in the DOM (e.g. they aren't in the 5-element flipper window) the bindings get cleaned up and once the bindings are cleaned up things stop changing,  and the button stops working because the repro uses data binding to propagate the action.

    This is by design to be able to scale to very large data sets. Our recommended pattern is as follows: 

    // default.js:// For an introduction to the Blank template, see the following documentation:

    // http://go.microsoft.com/fwlink/?LinkId=232509

    (function () {

        "use strict";

        var app = WinJS.Application;

        app.onactivated = function (eventObject) {

            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {

                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {

                    // TODO: This application has been newly launched. Initialize

                    // your application here.

                } else {

                    // TODO: This application has been reactivated from suspension.

                    // Restore application state here.

                }

                WinJS.UI.processAll();

            }

        };

        app.oncheckpoint = function (eventObject) {

            // TODO: This application is about to be suspended. Save any state

            // that needs to persist across suspensions here. You might use the

            // WinJS.Application.sessionState object, which is automatically

            // saved and restored across suspension. If you need to complete an

            // asynchronous operation before your application is suspended, call

            // eventObject.setPromise().

        };

        app.start();

        var myDatas;

        var Data = WinJS.Binding.define({

            someProperty: ''

        });

        function initialize() {

            WinJS.UI.processAll().then(function () {

                var flipView = document.getElementById('myFlipView').winControl;

                myDatas = new WinJS.Binding.List();

                flipView.itemDataSource = myDatas.dataSource;

                flipView.itemTemplate = document.getElementById('someTemplate');

                for (var i = 0; i < 10; i++) {

                    addPage();

                }

                setInterval(changeRandom, 100);

            });

        }

        function changeRandom() {

            var index = Math.floor(Math.random() * myDatas.length);

            var value = Math.floor(Math.random() * 100);

            myDatas.getAt(index).someProperty = value;

        }

        function addPage() {

            myDatas.push(new Data({ someProperty: 3 }));

        }

        document.addEventListener('DOMContentLoaded', initialize);

       

    })();

    Default.html:

    <!DOCTYPE html>

    <html>

    <head>

        <meta charset="utf-8" />

        <title>App2</title>

        <!-- WinJS references -->

        <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet" />

        <script src="//Microsoft.WinJS. 0.6/js/base.js"></script>

        <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>

        <!-- App references -->

        <link href="/css/default.css" rel="stylesheet" />

        <script src="/js/default.js"></script>

    </head>

    <body>

        <p>Content goes here</p>

        <div id="myFlipView" data-win-control="WinJS.UI.FlipView" style="width500px; height500px; background-color: red;"></div>

        <div id="someTemplate" data-win-control="WinJS.Binding.Template">

            <h2 data-win-bind="textContent: someProperty"></h2>

            <button onclick="myData.someProperty = 'changed'" data-win-bind="myData: this">change</button>

        </div>

    </body>

    </html>

    -Jeff


    Jeff Sanders (MSFT)

    Thursday, May 17, 2012 6:13 PM
    Moderator
  • Thank you for the quick reply. Your code works 100% for the example I gave, but how can I specify different itemTemplates for the FlipView? I am using the FlipView as a page navigation for my app, so not all the pages are created from the same template. Some pages are the same 'type', created from the same template, but others are not from a template, but pure html, or from another template.

    I could make a base template for all pages, but I don't know how I would render another template into that template.

    Thursday, May 17, 2012 6:52 PM
  • You can define a function for the flipview template and do whatever you need:

    // default.js:// For an introduction to the Blank template, see the following documentation:

    // http://go.microsoft.com/fwlink/?LinkId=232509

    (function () {

        "use strict";

        var app = WinJS.Application;

        app.onactivated = function (eventObject) {

            if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {

                if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {

                    // TODO: This application has been newly launched. Initialize

                    // your application here.

                } else {

                    // TODO: This application has been reactivated from suspension.

                    // Restore application state here.

                }

                WinJS.UI.processAll();

            }

        };

        app.oncheckpoint = function (eventObject) {

            // TODO: This application is about to be suspended. Save any state

            // that needs to persist across suspensions here. You might use the

            // WinJS.Application.sessionState object, which is automatically

            // saved and restored across suspension. If you need to complete an

            // asynchronous operation before your application is suspended, call

            // eventObject.setPromise().

        };

        app.start();

        var myDatas;

        var Data = WinJS.Binding.define({

            someProperty: ''

        });

        function initialize() {

            WinJS.UI.processAll().then(function () {

                var flipView = document.getElementById('myFlipView').winControl;

                myDatas = new WinJS.Binding.List();

                flipView.itemDataSource = myDatas.dataSource;

                flipView.itemTemplate = fvTemplate;

                for (var i = 0; i < 10; i++) {

                    addPage();

                }

                setInterval(changeRandom, 100);

            });

        }

        function fvTemplate(itemPromise) {

            return itemPromise.then(function (currentItem) {

                // You can pick whatever template you want here

                var someTemplate = document.getElementById('someTemplate');

                return WinJS.UI.process(someTemplate).then(function (st) {

                    var element = document.createElement('div');

                    return st.render(currentItem.data, element).then(function () {

                        return element;

                    });

                });

            });

        }

        function changeRandom() {

            var index = Math.floor(Math.random() * myDatas.length);

            var value = Math.floor(Math.random() * 100);

            myDatas.getAt(index).someProperty = value;

        }

        function addPage() {

            myDatas.push(new Data({ someProperty: 3 }));

        }

        document.addEventListener('DOMContentLoaded', initialize);

       

    })();

    Default.html:

    <!DOCTYPE html>

    <html>

    <head>

        <meta charset="utf-8" />

        <title>App2</title>

        <!-- WinJS references -->

        <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet" />

        <script src="//Microsoft.WinJS.0.6/js/base.js"></script>

        <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>

        <!-- App2 references -->

        <link href="/css/default.css" rel="stylesheet" />

        <script src="/js/default.js"></script>

    </head>

    <body>

        <p>Content goes here</p>

        <div id="myFlipView" data-win-control="WinJS.UI.FlipView" style="width500px; height500px; background-color: red;"></div>

        <div id="someTemplate" data-win-control="WinJS.Binding.Template">

            <h2 data-win-bind="textContent: someProperty"></h2><br />

            <button onclick="myData.someProperty = 'changed'" data-win-bind="myData: this">change</button>

        </div>

    </body>

    </html>

    -Jeff


    Jeff Sanders (MSFT)

    Friday, May 18, 2012 12:34 PM
    Moderator
  • Great! This is exactly what I needed. Thank you very much for all the help you gave me.
    Friday, May 18, 2012 3:12 PM
  • Can you help me http://social.msdn.microsoft.com/Forums/en-US/winappswithhtml5/thread/8960f906-25cd-4e5a-97de-1f0ba544bde3

    Thanks.

    Thursday, April 18, 2013 3:11 AM