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

C# and .NET Tips and Tricks

Quests in programming in .NET

Παρουσίαση με Ετικέτες

Όλες οι Ετικέτε... » WinJS   (RSS)
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 | 0 σχόλια
Δημοσίευση στην κατηγορία: , ,
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 σχόλια
Δημοσίευση στην κατηγορία: , ,
Windows 8 Apps with HTML and Javascript successful workflow

It is claimed a lot that whoever knows how to develop web applications is also ready to develop Windows 8.1 applications. And this is definitely true in terms of the language used. In this post we will explore how we can set our VS2013, Expression blend IDE and use specific frameworks in order to also work as fast as we do when developing Web applications.

First of all lets describe what we want today in our web development workflow:

  • We need a way to write faster HTML by using some shorts of shortcuts that will enable us to free ourselves from all the "HTML" syntactic sugar. This is solved in VS2013 by installing the Web Essentials extension that comes equipped with "Zen Coding". "Zen Coding" will allow you to write ul>li*8, press tab and have it expanded to a fully blow unordered list with 8 list elements.
  • We need a way to keep our CSS stylesheets DRY. The CSS language is not DRY ("Do not Repeat Yourself" principle). You tend to repeat the names of the selectors when you define their full paths, you cannot declare variables or define mathematical expressions in the calculation of the sizes and so on. SASS helps a lot in this process and you can use it in VS2013 by installing the Mindscape Web Workbench plugin.
  • Automatic browser refresh is another important issue solved in VS2013 using the BrowserLink technology.
  • Javascript intellisense is also crucial in development speed and it is handled in VS2013 by using the _reference.js file with the javascript file definitions or by inserting commented-out references of js files (see the Reference Directives section).
  • Typescript or Coffescript support coming out of the box by installing the Typescript addon and the Mindscape Web Workbench or the Web Essentials extension.

So how do all those fit in the Windows8 development workflow. Can I take advantage of them?

The answer is yes provided that you follow some simple guidelines in order to make your life easier.

  • Automatic browser refresh In the Windows8 development workflow, your browser equivalent is Expression blend. You will find yourself a lot of times annoyed by having to press the "Reload" button on blend whenever you make some change in an html-css file in VS2013. To avoid this and have this "automatic refresh" experience you need to go to Tools/Options/Project in Blend and enable the "Always reload files modified outside of Blend" option.
  • We need a way to keep our CSS stylesheets DRY You want to use SASS but you usually perform all your CSS changes in Blend. Having enabled automatic reloads in the previous step you do not need to use Blend for editing your CSS files anymore. So having installed the Mindscape Web Workbench extension and using VS2013 to code in SASS, whenever you save, your css is created automatically and Blend is also automatically refreshed to reflect your changes.
  • We need a way to write faster HTML. Zen coding works like a charm in Windows8 development (Web Essentials plugin) and therefore VS2013 is the "natural" place to write HTML for your Windows8 project. The only problem here is when Blend refreshes due to a change in an HTML file it looses its current state( meaning that if you used it to navigate to a specific part of you app and then started working in that state this state will be lost). Therefore the recommendation here is to use VS2013 for your "heavy-duty" HTML creation and then use Blend to perform smaller changes in the html file since changing those files in blend preserves the application's state.
  • Javascript intellisense Unfortunately a _reference.js file cannot be used in Windows8. But there are other things you can do. First use IDs for your HTML elements that will become winControls. By doing so you get out of the box full intellisense support for those controls in the js files and you do not need to use querySelector to select them. That is document.querySelector("#demoControl").winControl now becomes demoControl.winControl with intellisense after the dot targeted to the specific control. You can also use commented-out references of js files (see the Reference Directives section) and also keep in mind that js files declared in the same HTML file share intellisense suggestions to each other.
  • Typescript or Coffescript support coming out of the box by installing the Typescript addon and the Mindscape Web Workbench or the Web Essentials extension. Especially for TypeScript after you install the addon make sure to go to Tools/Options/Text Editor/TypeScript/Project an check the "Automatically compile Typescript files which are part of the project" option. Additionally, if you want intellisense support in Typescript for libraries that are not written in Typescript such as (WinJS, JQuery etx) you can download the .d.ts files from nuget, include them in your projects and then reference them using the above Javascript intellisense techniques.
Posted: Σάββατο, 30 Νοεμβρίου 2013 11:17 πμ από iwannis | 3 σχόλια
Δημοσίευση στην κατηγορία: , ,
WinJS ListView - The most important features an how to use them

The WinJS.UI.ListView control is one of the most important in Window Store Applications. It is the basis of the application's UI and the one most familiar to users. In this post will explore the features/tweaks available for the control that are most needed to the developer when used. This post can serve as a cheatsheet to anyone wishing to use the WinJS.UI.ListView control in his applications.

Adding a ListView, its Data Template, its Data Items and styling it

In the part of the page you want the list to be displayed just add a div element with the data-win-control attribute set to WinJS.UI.ListView.

<div id="demolistview" data-win-control="WinJS.UI.ListView"></div>

The ListView by default will have a height of 400px and a width of 100%. Next, we need to add a data template for the list elements. This is done by creating a div element and setting its data-win-control attribute to WinJS.Binding.Template. Then we can use the data-win-bind attribute to bind to specific properties of the elements in the data list used for the ListView. One representative example of a data template is the following:

<div id="dataTemple" data-win-control="WinJS.Binding.Template">
    <div class="dataTemplateContainer">
        <div class="title" data-win-bind="textContent:title;style.backgroundColor:backColor"></div>
        <img src="#" class="image" data-win-bind="src:image" />
    </div>
</div>

We also need some demo data list that will hydrate the ListView. We can use a javascript Array for this purpose but it is better to create a WinJS.Binding.List for our data. This kind of list provides notifications to the ListView control toget automatically updated when items are inserted/removed. Below is an example of such a list with demo data for our purpose. Note that the properties of the elements of the list match the names that are used in the "data-win-bind" attributes of the elements in the data template.

var dataBindingList = new WinJS.Binding.List();
for (var i = 0; i < 100; i++) {
    dataBindingList.push({ 
    	title: 'Item ' + i.toString(), 
    	image: '/images/demo.jpg', 
    	backColor: 'rgba(255,0,0,0.5)' 
    });
}

Next we need to bind the data template and the data list with the ListView. This is done using the code below:

var listViewControl = document.querySelector("#demolistview").winControl;
listViewControl.itemDataSource = dataBindingList.dataSource;
listViewControl.itemTemplate = document.querySelector("#dataTemple");

That is we get the ListView object (listViewControl) and then associate the data list (dataBindingList) and the data template. There is a last think necessary to specify which is the default size of each element in the list. This is set for the div element of the list item with class name "win-item". This element gets generated for you by the framework. So for our example this should be elements of 100x100 pixels size:

#demolistview .win-item{
	width:100px;
	height:100px;
}

