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

C# and .NET Tips and Tricks

Quests in programming in .NET

Δεκέμβριος 2013 - Δημοσιεύσεις

ITProDevConnections 2013 Presentation and Material

Thank you all for coming.

The presentation (in greek) about the common/different parts of Web and Windows 8.1 Applications is available through the following:

 From Web development ro Windows 8 development and Vice-Versa(in greek).

Make sure you also check the following blog posts (in English) related to the presentation:

Data binding in Windows 8.1 Apps with Knockout

Data binding in Windows 8.1 Apps with WinJS

Windows 8 Apps with HTML and Javascript successful workflow

WinJS ListView - The most important features and how to use them

Implementing a JSON REST service with ASP.NET MVC

An introduction to creating scalable Single Page Applications with Knockoutjs and SASS

(Windows8) WinJS single page navigation and ViewModels

(Windows8) WinJS Basic Javascript Objects and the ViewModel pattern

See you all next year!!

 

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

Posted: Σάββατο, 7 Δεκεμβρίου 2013 3:32 μμ από iwannis | 2 σχόλια
Δημοσίευση στην κατηγορία: , ,
Data binding in Windows 8.1 Apps with WinJS

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 WinJS (see the one for knockout).

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 WinJS.Binding.List object

// this.listOfValues = [{ text: '-' }, ...];
this.listOfValues = new WinJS.Binding.List([{ text: '-' }, ...]);

Binding is: <div class="listView" data-win-control="..." data-win-bind="winControl.itemDataSource:listOfValues.dataSource">

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 WinJS.Binding.as

// this.listOfValues = [{ text: '-' }, ...];
// this.listOfValues = new WinJS.Binding.List([{ text: '-' }, ...]);
this.listOfValues = new WinJS.Binding.List([WinJS.Binding.as({ text: '-' }),...]);

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

Requirement: I need to bind the buttonInvoked method to the click event of the button.
Solution: Wrap the function in WinJS.Utilities.markSupportedForProcessing and change the context to this (the same applies to any function that you need to bind such as the itemInvoked method to the winControl.oniteminvoked event of the listview):

//this.buttonInvoked = function () {
	// Your code here
//};
this.buttonInvoked = WinJS.Utilities.markSupportedForProcessing((function () {
	// Your code here
}).bind(this));

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

Requirement: Whenever the value of the input element changes we want the data bound value to change (TwoWay binding support)
Solution: Create a generic binding initializer and use it.

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;
        }
    })
});

Binding is: <input type="text" data-win-bind="value:inputValue Binding.Mode.TwoWay">

Requirement: When a value is displayed we need to add some extra characters in the end (for example a € symbol in prices)
Solution: You cannot use expression directly in data-win-bind and therefore you have to declare a binding converter and use it

WinJS.Namespace.define("Converters", {
    demoConverter: WinJS.Binding.converter(function (value) {
        return value + " €";
    })
});

Binding is: <div data-win-bind="innerHTML:textValue Converters.demoConverter">

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 WinJS.Binding.as
Solution: You need change your field in the object to property first (say you want to do this for the textValue field)

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

Then define property that has getters/setters and is enumerable and use the notify method in the setter. The notify method is provided by a mixin - see below

Object.defineProperty(VM.prototype, "textValue", {
    get: function () {
        return this._textValue;
    },
    set: function (value) {
        this._textValue = value;
        this.notify("textValue", value);  
    }, enumerable: true
});

You will not use the VM class to create your object but the VMObservable class which will be returned from the following mixin.

var VMObservable = WinJS.Class.mix(VM, WinJS.Binding.mixin);

Requirement: Apply the ViewModel to a given element and its children
Solution: Execute the WinJS.Binding.processAll() after the mixin.

WinJS.UI.Pages.define("/pages/MVVMWinJS/MVVMWinJS.html", {
    ready: function (element, options) {
        var VMObservable = WinJS.Class.mix(VM, WinJS.Binding.mixin); 
        WinJS.Binding.processAll(element.querySelector(".dataBindingRoot"), new VMObservable());
    },
    ...

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 = new WinJS.Binding.List([WinJS.Binding.as({ text: '-' }),...)]);
    this._textValue = "-";
    this._inputValue = "-";
    this.buttonInvoked = WinJS.Utilities.markSupportedForProcessing((function () {
        for (var i = 0; i < this.listOfValues.length;i++)
            this.listOfValues.getAt(i).text = (Math.random()*100).toFixed(2);

        this.textValue = (Math.random() * 100).toFixed(2);
    }).bind(this)); // markSupportedForProcessing required for "safe" binding

    this.itemInvoked = WinJS.Utilities.markSupportedForProcessing((function (ev) {
        var item =this.listOfValues.getAt(ev.detail.itemIndex);
        var md = new Windows.UI.Popups.MessageDialog(item.text);
        md.showAsync();
    }).bind(this)); // markSupportedForProcessing required for "safe" binding
};
Object.defineProperty(VM.prototype, "textValue", {
    get: function () {
        return this._textValue;
    },
    set: function (value) {
        this._textValue = value;
        this.notify("textValue", value);    
    }, enumerable: true
});
Object.defineProperty(VM.prototype, "inputValue", {
    get: function () {
        return this._inputValue;
    },
    set: function (value) {
        this._inputValue = value;
        this.notify("inputValue", value); 
    }, enumerable: true
});

WinJS.UI.Pages.define("/pages/MVVMWinJS/MVVMWinJS.html", {
    ready: function (element, options) {
        var BindableVM = WinJS.Class.mix(VM, WinJS.Binding.mixin);
        WinJS.Binding.processAll(element.querySelector(".dataBinding"), new BindableVM());
    },
    ...
});

Wow. From 14 lines of code we went to 52 lines. If you want to see how knockout does it go to this post

Posted: Κυριακή, 1 Δεκεμβρίου 2013 3:30 μμ από iwannis | 0 σχόλια
Δημοσίευση στην κατηγορία: , ,