Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

C# and .NET Tips and Tricks

Quests in programming in .NET
Data binding in Windows 8.1 Apps with Knockout

During the implementation of a Windows 8 application and especially in the process of implementing the functionality of a single Page Control, one is faced with the question on whether to use the WinJS data-binding engine for his ViewModel or resort to a "web" solution such as Knockoutjs. This post is about data-binding using Knockoutjs (see the one for WinJS).

The page control (our view) will be as simple as possible to illustrate the most important aspects of each approach. The corresponding ViewModel to support the view in its "clean" form (without any structures added to support the binding requirements) is like that:

function VMClean() {
        this.listOfValues = [{ text: '-' }, ...];
        this.textValue = "-";
        this.inputValue = "-";
        this.buttonInvoked = function () {
            for (var i = 0; i < this.listOfValues.length; i++)
                this.listOfValuesIdea.text = (Math.random() * 100).toFixed(2);

            this.textValue = (Math.random() * 100).toFixed(2);
        };
        this.itemInvoked = function (item) {
            var md = new Windows.UI.Popups.MessageDialog(item.text);
            md.showAsync();
        }
    };

Below is the image of the view along with pointers of the properties/methods that are bound to specific elements:

Requirement: When I add/remove items from the list I need the change to be reflected in the UI.
Solution: (WinJS) Wrap the list in a ko.observableArray method

// this.listOfValues = [{ text: '-' }, ...];
this.listOfValues = ko.observableArray([{ text: '-' }, ...]);

Binding is: <div class="listView" data-bind="foreach:listOfValues">

Requirement: I need to be able to change the properties of the elements in the list and have the change reflected in the UI.
Solution: Wrap the JSON elements in ko.mapping.fromJS

// this.listOfValues = [{ text: '-' }, ...];
// this.listOfValues = ko.observableArray([{ text: '-' }, ...]);
this.listOfValues = ko.observableArray([ko.mapping.fromJS({ text: '-' }),...]);

Binding is in the listView item template as: <span data-bind="text:text">

Requirement: I need to bind the buttonInvoked method to the click event of the button.
Solution: No changes in the initial ViewModel (see the binding):

this.buttonInvoked = function () {
	// Your code here
};

Binding is: <button data-bind="click:buttonInvoked">

Requirement: Whenever the value of the input element changes we want the data bound value to change (TwoWay binding support)
Solution: No changes in the initial ViewModel (supported out of the box)

Requirement: When a value is displayed we need to add some extra characters in the end (for example a € symbol in prices)
Solution: Expressions are supported in the binding with no extra effort.

Binding is: <div data-bind="html:(textValue()*100).toFixed(2)+' €'">

Requirement: When you change a value in code you need the change to be reflected in the UI in an ordinary object and not in a JSON object through the use of ko.mapping.
Solution: Declare the property as ko.observable

function VM() {
    ...
    //this.textValue="-";
    this._textValue =ko.observable("-");
    ...
};

Requirement: Apply the ViewModel to a given element and its children
Solution: Execute the ko.applyBindings().

WinJS.UI.Pages.define("/pages/MVVMWinJS/MVVMWinJS.html", {
    ready: function (element, options) {
        ko.applyBindings(new VM(), element.querySelector(".dataBinding"));
    },
    ...

The final code of the ViewModel is:

WinJS.Namespace.define("Converters", {
    demoConverter: WinJS.Binding.converter(function (value) {
        return value + " €";
    })
}); // Converter required to manipulate the binding value

WinJS.Namespace.define("Binding.Mode", {
    TwoWay: WinJS.Binding.initializer(function (source, sourceProps, dest, destProps) {
        WinJS.Binding.defaultBind(source, sourceProps, dest, destProps);
        dest.onchange = function () {
            var d = dest[destProps[0]];
            var s = source[sourceProps[0]];
            if (s !== d) source[sourceProps[0]] = d;
        }
    })
}); // Required to enable two way binding

function VM() {
    this.listOfValues = ko.observableArray([ko.mapping.fromJS({ text: '-' }), ...]);
    
    this.textValue = ko.observable("-");
    this.inputValue = ko.observable("-");
    this.buttonInvoked = function () {
        for (var i = 0; i < this.listOfValues().length; i++)
            this.listOfValues()Idea.text(Math.random());

        this.textValue(Math.random());
    };
    this.itemInvoked = function (item) {
        var md = new Windows.UI.Popups.MessageDialog(item.text());
        md.showAsync();
    }
};


WinJS.UI.Pages.define("/pages/MVVMWinJS/MVVMWinJS.html", {
    ready: function (element, options) {
        ko.applyBindings(new VM(), element.querySelector(".dataBinding"));
    },
    ...
});

Wow. From 14 lines of code we went to 16 lines. But do have we lost something?

ListView considerations

The new listView in knockoutjs is created with the foreach binding and although usable does not reflect the UI characteristics of the WinJS.UI.ListView. Therefore we need to find a way to support the ListView and binding in its items with knockoutjs. To achieve that we take advantage of the fact that we can provide the item template to the listview from a function. The trick is to apply the ko.applyBindings command prior to delivering the template.

The change is as follows: Initially the template is defined in HTML as follows: <div class="listView" data-win-control="WinJS.UI.ListView" data-win-options="{ itemTemplate: select('.template')...>. This will be done in code through the function:

WinJS.UI.Pages.define("/pages/MVVMWinJS/MVVMWinJS.html", {
    ready: function (element, options) {
    	element.querySelector(".listView").winControl.itemTemplate = function (itemPromise) {
		    return itemPromise.then(function (item) {
		        var itemTemplate = document.querySelector('.template');
		        var container = document.createElement("div");
		        itemTemplate.winControl.render(item.data, container).then(function () {
		            ko.applyBindings(item.data, container); // THIS LINE DOES THE MAGIC
		        });;
		        return container;
		    });
		};

        ko.applyBindings(new VM(), element.querySelector(".dataBinding"));
    },
    ...

In the case that you want to provide declarative databinding the the ListView's itemDataSource you need to create a new binding attribute as follows and then use is as you do with the other attributes (text:,html:...) - that is itemDataSource:ListOfValues.dataSource :

ko.bindingHandlers.itemDataSource = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {        
    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        element.winControl.itemDataSource = valueAccessor();
    }
};

If you want to see the exact same example with pure WinJS binding click here

Share
Posted: Σάββατο, 7 Δεκεμβρίου 2013 3:32 μμ από το μέλος iwannis
Δημοσίευση στην κατηγορία: , ,

Σχόλια:

کارت شارژ έγραψε:

C# and .NET Tips and Tricks : Data binding in Windows 8.1 Apps with Knockout

# Νοεμβρίου 23, 2014 8:27 μμ

text message έγραψε:

C# and .NET Tips and Tricks : Data binding in Windows 8.1 Apps with Knockout

# Νοεμβρίου 28, 2014 11:33 πμ
Ποιά είναι η άποψή σας για την παραπάνω δημοσίευση;

(απαιτούμενο)

(απαιτούμενο)

(προαιρετικό)

(απαιτούμενο)
ÅéóÜãåôå ôïí êùäéêü:
CAPTCHA Image

Ενημέρωση για Σχόλια

Αν θα θέλατε να λαμβάνετε ένα e-mail όταν γίνονται ανανεώσεις στο περιεχόμενο αυτής της δημοσίευσης, παρακαλούμε γίνετε συνδρομητής εδώ

Παραμείνετε ενήμεροι στα τελευταία σχόλια με την χρήση του αγαπημένου σας RSS Aggregator και συνδρομή στη Τροφοδοσία RSS με σχόλια