One thing to keep in mind is that when styling the list elements you should not be using the id of the element defined as the template (in our case #dataTemplate) since this will not work. Insted use an element within the template (and this is the reasong why in our case we define the element with the dataTemplateContainer class name). For example, here is a possible styling for the example's template:

.dataTemplateContainer {
	display:-ms-grid;
	height:100%;
	-ms-grid-columns:1fr;
	-ms-grid-rows:50px 1fr;
}
.dataTemplateContainer .title {
	-ms-grid-row:1;
}
.dataTemplateContainer .image {
	-ms-grid-row-span:2;
}

Populating the ListView with data from a service

In this case you create the ListView as stated above and also the dataList as before but you do not fill it with data. You can then perform an asynchronous request (WinJS.xhr) to get some data from a web service and once you get them you push the items to the data list as follows (the following example uses the Web API )

WinJS.xhr({ url: 'SERVICE URL' }).then(function (data) {
    var responseJSON = JSON.parse(data.response);
    var items = responseJSON.results;
    for (var i = 0; i < items.length; i++)
        dataBindingList.push({
            title: itemsIdea.aliases,
            image: itemsIdea.image ? itemsIdea.image.medium_url : '/images/listview/noImage.png',
            backColor: 'rgba(255,255,255,0.5)'
        });
});

Note that the respose is contained in data.response and you need to convert it to a JSON object using JSON.parse. You then cycle through the results an map them one to one to the elements of the list. Since the list is of WinJS.Binding.List type the moment you push the items, they appear to the UI.

Changing the layout

The default list layout is grid layout. This means that items will first occupy all their container height and then, they will start expanding to the right. You can also set the layout to list layout which will expand your items top to bottom like an ordinary list making them occupy all the available horizontal space. The layout is chosen by setting the listViewControl.layout property to either "new WinJS.UI.GridLayout()" or "new WinJS.UI.ListLayout()".

Creating a grouped view

One of the features of the ListView is its ability to group its items and display them to the user as groups with a title. For this to work each item in the list needs to carry a property that will serve as the group definition for the item. Say for example that you have 3 groups with the following data:

var groups = [
    {id:1,title:'Group 1'},
    {id:2,title:'Group 2'},
    {id:3,title: 'Group 3' }
];

Each item in the list has a group property that points to one of those groups. Next, you execute the following, which creates your grouped datasource:

dataBindingList = dataBindingList.createGrouped(function getKey(item) {
    return item.group.id;
}, function getGroup(item) {
    return item.group;
}, function sortGroup(groupKey1,groupKey2) {
    if (groupKey1 > groupKey2) return 1;
    return -1;
});

Which means that you change your initial WinJS.Binding.List with the createGrouped method to which you supply three functions. One that returns the group key for each item, one that returns the group object for each item and one that helps in the sorting of the groups. The last needs you to return for each two group key combinations a,b, 1 if a>b and -1 if a<b.

The last step is associating the new datasource with the ListView as follows:

listViewControl.itemDataSource = dataBindingList.dataSource;
listViewControl.groupDataSource = dataBindingList.groups.dataSource;

If you do all this and execute the app you will notice that the list items are getting grouped but the group title displays the group object itself serialized as a JSON string. This is due to the fact that your ListView does not have a template to use in order to diplay the group. So you need to define one, for example:

<div id="groupTemplate" data-win-control="WinJS.Binding.Template">
    <div class="groupTemplateContainer">
		<div class="title" data-win-bind="textContent:title"></div>
    </div>
</div>

To be able to use this template you need to declare it in javascript as follows:

 listViewControl.groupHeaderTemplate = document.querySelector("#groupTemplate");

List items with different sizes

So what if you want to support different dimensions for your tiles? Say for each group you have one "featured" element which you want to reside in a bigger tile. With the ListView this is possible provided that the two different sized tiles are both divisible with the same "minimum" grid size that you define. In mathematical terms the two (or more sizes) should have the same GCD for each one of their dimensions which will be used as the "minimum" grid size (you also need to consider the margin as you will see in the following example).

Let's say we want to support an 130x130 dimension for the items and a dimension around 390x390 for the featured item. Then the "GCD" for both dimensions is 130x130 but since we also have a 10 pixels margin between tiles in both directions the small tile should be 130 by 130 and the big one 390+20 by 390+20 due to the margins (3 times the small tile plus 2 times the margins between them). In javascript we need to define the GCD (130 by 130) as follows:

listViewControl.layout = new WinJS.UI.GridLayout({
    groupHeaderPosition: "top",
    groupInfo: {
        enableCellSpanning: true,
        cellWidth: 130,
        cellHeight: 130
    }
});

We also define the template for the featured item:

<div id="dataTemplateFeatured" data-win-control="WinJS.Binding.Template">
    <div class="dataTemplateContainer featured">
		<img src="#" class="image" data-win-bind="src:image" />
        <div class="title" data-win-bind="textContent:title;style.backgroundColor:backColor"></div>  
    </div>
</div>

Finally we specify a function that gives the appropriate template based on the tile's data:

listViewControl.itemTemplate = function (itemPromise) {
    return itemPromise.then(function (item) {
        var itemTemplate = getTemplateForItem(item);
        var container = document.createElement("div");
        itemTemplate.winControl.render(item.data, container).done();
        return container;
    });
}

The getTemplateForItem() function is the one deciding which template will be sent to the view. In our case it is implemented as follows:

function getTemplateForItem (item) {
        var itemData = item.data;

        if (itemData.isFeatured)
            return document.querySelector('#dataTemplateFeatured');

        return document.querySelector('#dataTemplate');;
    }

Get the item pressed

This is an easy one. You just put a callback on the oniteminvoked event. To get the element that was pressed use the index provided to you by the event.detail.index property to search in the list as follows:

listViewControl.oniteminvoked = function (event) {
    var index = event.detail.itemIndex;
    var item = dataBindingList.getAt(index);
    // ...
}

Get the group item that was pressed

This is more tricky and in my opinion it is kind of a hack. You bind to a click event on the group title element itself and you provide the argument you need to know which group was invoked. For example you specify a global method named groupHeaderInvoked which accepts as the first argument the group's key as follows:

ipplos.groupHeaderInvoked = function (key) {
    // ...
}

You then bind to the click event in the group's template. The ony thing you need is to be able to pass the key value. This is done by attaching an extra attribute to the element via data-wind-bind:

<div id="groupTemplate" data-win-control="WinJS.Binding.Template">
	<div class="groupTemplateContainer">
		<div class="title" data-win-bind="textContent:title;groupKey:id" onclick="ipplos.groupHeaderInvoked(event.srcElement.groupKey)"></div>
    </div>
</div>

Note this method which may come in handy in many other situations. In the case where your groupHeaderInvoked method is in a page cotnrol you can refer to it by the Application.navigator.pageControl property which holds the current active page control.

Posted: Σάββατο, 31 Αυγούστου 2013 7:58 πμ από iwannis | 0 σχόλια
Δημοσίευση στην κατηγορία: ,
Handling errors and internet connectivity problems in Windows 8 Apps with navigation (WinJS)

In this post we will explore a generic way of handling unpredicatble errors/lost connectivity in our Windows 8 Apps developed with HTML5 and Javascript. The two requirements we need to tackle are as follows:

  • There is a chance that while our application is running, internet connectivity will be lost. We need to know when this happens in order to inform the user and possibly handle the problem while preserving application state.
  • Unpredicatble errors may occur that will cause a general exception. We need to "catch" those errors, informing the user with a generic (and apologetic) error message and maybe transmit some useful details about the error to a service we have implemented for this purpose.

Handling internet connectivity problems

First we need a way to verify whether the app has connectivity to the internet. This can be done by using the following function:

utilities.isConnected=function() {
	var connectivity=Windows.Networking.Connectivity;
	var profile = connectivity.NetworkInformation.getInternetConnectionProfile();
	if (profile) {
		return (profile.getNetworkConnectivityLevel() != connectivity.NetworkConnectivityLevel.internetAccess);
	}
	else {
		return false;
	}
}					

The function returns true if the application is connected to the internet and false if it is not. This function can be used in a page whenever the user clicks on a button or in general an action will be invoked that will trigger a WinJS.xhr or something similar and if it returns false the application should prevent the action from being executed.

The same can be applied when the user navigates to a page that uses the internet in its "onload" event. But in this case what can you do? Meaning that the user reaches the page/pages that to fetch data "on load", the connection is down so nothing is fetched and then you need to display a button or something or start a timer that checks the coneectivity and at some time refreshes the page.

Or you could easily and with a few lines of code handle this case generally as follows: The idea is to use a "nointernet" page and lead the user there when you detect there is no internet. Then you prompt the user to hit the back button or a button you provide when he feels that the connection has been re- established. So let's see how this works:

First you need to implement a simple "no internet" page. A snapshot of the page is given below.

demo nointernet page

In the navigator.js file that handles the navigation, we find the _navigated function that is called whenever a user goes from one page to another before the load event of the new page and we add the following lines at the top:

_navigated: function (args) {
	if (args.detail.location.indexOf("nointernet") == -1 && !utilities.isConnected()) {
		nav.navigate("/pages/nointernet/nointernet.html");
		return;
	}	
	// ...rest of the function			

This means that if we are not already in the "nointernet" page and the internet connectivity has been lost instead of going wherevere the user chose to go we navigate to the "nointernet" page.

In the "nointernet" page the click event handler of the "Retry" button calls WinJS.Navigation.back();

And this is it. With just a few lines of code you can handle with dignity all connectivity issues that may occur during the user's navigation in all application pages

Handling unpredicatble erros

The second requirement states that the app should be able to handle unpredictable errors in a graceful manner. We will follow a similar approach with an "error" page dedicated just for that.

demo nointernet page

The "Retry" button with just call again WinJS.Navigation.back() hoping that ther error was temporar. But how do we send the user to this page upon an unpredictable error?

We just need to handle the onerror event in the default.js file (or in some other file) as follows:

app.onerror = function (customEventObject) {
    var errorMessage = '';
    var errorName = '';
    if (customEventObject.detail.error) {
        errorMessage = customEventObject.detail.error.message;
        errorName = customEventObject.detail.error.name;
    }
    else {
        errorMessage = customEventObject.detail.exception.message;
        errorName = 'Exception';
    }
    var optionsObject = { errName: errorName, errMsg: errorMessage };
    nav.navigate("/pages/error/error.html", optionsObject);
    return true;
}				

Now if you want to take this one step further and have some private service listening for those errors (so you know what your users are experiencing) you can implement a simple ASP.MVC 3 application (empty), add just one controller (say ServicesController) with a single Action called Error:

public class ServicesController : Controller
{
    public JsonResult Error(string errName,string errMsg)
    {
        // Store the data in the database or whatever 
        return Json(new {stored=true},JsonRequestBehavior.AllowGet);
    }

}				

You host this service in the net and then in the "error" page (on ready event) you write something similar to the following:

ready: function (element, options) {
    element.querySelector("#buttonRetry").onclick = function (ev) {
        WinJS.Navigation.back();
    }

    WinJS.xhr({ url: 'http://localhost:10162/Services/Error?errName=' + 
                options.errName + '&errMsg=' + options.errMsg }, function (data) {

    });
}				

Now you have a personal bug reporting tool so you know the problems that occur for each one of the deployed application instances out there.

Posted: Παρασκευή, 24 Μαΐου 2013 3:57 μμ από iwannis | 0 σχόλια
Δημοσίευση στην κατηγορία: ,
Drag and Drop in WinJS.UI.ListView for repositioning items in Windows Store Apps

After hours of searching in the documentation in order to implement a decent drag and drop behavior in WinJS.UI.ListView (like the one we see on the “start” screen) I have finally managed to make it happen and in this post I am sharing the way it can be done. During this post there are also answers about:

How to provide multiple templates for the ListView items.

How to “gesture enable” your DOM elements.

The main requirement is simple.

You have a WinJS.UI.ListView control on your Windows Store app and need to allow the user move some elements in the list (usually to allow the user to define via drag and drop their order in the list).

We will start with a very simple project where a demo list (dataList) is defined as follows:


var groups = [
    { key: 'group1', title: 'Group 1' },
    { key: 'group2', title: 'Group 2' }
];
var dataList = new WinJS.Binding.List();
dataList.push({ group: groups[0], data: { id: 1, name: 'one' } });
...
dataList.push({ group: groups[0], data: { id: 7, name: 'seven' } });

dataList = dataList.createGrouped(
        function (item) {
            return item.group.key;
        },
        function (item) {
            return item.group;
        });
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }


That is we create a WinJS.Binding.List object with 7 elements all having their property group pointing to groups[0] (which means that they will belong to the same group.Later we will change that) and some demo object in their data property. We then use the createGrouped method passing as arguments the function that defines the id of the group each item belongs to and the function that returns the actual group object for each item. Now our dataList is ready to be the source of a WinJS.UI.ListView control. The control in HTML is defined with two templates (one for the group items with class name “.groupTemplate” and one for the list items with class name “.itemTemplate”).


<div class="groupTemplate" data-win-control="WinJS.Binding.Template">
    <span data-win-bind="textContent: title"></span>
</div>
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
    <div style="width:200px;height:200px;background-color:green">
        <div data-win-bind="textContent:data.name" style="padding:20px"></div>
    </div>
</div>
<div data-win-control="WinJS.UI.ViewBox">
    <div class="fixedlayout">
        <div id="demoListView" style="width:100%;height:100%;" data-win-control="WinJS.UI.ListView"></div>
    </div>
</div>
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Note that although we could declarative define the templates in the div element of the ListView control, we choose not to do so since for the implementation of drag and drop we will have to add some code to the “item from template generation logic”. Therefore we initialize the templates via code as follows:


var demoListView = document.querySelector("#demoListView");
var demoListViewControl = demoListView.winControl;
demoListViewControl.layout = new WinJS.UI.GridLayout({
    groupHeaderPosition: "top",
    groupInfo: {
        enableCellSpanning: true,
        cellWidth: 100,
        cellHeight: 100
    }
});
demoListViewControl.groupHeaderTemplate = document.querySelector('.groupTemplate');
demoListViewControl.itemTemplate = function (itemPromise) {
    return itemPromise.then(function (item) {
        var itemTemplate = document.querySelector('.itemTemplate');
        var container = document.createElement("div");
        itemTemplate.winControl.render(item.data, container).done();
        return container;
    });
}
demoListViewControl.itemDataSource = dataList.dataSource;
demoListViewControl.groupDataSource = dataList.groups.dataSource;
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

The layout property is defined as a GridLayout object and we provide the enableCellSpanning, cellWIdth and cellHeight properties to allow multiple sizes for the item templates if needed (only remember that their size needs to be a multiple of the cellWidth and cellHeight properties taking into consideration the margins between them). We define a fixed header template for the display of the current group and then a function that returns the required template for the item. Usually the function is provided if we need to support multiple item template but in our case we use it to gain access to the items’ DOM elements.

Let’s first “gesture enable” our DOM Elements so that they can understand user gestures (meaning different “kinds” of mouse or touch interactions such as tap, hold, swipe). To do this we need to run a function like the following for the DOMElement:


prepareDOMElementForGestures:function (DOMElement) {
    var msGesture = new MSGesture();
    msGesture.target = DOMElement;
    DOMElement.gesture = msGesture;

    DOMElement.addEventListener("MSGestureStart", _gestureHandler, false);
    DOMElement.addEventListener("MSGestureEnd", _gestureHandler, false);
    DOMElement.addEventListener("MSGestureChange", _gestureHandler, false);
    DOMElement.addEventListener("MSInertiaStart", _gestureHandler, false);
    DOMElement.addEventListener("MSGestureTap", _gestureHandler, false);
    DOMElement.addEventListener("MSGestureHold", _gestureHandler, false);
    DOMElement.addEventListener("MSPointerDown", _gestureHandler, false);
}

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }


This adds the appropriate event handlers and assigns the MSGesture object to the DOM element. The handler “_gestureHandler” is as follows:
_gestureHandler :function (event) {
    if (event.currentTarget.gesture.target == null)
        event.currentTarget.gesture.target = event.currentTarget;
    if (event.type == "MSPointerDown") {
        event.currentTarget.gesture.addPointer(event.pointerId);
    }
    if (event.type == "MSGestureStart") {
        if (this._inDragAndDrop) {
            // initializations here
        }
    }
    if (event.type == "MSGestureHold") {
        this._inDragAndDrop = true;
    }
    if (event.type == "MSGestureChange") {
        if (this._inDragAndDrop) {
            // respond to mouse movement
        }
    }
    if (event.type == "MSGestureEnd") {
        if (this._inDragAndDrop) {
            // finalize
            this._inDragAndDrop=false;
        }
    }
    if (event.type == "MSGestureTap") {
    }
}

Some important points here is the you should not erate the MSPointerDown if block since it is needed for the handler. Also the highlighted in yellow part is a hack since sometimes the DOMElement looses its gesture object when added to the list and by doing this we reassign it in the first call of the handler. The other is straightforward. On Hold we set the drag and drop is ready to start. On Start we initialize, on Change we move and on End we will be checking what has happened. The tap gesture is needed since by enabling gestures on the DOMElement we are effectively “stealing” its click event.

Therefore to enable gestures in our list item’s DOM Elements we change the rendering line of the template generation function (and this is why we need it even though we only have one template) as follows:


demoListViewControl.itemTemplate = function (itemPromise) {
        ...
        itemTemplate.winControl.render(item.data, container).done(function (ev) {
            setTimeout(function () {
                dragAndDrop.prepareDOMElementForGestures(container.parentElement, true);
            }, 100);
        });
        ...
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

The whole idea lies at the fact that as we generate the DOM element for the list item, we enable it for the gestures. The timeout is needed to allow some time for the ListView to render the item in the document so we get the complete DOMElement. Now when the hold gesture is detected we set the boolean of drag and drop to true and then when gesture start is detected. So in “MSGestureStart” if block:


if (this._inDragAndDrop) {
    event.target.style.zIndex = 10000;
    WinJS.UI.Animation.dragSourceStart(event.currentTarget).done();
    this._initPos.left = parseInt(event.target.style.left.replace("px",""));
    this._initPos.top = parseInt(event.target.style.top.replace("px", ""));

    var listToScrollElement = this._listViewDOM.querySelector(".win-viewport.win-horizontal");
    this._offset.left = event.clientX + listToScrollElement.scrollLeft - this._initPos.left;
    this._offset.top = event.clientY - this._initPos.top;
}


We set the zIndex of the element to a big value to make it appear on top of everything else. We then animate the element to show that drag has started. We then store the initial position of the element (to return it if drop is cancelled) and we also calculate the offset of the mouse-touch position in relation to the top left corner of the DOMElement (this is to keep moving the element at the point where our mouse-touch pointer was initially detected and it also takes into consideration the scroll position).

Now during the movement of the mouse-finger (MSGestureChange detected) we need to move the DOM element and also detect which items are under it in order to animate them also to give the user some feedback on where the item will be placed if dropped. Finally we need to detect the bounds of the display in order to scroll the list if needed.

We will tackle those issues one by one but note that all the code mentioned in the following paragraphs actually belongs to the “MSGestureChange” if block. Dragged item movement is the simplest of all:


var x = event.clientX;
var y = event.clientY;
var listToScrollElement = this._listViewDOM.querySelector(".win-viewport.win-horizontal");
event.currentTarget.style.left = (x - this._offset.left + listToScrollElement.scrollLeft).toString() + "px";
event.currentTarget.style.top = (y - this._offset.top).toString() + "px";
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Then we need to know what element is under the mouse (not the dragged but the other below). This is achieved by temporarily hiding the dragged element to reveal through document.elementFromPoint the element underneath.


// get the DOM element currently under the mouse-finger
event.currentTarget.style.display = 'none';
var DOMElement = document.elementFromPoint(x, y);
event.currentTarget.style.display = '';
// get the index of the actual list item that the DOMElement belongs to (if none –1 will be returned)
var idx = this._listViewControl.indexOfElement(DOMElement);
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Now the whole idea is to know which two elements need to be moved as the item is being dragged

drag

Using the above code we know the currentOverElement1(named DOMElement) and if idx is not –1 we know that this is actually a part of the ListView and not something that does not participate in the drag and drop action. So if we detect an idx change and this change gives as an idx that is not –1 then we know that currentOverElement1=DOMElement and we need to figure out what the currentOverElement2’s value will be. Of course this applies when we move to the right of the list. When we move left then we know currentOverElement2 and we need to find currentOverElement1. We also need to check for limit conditions at the start – end of the list and revert the previous currentOverElements.All of the above are implememented right after the previously mentioned code block in (MSGestureChange)


if (this._idxOfcurrentOverElement != idx) { // this means that the element has changed since the last event
    // if there were any previous elements animated
    if (this._currentOverElement1) WinJS.UI.Animation.dragBetweenLeave(this._currentOverElement1);
    if (this._currentOverElement2) WinJS.UI.Animation.dragBetweenLeave(this._currentOverElement2);

    var draggedItemIdx = this._listViewControl.indexOfElement(event.currentTarget);
    this._idxOfcurrentOverElement = idx;
    // if there are some elements to be animated below the mouse-finger
    if (this._idxOfcurrentOverElement != -1) {
        if (draggedItemIdx > this._idxOfcurrentOverElement) { // we are moving to the left
            this._currentOverElement1 = undefined;
            this._currentOverElement2 = this._listViewControl.elementFromIndex(this._idxOfcurrentOverElement);
            if ((this._idxOfcurrentOverElement - 1) >= 0)
                this._currentOverElement1 = this._listViewControl.elementFromIndex(this._idxOfcurrentOverElement - 1);
        }
        else { // we are moving to the right
            this._currentOverElement1 = this._listViewControl.elementFromIndex(this._idxOfcurrentOverElement);
            this._currentOverElement2 = undefined;
            if ((this._idxOfcurrentOverElement + 1) < this._listViewControl.itemDataSource.getCount()._value)
                this._currentOverElement2 = this._listViewControl.elementFromIndex(this._idxOfcurrentOverElement + 1);
        }
        if (this._currentOverElement1)
            WinJS.UI.Animation.dragBetweenEnter(this._currentOverElement1, { left: this._movementOffset.left, top: '0px' });
        if (this._currentOverElement2)
            WinJS.UI.Animation.dragBetweenEnter(this._currentOverElement2, { left: this._movementOffset.right, top: '0px' });
    }
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }


When the user drops the item since we know currentOverElement1 and 2 we know where the item is being dropped. If those are undefined we just restore the initial element’s position. Otherwise we call a method with the currentOverElement1,2 indexes as parameters that will manipulate the list and will return true if it has handled the placement or false if based on the logic the insertion should not be performed (MSGestureEnd if block).


this._inDragAndDrop = false;
event.target.style.zIndex = "";

if (this._currentOverElement1) WinJS.UI.Animation.dragBetweenLeave(this._currentOverElement1).done();
if (this._currentOverElement2) WinJS.UI.Animation.dragBetweenLeave(this._currentOverElement2).done();
WinJS.UI.Animation.dragSourceEnd(event.target, undefined, this._allDOMElements).done();

var idxOfLeftItem = this._listViewControl.indexOfElement(this._currentOverElement1);
var idxOfRightItem = this._listViewControl.indexOfElement(this._currentOverElement2);

var itemPlaced = false;
if (idxOfLeftItem != -1 || idxOfRightItem != -1)
    itemPlaced = this._dropCallback(this._listViewControl.indexOfElement(event.target), idxOfLeftItem, idxOfRightItem);
if (!itemPlaced) {
    event.target.style.left = this._initPos.left.toString()+"px";
    event.target.style.top = this._initPos.top.toString()+"px";
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }


A possible implementation of the dropcallback can be as follows:


function (itemIdx, leftItemIdx, rightItemIdx) {
    var dropAfterItem;
    if (leftItemIdx != -1)
        dropAfterItem = dataList.getAt(leftItemIdx);

    var item = dataList.getAt(itemIdx);
    var result = false;
    if (!dropAfterItem) { //Insert at the beginning
        dataList.move(itemIdx, 0);
        result = true;
    }
    else {
        var desiredPos = leftItemIdx < itemIdx ? leftItemIdx + 1 : leftItemIdx;
            dataList.move(itemIdx, desiredPos);
            result = true;
    }
    return result;
}

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }


Where we just check the indexes and perform some movements between the items in the list.

The only thing left to be added is the scrolling of the list while an item is being dragged. This is a matter of detecting the bounds of the displayed list and scrolling the view area while the mouse – finger remains in those bounds via a setTimeout. At the end of the drag and drop or when the mouse – cursor leaves those bounds you should remember to cancel the timeout. The implementation of the scrolling can be found in the demo project.

So this is what it takes to have drag and drop in WinJS.UI.ListView. A minor note to remember is that you will notice that dragBetweenLeave leaves a scale(0.95) transform to the element which I consider to be a library’s bug. If you find any solution to this please do contact me.

Download the demo project.

Posted: Τρίτη, 18 Δεκεμβρίου 2012 12:19 μμ από iwannis | 0 σχόλια
Δημοσίευση στην κατηγορία: , ,
Tile Notifications in Windows 8 (Javascript)

This post will cover the main aspects of using Tile Notifications in a Metro-Style application. The only topic that will not be covered is “Push Notifications” since those deserve a post on their own and will follow in the near future.

So what are “Tile Notifications”? Well as you already know, each Metro-Style application has its own tile which appears in the start screen. This tile is either a “square” or a “wide” one as depicted in the following figure:

 

tiles

 

The user gets to choose from the “start menu” application bar which one of the two will be shown. You specify in your application that you support both by providing in the package.appxmanifest file (Application UI tab) a image for both the Logo and the Wide logo property:

 

LogoWide

 

It is important that you provide the wide version of the logo cause otherwise your application will not be able to appear as “wide” in the start menu and that option will not exist for the user in the application bar. Having done this, you may use the tile to make it “alive”, that is to provide some meaningful information (related to your app) to the user. The way this information will be delivered, defines the type of the notification:

 

Scheduled: Your application, when it is executed, schedules for some tile notification in the future. A possible scenario for this is when you have for example a calendar app, the user adds an appointment and defines that he would like to get notified. Then your app may schedule a notification to appear in the tile when the time comes.

Periodic: Your applications, at specific time intervals, asks from the operating system to make a request to a web service and display in the application’s tile the content it receives from the service. A possible scenario for this is when for example your app displays news of some kind and you want your tile to always present the latest news.

Push: Those are notifications that are initiated from a web service outside of your app which instructs the OS to change your tile with the content the service provides. As stated before this kind of notification and the way to create a web server capable of delivering such notification will be explained in a future post.

 

Apart from the type of notifications you may also define a notifications queue. That is you can define whether a newer notification will overwrite the current one displayed on the tile or the new notification will be inserted in a queue of the five most recent ones to be cycled on the tile. So let’s get started with the following code that schedules for a notification after 10 seconds:

   1: var notifications = Windows.UI.Notifications;
   2: var currentDate = new Date();

The following lines create the tile appearance for its “wide” version. The version is defined by the template (highlighted in yellow). The list of available templates may be found here. Basically the content provided for the tile is nothing more than a simple xml document with the information required for each tile type.

   1: var tileXMLWide = notifications.TileUpdateManager.getTemplateContent
                                               (notifications.TileTemplateType.tileWideText03);
   2: var tileAttributesWide = tileXMLWide.getElementsByTagName("text"); 
   3: tileAttributesWide[0].appendChild(tileXMLWide.createTextNode("First Notification"));

The following lines create the tile appearance for its “square” version (of course we may only create one of the two but it is good practice if we support both versions to support both versions in the notifications also):

   1: var tileXMLSquare = notifications.TileUpdateManager.getTemplateContent
                                               (notifications.TileTemplateType.tileSquareText01);
   2: var tileAttributesSquare = tileXMLSquare.getElementsByTagName("text"); 
   3: tileAttributesSquare[0].appendChild(tileXMLSquare.createTextNode("1st"));

Finally the two versions “square” and “wide” are packed in a single XML document:

   1: var node = tileXMLWide.importNode(tileXMLSquare.getElementsByTagName("binding")[0], true); 
   2: tileXMLWide.getElementsByTagName("visual").item(0).appendChild(node);

And the tile update is ready to be scheduled

   1: var futureTile = new notifications.ScheduledTileNotification(tileXMLWide, 
                                                       new Date(new Date().getTime() + 10 * 1000));
   2: futureTile.id = "Tile1"; 
   3: notifications.TileUpdateManager.createTileUpdaterForApplication().addToSchedule(futureTile);

If we want to add more scheduled notifications we just create them as above and add them to the Schedule with the previous (last) line. Now because we have not specified any "”queue” the next notification will overwrite the current. Therefore the tile’s id, tag does not play an role. If we don’t provide a new notification the current one will remain visible for ever unless we provide an expiration date for the notification. Adding the following line to the notification we have created before adding it to the schedule will make the notification disappear after 10secs from its appearance:

   1: futureTile.expirationTime = new Date(new Date().getTime() + 20 * 1000);

Now for periodic notifications we need to create a basic service (web service) that returns the XML required for updating the tile. The XML to generate is the same as before and taken from the possible templates here. Again we main combine the two tile versions square and wide to support both. An example of possible XML generated from the service is given in this link. So how can you create first of all such a service? One way to go is to use an ASP.NET MVC project. Go ahead and create one. Then erase the Content, Script etc folders. Create a class in the Models folder as follows:

   1: public class TileData
   2: {
   3:     public string Image1 { get; set; }
   4:     public string Image2 { get; set; }
   5:     public string Image3 { get; set; }
   6:     public string Image4 { get; set; }
   7:     public string Image5 { get; set; }
   8:  
   9:     public List<string> Text { get; set; }
  10: }

Then write the following code in the HomeController’s Index action (you basically need to populate with data the model you have created in the Models folder and pass it to the view). In the following example we just populate the model with random values:

   1: public ActionResult Index()
   2: {
   3:     TileData Model = new TileData();
   4:     // Popullate the model with your data here
   5:     return View(Model);
   6: }

Finally your view (for the index action) will be something like the following:

   1: @{
   2:     Layout = null;
   3:     Response.ContentType = "text/xml";
   4: }
   5: @model MetroStylePullNotificationDemo.Models.TileData 
   6: <tile>
   7:     <visual lang="en-US">
   8:         <binding template="TileSquarePeekImageAndText03">
   9:             <image id="1" src="@Model.Image1" />
  10:             @for (int i=0;i<Model.Text.Count;i++)
  11:             {
  12:                 @:<text id="@(i+1)">@Model.TextIdea</text>
  13:             }
  14:         </binding>
  15:         <binding template="TileWideImageCollection">
  16:             <image id="1" src="@Model.Image1" />
  17:             <image id="2" src="@Model.Image2" />
  18:             <image id="3" src="@Model.Image3" />
  19:             <image id="4" src="@Model.Image4" />
  20:             <image id="5" src="@Model.Image5" />
  21:         </binding>
  22:     </visual>
  23: </tile>

You then deploy your service and you are “good to go” server-side. In your metro-style app it is very easy to tell the OS that you need it to query that service to update your tile with the following code:

   1: var recurrence = notifications.PeriodicUpdateRecurrence.halfHour; 
   2: var url = new Windows.Foundation.Uri("http://www.progware.org/windows8/tiles/"); 
   3: notifications.TileUpdateManager.createTileUpdaterForApplication().
                                      startPeriodicUpdate(url, recurrence);

Note that the period is an enumeration and can be of specific values only (30min, 1hour, 6hour, 12hour,24hour) and that is expected since the OS will handle the requests and your app lives in an ecosystem with hundreds of other apps that will compete for the same resources and bandwidth.

 

No matter the notification type (scheduled or periodic) you can activated the queue for holding the last ones and recycling them in a random order that favors the most recent ones. This is done with a single directive:

   1: notifications.TileUpdateManager.createTileUpdaterForApplication().enableNotificationQueue(true);

Having the queue means that your notification may live more than you want them to or you may need to update an already present notification in the queue. For this reason in this case it is important to tag your notifications by setting their tag property (for scheduled notifications). In periodic notifications this tag is inserted at the response header (see here– the Remarks section). Actually, in the header, we also store the notification’s expiration time.

So this was a brief overview on using tile notifications in metro-style apps with Javascript.We have seen scheduled and periodic notifications. I n the near future there will be a post for push notifications.

Posted: Τετάρτη, 27 Ιουνίου 2012 8:51 πμ από iwannis | 0 σχόλια
Δημοσίευση στην κατηγορία: ,
(Windows8) WinJS single page navigation and ViewModels

In the previous post, we have seen simple data-binding scenarios using WinJS and got a glimpse on the way a ViewModel can be created (following the MVVM pattern). In this post, we will build and application where there are more than one views (pages - simple page navigation app) with simple html elements and ListViews with real data. The question we need to answer is:

 

How do the ViewModels and MVVM can be applied in a single page navigation WinJS application?”

(FOR .NET Developers: if you are coming from the .NET world and especially XAML you will also get a chance to see how things like INotifyPropertyChanged and ObservableCollections apply to the WinJS world. The ViewModel presented here is as close as possible to the one that would be applied in a WPF-Silverlight window)

 

We will create a simple app that displays the births and deaths of celebrities in today’s date. Data will come from the RSS feeds provided by IMDB. Clicking on a celebrity should lead us to a new page where we get the IMDB webpage of that celebrity. Within this application, ViewModels will be used for each page. The requirements we have for our ViewModels are as follows:

 

  • There should be one ViewModel for each page.
  • The ViewModel should hold all the variables and methods required to provide the data to the view (html page)
  • The view will connect to the ViewModel through data binding.
  • All events generated by the page should also fire methods of the ViewModel.

 

Open VS 2011 and create a “Navigation Application” project. The “default.html” file that is generated contains the following:

 

<div id="contenthost" data-win-control="BirthsDeaths.PageControlNavigator" 
data-win-options="{home: '/html/homePage.html'}">
</div>

That defines an area that will be loading your pages according to your application’s state. This area is initialized and loads the “homePage.html” page (VS has also generated for you a navigate.js file that handles the navigation. For an introduction to single page navigation you can read this article).

 

You should consider each page that will be loaded in this area as a separate window or view of your application. Therefore, each page should have its own ViewModel. In our case, we have two pages: one (homePage) that displays the births and deaths in two ListViews and one (actorDetails) that displays the actor’s details when the user clicks on an item in the lists.

 

Let’s start with the homePage view that will look like the one in the following figure:

 

image

 

That is, there should be two lists one showing the deaths and one showing the births. Also, just below the main header we need to display the date and time and a downloading message when the data are fetched from IMDB. The html that generates that view is (of course there is the .css that also does the layout magic):

 

<section aria-label="Main content" role="main">
<span id="dateSpan"></span>&nbsp;&nbsp;
<span id="downloadingSpan"></span>
<section class="listsContainer">
<h2 class="birthsHeader">born today</h2>
<h2 class="deathsHeader">died today</h2>
<div id="birthsListElement" data-win-control="WinJS.UI.ListView"></div>
<div id="deathsListElement" data-win-control="WinJS.UI.ListView"></div>
</section>
</section>

So for our ViewModel we will need: A property for the date/time, a property for the “downloading” message, two lists for the births and deaths, two methods to fetch the deaths and births and the event handler that will be invoked when the user clicks on a list item. In the “homepage.js” file, the process for creating this ViewModel is as follows:

Within the self executing function that is generated for us, we define a new class (WinJS.Class.define) that we name HomePageViewModelClass:

 

var HomePageViewModelClass = WinJS.Class.define(
function () {
this.onShowActorDetailsBirths = this.showActorDetailsBirths.bind(this);
this.onShowActorDetailsDeaths = this.showActorDetailsDeaths.bind(this);
},
{
_today: "",
today: {
get: function () {
return this._today;
},
set: function (value) {
this._today = value;
this.notify("today", value);
}
},

_downloading: "",
downloading: (...),

birthsList: new WinJS.Binding.List(),
deathsList: new WinJS.Binding.List(),
getBirths: function () {
this.downloading = "(downloading...)";
var that = this;
var syn = new Windows.Web.Syndication.SyndicationClient();
var url = new Windows.Foundation.Uri("http://rss.imdb.com/daily/born/");
syn.retrieveFeedAsync(url).then(
function (feed) {
for (var i = 0, len = feed.items.length; i < len; i++) {
var item = feed.itemsIdea;
var birth = {
title: item.title.text,
date: item.publishedDate,
content: item.summary.text,
link: item.links[0].nodeValue
}
that.birthsList.push(birth);
}
that.downloading = "";
});
},
getDeaths: (...),
showActorDetailsBirths: function (e) {
this._showActorDetails(this.birthsList.getAt(e.detail.itemIndex));
},
showActorDetailsDeaths: function (e) {
this._showActorDetails(this.deathsList.getAt(e.detail.itemIndex));
},

_showActorDetails: function (item) {
WinJS.Navigation.navigate("/html/actorDetail.html", item);
}

}
);


There are a few things to note here:

 

  • For the properties “today” and “downloading” we use accessors. The accessors get and set their corresponding private fields. Note that after they set their value they call a “strange” (for now) method which is called notify with the name of the property that has changed and the new value. You will see in just a minute where this method is defined but for now remember that this is what makes the data-bound part of the UI update when this property changes (for .NET Developers: the equivalent of PropertyChanged).

 

  • The “showActorDetailsBirths” and “showActorDetailsDeaths” methods are the ones that will be called when the user presses on a list item and therefore will be bound to the “oniteminvoked” event. Due to the javascript’s issues with the “this” keyword”, on the class constructor, we create two new methods from those ones (prefixed with “on”) using the bind method, that have the correct context for “this”, that is the object itself, and those will be the ones that will be bound to the events.

 

  • The two lists are of “WinJS.Binding.List” type in order to provide the necessary events for the UI to be updated when items are added to them (for .NET Developers” the equivalent to ObservableCollection).

 

Now just after the aforementioned class definition, we create from it a new class definition as follows:

 

var BindableHomePageViewModelClass = WinJS.Class.mix(HomePageViewModelClass, WinJS.Binding.mixin);

This actually uses the “WinJS.Class.mix” to add to our HomePageViewModelClass class all methods and properties that are contained in the “WinJS.Binding.mixin” class and guess what, those are the methods needed for providing change notifications of the data-binding (this is the source of the notify method). This also means that we should never use the HomePageViewModelClass but instead always use the BindableHomePageViewModelClass since this is the “complete” one after the application of the mix method.

 

Now we make our ViewModel public in the “ProgwareOrg” namespace as follows creating a new object. This is the object that will be bound to the view:

 

WinJS.Namespace.define("ProgwareOrg", {
HomePageViewModel: new BindableHomePageViewModelClass()
});

In the page’s functions that handle the basic events of our page we can bind the ViewModel to the View as follows (for .NET Developers: this code should be considered as the xaml.cs file and the binding to the ViewModel that occurs there):

 

WinJS.UI.Pages.define("/html/homePage.html", {
init: function init(element, options) {

var calendar = new Windows.Globalization.Calendar();
ProgwareOrg.HomePageViewModel.today = calendar.dayOfWeekAsString() + " " +

calendar.day + " " + calendar.monthAsString() + " " + calendar.yearAsString();
ProgwareOrg.HomePageViewModel.getBirths();
ProgwareOrg.HomePageViewModel.getDeaths();
},
ready: function ready(element, options) {
WinJS.Binding.processAll(home, ProgwareOrg.HomePageViewModel);
}
});


We initialize our ViewModel and in the ready function we bind it to the view via the “WinJS.Binding.ProcessAll” method (for .NET Developers: this would be the this.DataContext=ViewModel in XAML). This method takes as first parameter the root element where the ViewModel is applied and in our case is the one with the id=”home”.

 

The binding in the view is as follows:

 

<div class="homepage" id="home">
<section aria-label="Main content" role="main">
<span id="dateSpan" data-win-bind="innerText:today"></span>&nbsp;&nbsp;
<span id="downloadingSpan" data-win-bind="innerText:downloading"></span>
<section class="listsContainer">
(...)
<div id="birthsListElement"
data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource:ProgwareOrg.HomePageViewModel.birthsList.dataSource,
itemTemplate:select('#template'),
layout:{type:WinJS.UI.ListLayout},
oniteminvoked : ProgwareOrg.HomePageViewModel.onShowActorDetailsBirths}"
>
</div>
<div id="deathsListElement" (...)></div>
</section>
</section>
</div>

First note that the top-level element (the one used in processAll) is the id=”home” element. Also note the “data-win-bind”  attributes that provide the binding for the HTML elements.

 

For the two ListViews note the following:

 

The itemDataSource is bound to our ViewModel but it is using the full qualified name. This means that it does not take into consideration the processAll method. I do not know why this happens and I think that this is something that needs to be fixed but for now the hack I found is that for the controls of WinJS for binding with the ViewModel we need to use the full access paths of our ViewModel. This also applies to the binding of the oniteminvoked event handler. The template set is defined in the page just above this code and not displayed here for simplicity.

 

And this summarizes the way the ViewModel can be bound to our view. The ViewModel for the actorDetails page is simpler:

 

(function () {
"use strict";

var ActoDetailViewModelClass = WinJS.Class.define(
function () {},
{
_pageToShow: "",
pageToShow: {
get: function () {
return this._pageToShow;
},
set: function (value) {
this._pageToShow = value;
this.notify("pageToShow", value);
}
}
}
);

var BindableActoDetailViewModelClass = WinJS.Class.mix(ActoDetailViewModelClass, WinJS.Binding.mixin);

WinJS.Namespace.define("ProgwareOrg", {
ActoDetailViewModel: new BindableActoDetailViewModelClass()
});

WinJS.UI.Pages.define("/html/actorDetail.html", {
ready: function ready(element, options) {
ProgwareOrg.ActoDetailViewModel.pageToShow = options.link;
WinJS.Binding.processAll(mainSection, ProgwareOrg.ActoDetailViewModel);
},
updateLayout: function updateLayout(element, viewState) { }
});
})();

And the view:

 

<div id="mainSection" class="actorDetail fragment">
...
<section aria-label="Main content" role="main">
<iframe id="actorsPage" data-win-bind="src:pageToShow" style="width: 100%;height: 100%" />
</section>
</div>

Data are passed between pages as the second parameter of the WinJS.Navigation.navigate("/html/actorDetail.html", item);. They can be retrieved from the navigated page through the options parameter of the ready function.

The “actorDetail” page uses an iframe to display the webpage. Because the page has ActiveX controls when you run the project from within VS you will be getting the error “JavaScript runtime error: Access is denied”.If you look at the Javascript Console you will see that the error is “Cannot load the ActiveX plug-in that has (…)” and that “Apps can't load ActiveX controls”. But if you compile and deploy in “Release” go to Windows8 start page and run the app this will not appear. This does not mean that the error disappears. It just means that in release mode the ActiveX will just don’t load which is fine with us.

 

So in this post we have seen an approach on how to create ViewModels in a single navigation application in Windows8.

Posted: Παρασκευή, 23 Μαρτίου 2012 6:43 πμ από iwannis | 2 σχόλια
Δημοσίευση στην κατηγορία: , ,
(Windows8) WinJS Basic Javascript Objects and the ViewModel pattern

When someone starts implementing small programs in Visual Studio 2011 and starts playing with the VS2011 templates for Metro applications in Javascript, he soon wonders about the differences that WinJS brings in Javascript object creation and what is the best practice of connecting the view (HTML) with the model (Javascript). he second relates to the functionality that WinJS brings and is similar to other data binding libraries like for example Knockoutjs.In this blog we will see a general overview of the object and ViewModel creation patterns using only WinJS via the implementation of a simple “Metro Style” application.

 

Please note that due to the lack of complete documentation for the WinJS library and its workings, some of the statements below are my assumptions and may not reflect 100% the reality. Of course every single line of code below is tested and works but bear in mind that there may be a better way of doing this and I will definitely keep researching for it.

 

How do I try the examples in this post?

If you do not have Windows8 – VS 2011 installed go ahead and download the Consumer Preview from here, then follow the steps given either here for running Windows8 on a VM or here (amazing and totally recommended) for running Windows8 on a virtual partition but directly on your “metal”. Then you may install VS 2011 Express which comes along with Expression Blend from the files downloaded from here.

 

How do I define classes with properties and fields in WinJS?

Properties are object variables that have setters and getters. Fields are object variables that do not have getters and setter. It is also preferable to define classes within namespaces to avoid naming collisions with other variables of other libraries. Let us define within the namespace “Progware”, a class named “DemoClass” with two public fields “string1”, “string2”, a public property “number” and a private field “_number”. We will be using WinJS.Namespace.define for the namespace definition and WinJS.Class.define for the class definition. To implement this class with its namespace, within a file named “DemoClass.js” we define a self executing function as follows:

 

(function () {
"use strict";

WinJS.Namespace.define("Progware", {
DemoClass: WinJS.Class.define(
function (string1, string2,number) {


this.string1 = string1;
this.string2 = string2;
this.number = number;
}
, {

string1: "",
string2: undefined,


number: {
set: function (value) {
if (isNaN(value) || value < 0)
throw new Error("Invalid number");
this._number = value;
},
get: function () {
return this._number;
}
},
incrementCool: function () {
this._privateIncrementCool();
},
getCoolValue: function () {
return Progware.DemoClass.staticPropertyNumber;
},


_privateIncrementCool:function(){
Progware.DemoClass.staticPropertyNumber++;
}

},


{
staticPropertyNumber: 1
}),
namespaceNumberVariable: 55
});
})();

WinJS.Namespace.define requires two parameters: The namespace name (Progware) and a list of the namespace’s classes, methods and/or variables. In this namespace a “namespaceNumberVariable” variable is declared (ie a global variable that can be referenced from anywhere in your code using the namepsace’s prefix – “Progware.namespaceNumberVariable”) and also a class is declared using the WinJS.Class.define method.

 

The WinJS.Class.define method requires 3 parameters: a function to act as the constructor of the class, a list of instance members of the class (those are the normal properties, fields, methods of each object of this class ) and a list of static members which are the equivalent of the properties, fields and methods declared as static in .NET and can be referenced from everywhere with the prefix of the namespace and the name of the class (in this example “staticPropertyNumber” is a static field and can be referenced from everywhere in the application using the “Progware.DemoClass.staticPropertyNumber” name).

 

The constructor of the class initializes the properties and fields. Note here that just the process of initializing a property on the constructor adds it to the list of properties of the class without having to declare it later. Those are the merits (and fallacies) of the loosely typed world! It is generally though good practice to declare them also in the appropriate place (second parameter) for “code consistency”.  Also note the number property and the way its setter and getter is defined. They both use a “private” field “_number” that is implicitly defined just by its use.  Of course there is no such thing as privates in Javascript but in our case just prefixing a field with the underscore “_” makes the field not enumerable and it also hides it from VS2011 intellisense (this is the IDE actually enforces a missing feature of the language which is also pretty handy). The same applies for the private method “_privateIncrementCool”. Also note how the classe’s methods manipulate the static field “staticPropertyNumber” and the naming convention they use to reference it.

 

In general, in the code above you have a full blown namespace with its global variables and its classes (with public, private properties, fields and methods). This I dare to say is the equivalent .cs file we would write for a class in C# apart from the namespace variables which cannot exist in .NET.

 

How do I create a ViewModel following the MVVM pattern in WinJS?

Now suppose you want to obey the MVVM principle in WinJS. That is you want to create a JavaScript class that implements all the view’s  functionality and data and bind that class to the view (that is the html page that displays the data and reacts to user events). The requirements for our simple example are as follows: We need to create a “DemoClass” object and display its contents. When a user click a button named “Add Prefix” we want to prefix the current value of the “string1” property of the class with the letter “A”. Here is a snapshot of the result:

 

image

 

Create a new Javascript/Blank Application project in VS 2011. You get a blank application with some primitive structure. In the “js” folder create a “DemoClass.js” file with the code mentioned above. This file defines your “DemoClass” class. Now go to your “default.html” page and write your DIVs for displaying the data and also implement a BUTTON so that your view reflects the image above.

 

Where do I put the JavaScript code to display the “DemoClass” object data to the view?

The first answer is everywhere you like! You can use the “default.html” itself if you like. It is pure plain JavaScript and the HTML world! Of course you need to make sure that the DOM is ready using well-known techniques. There is though a better suited space in Metro style application you can put your code where you are sure that your DOM will be ready. In the “default.js” file created for you you have the “app.onactivated” event that will be called when your app initializes. At that place the initial if checks to see whether your application is activated from a “terminated” state, that is it was not running before (more of this in a future post). Within that you can be sure that the DOM is ready and the “DemoClass" has been initialized. You first approach to display the data can be something like the following:

 

var class1 = new Progware.DemoClass("STRING1", "STRING2", 14);
document.querySelector(".string1Direct").innerText = class1.string1;
document.querySelector(".string2Direct").innerText = class1.string2;
document.getElementsByClassName("numberDirect")[0].innerText = class1.number;

 

That is, you create your “class1” object and then you either use WinJS’s querySelector (a structure that resembles the way you would query for DOM elements using JQuery) or the more traditional getElementsByClassName or getElementById to assign to the appropriate DOM elements the “class1”’s values.

 

All is good with that but you need to know that with this approach you make your code aware of the view (it has to know the id and class names of the HTML Page) which violates the separation we are trying to achieve with the MVVM pattern. The second approach described below solves this issue and brings you closer to the ViewModel pattern.

 

You initialize again the “class1” object but this time your view has the “data-win-bind” properties pointing the properties of the “class1” object:

 

<div id="boundDiv" style="margin-bottom:10px">
string1: <span data-win-bind="innerText: string1" style="color:green"></span><br/>
string2: <span data-win-bind="innerText: string2" style="color:green"></span><br />
number: <span data-win-bind="innerText: number" style="color:green"></span><br />
</div>

Now in the same place as before (the app.onactivated event) we initialize our object and then call WinJS.Binding.processAllfor the binding:

 

var class1 = new Progware.DemoClass("STRING1", "STRING2", 14);
var boundDiv = document.getElementById("boundDiv");
WinJS.Binding.processAll(boundDiv, class1);

 

Note that we specify the root element for our binding. All children of this element “see” the same data to which they bind (in our case class1). All is great apart from the  fact that if we change in code any of the values of string1,string2 or number, this change will not be reflected to the UI. To be able to achieve this we need o make a little change to the class1 we bind as follows:

 

var class1VM = WinJS.Binding.as(class1);
WinJS.Binding.processAll(boundDiv2Way, class1VM);

Thus we use WinJS.Binding.as to get a new “observable” class named “class1VM” and the new class is the one used for the binding. Note that this new class is just a wrapper. Changes to the properties of this class will affect the properties of “class1”. The opposite does not hold.

 

The only thing left to do is provide the functionality for the button. Again a way to go is to bind something to the click event of the button element as follows:

 

document.getElementById("prefixString1NoBinding").onclick = function () {
class1VM.string1 = "A" + class1VM.string1;
}
 
This will both change the value of “class1.string1” and reflect the change in the UI. But again to use the ViewModel pattern we need a way to bind the “click event” of the button to a method in the ViewModel. Actually why won’t we implement a general ViewModel class that will has this method but it will also have the “class1” for the data:
 
(function () {
"use strict";
WinJS.Namespace.define("Progware", {
ViewModel: WinJS.Class.define(
function () {
this.class1 = new Progware.DemoClass("", "", 0);
this.onAddPrefix = this.addPrefix.bind(this);
},
{
class1: undefined,

addPrefix: function (eventObject) {
this.class1.string1 = "A" + this.class1.string1;
}
}, {}
)
});
})();

The HTML now will look like this:
 

<div id="boundDivVM" style="margin-bottom:10px">
string1: <span data-win-bind="innerText: class1.string1" style="color:green"></span><br/>
string2: <span data-win-bind="innerText: class1.string2" style="color:green"></span><br />
number: <span data-win-bind="innerText: class1.number" style="color:green"></span><br />
    <button id="prefixString1BindingVM" data-win-bind="onclick:onAddPrefix" /><br/>

</div>

 
We can actually now initialize the ViewModel in app.onactivated handler and bind it to the UI:
 
var defaultVM=new Progware.ViewModel();
defaultVM.class1 = WinJS.Binding.as(class1);
var javascriptViewModelDiv = document.getElementById("boundDivVM");
WinJS.Binding.processAll(javascriptViewModelDiv, defaultVM);

But wait a second!
 
Why the data-win-bind (onclick) refers to the onAddPrefix method and not on the addPrefix one? And what the hell is this.addPrefix.bind(this)?
 
Well, those of you who are in Javascript for some time you will know that having the handler directly reference the addPrefix method will make the “this” keyword used in it to refer to the whole window and not the object itself. The this.<function>.bind(this) sets the correct reference for the “this” keyword  order to be used from within the function and this is exactly what we need to do here (quite easy and actually really helpful). So instead of binding to “addPrefix” we bind to the method returned by “this.addPrefinx.bind(this)”.
 
This was a first approach to the ViewModel pattern in WinJS. If you need the source code please feel free to write me a message.
Posted: Κυριακή, 11 Μαρτίου 2012 4:03 μμ από iwannis | 1 σχόλια
Δημοσίευση στην κατηγορία: ,