📜 ⬆️ ⬇️

Win 8.1 App using HTML & WinJS

I assume that this article will be of interest to those who know and know HTML & JavaScript, but have not tried their hand at developing applications for Win8. In order to go through this article and code it is necessary to have VS 2013 on board.

The article will discuss the key aspects of developing applications for the Win 8.1 platform. Namely:

  1. The life cycle of the application;
  2. Promise;
  3. Work with DataSource;
  4. Creating your own controls;
  5. Working with templates;
  6. Tile s;
  7. Share;

For those who do not like to read, as I, for example, I posted the source code on github.com/Sigura/HubraWin ,

In order to reveal all the topics indicated, I created an application that will display a list of contacts.
')
If you are already viewing the source, then note that I changed default.js a bit so that there was no common code for launching the application and brought it to app.js. Leaving the default.js settings and directly launch. I also supplemented WinJs.Utilities with a modest set of "amenities" and a bus message.

Work with objects


In WinJS space, there is a special set of ways to create a class, add methods to it, extend it and make it available.

For example, the class declaration is a message bus:
//  ,        //     ,  -    //      (function (winJs) { 'use strict'; //   var bus = winJs.Class.define(winJs.Utilities.defaultConstructor(), { init: function (element, options) { var me = this; } }); //         winJs.Class.mix(bus, winJs.Utilities.eventMixin); //      winJs.Namespace.define('HabraWin', { MessageBus: bus }); })(WinJS); 


WinJS application


In fact, this is a web application that has its own hosting (WWAHost.exe). Its framework for working with OS resources and applications in the namespace WinJS, Application, Windows, ... And a set of controls in WinJS.UI.
I made "my" class for the application, in order to use it in other projects. In addition to the standard set of settings, this class creates events for launch processing (activated with information about the launch), termination of work and other things (oncheckpoint, before-start, after-start).
application class
; ( function ( winJs , habraWin ) {

var app = winJs. Class . define ( winJs. Utilities . defaultConstructor ( ) , {
init : function ( options ) {
var me = this ;
var activatedEventType = 'activated' ;
var ui = options. ui ;
var application = options. app ;
var nav = options. nav ;
var activation = options. activation ;
var sched = options. sched ;

application. addEventListener ( activatedEventType , function ( args ) {

me. dispatchEvent ( activatedEventType , {
kind : args. detail . kind ,
isReactivated : args. detail . previousExecutionState === activation. ApplicationExecutionState . terminated
parevEventDetails : args. detail
} ) ;

if ( args. detail . kind ! == activation. ActivationKind . launch )
return ;

nav. history = application. sessionState . history || { } ;
nav. history . current . initialPlaceholder = true ;

ui. disableAnimations ( ) ;
var p = ui. processAll ( ) . then ( function ( ) {
return nav. navigate ( nav. location || habraWin. navigator . home , nav. state ) ;
} ) . then ( function ( ) {
return sched. requestDrain ( sched. Priority . aboveNormal + 1 ) ;
} ) . then ( function ( ) {
ui. enableAnimations ( ) ;
} ) ;

args. setPromise ( p ) ;
} ) ;

application. oncheckpoint = function ( args ) {
me. dispatchEvent ( 'oncheckpoint' , { prevEvent : args } ) ;
application. sessionState . history = nav. history ;
} ;
} ,
start : function ( ) {
var me = this ;

me. dispatchEvent ( 'before-start' , me ) ;

me. options . app . start ( ) ;

me. dispatchEvent ( 'after-start' , me ) ;

}
} ) ;

winJs. Class . mix ( app , winJs. Utilities . eventMixin ) ;

winJs. Namespace . define ( 'Application' , {
Instance : app
} ) ;

} ) ( WinJS , HabraWin ) ;

Then the application launch itself (default.js) will look like this:
 ; (function (application, winJs, habraWin, windows, window) { 'use strict'; winJs.Binding.optimizeBindingReferences = true; //    var app = new application.Instance({ activation: windows.ApplicationModel.Activation, app: winJs.Application, nav: winJs.Navigation, sched: winJs.Utilities.Scheduler, ui: winJs.UI }); //       WinJS winJs.bus = new habraWin.MessageBus(); //   window.app = app; //  app.start(); })(Application, WinJS, HabraWin, Windows, window); 


Page navigation


I am a supporter of applications on the same "page". WinJS offers a rich set of capabilities for implementing modern user interaction scenarios.
Web aka WinJS application needs a separate object to maintain the conversion history, page by page, page life cycle maintenance.
Those. each page when moving to it we will need to render into its element, necessarily getting rid of the previous one, namely, removing event listeners, open resources, etc.
How the page life cycle should look like:

Control for navigation service
( function ( winJs , habraWin ) {
'use strict' ;

winJs. Namespace . define ( 'HabraWin' , {
PageNavigatorControl : winJs. Class . define (
function ( element , options ) {
var nav = winJs. Navigation ;

this ._element = element || document. createElement ( 'div' ) ;
this ._element. appendChild ( this ._createPageElement ( ) ) ;

this . home = options. home ;

this ._eventHandlerRemover = [ ] ;

this . addRemovableEventListener ( nav , 'navigating' , this ._navigating. bind ( this ) , false ) ;
this . addRemovableEventListener ( nav , 'navigated' , this ._navigated. bind ( this ) , false ) ;

window. onresize = this ._resized. bind ( this ) ;

habraWin. navigator = this ;
} , {
addRemovableEventListener : function ( e , eventName , handler , capture ) {
var that = this ;

e. addEventListener ( eventName , handler , capture ) ;

that._eventHandlerRemover. push ( function ( ) {
e. removeEventListener ( eventName , handler ) ;
} ) ;
} ,
home : '' ,
_element : null ,
_lastNavigationPromise : winJs. Promise . as ( )
_lastViewstate : 0 ,

pageControl : {
get : function ( ) { return this . pageElement && this . pageElement . winControl ; }
} ,

pageElement : {
get : function ( ) { return this ._element. firstElementChild ; }
} ,

dispose : function ( ) {
if ( this ._disposed ) {
return ;
}

this ._disposed = true ;
winJs. Utilities . disposeSubTree ( this ._element ) ;
for ( var i = 0 ; i < this ._eventHandlerRemover. length ; i ++ ) {
this ._eventHandlerRemover [ i ] ( ) ;
}
this ._eventHandlerRemover = null ;
} ,

_createPageElement : function ( ) {
var element = document. createElement ( 'div' ) ;
element. setAttribute ( 'dir' , window. getComputedStyle ( this ._element , null ) . direction ) ;
element. style . position = 'absolute' ;
element. style . visibility = 'hidden' ;
element. style . width = '100%' ;
element. style . height = '100%' ;
return element ;
} ,

_getAnimationElements : function ( ) {
if ( this . pageControl && this . pageControl . getAnimationElements ) {
return this . pageControl . getAnimationElements ( ) ;
}
return this . pageElement ;
} ,

_navigated : function ( ) {
this . pageElement . style . visibility = " ;
winJs. Ui . Animation . enterPage ( this ._getAnimationElements ( ) ) . done ( ) ;
} ,

_navigating : function ( args ) {
var newElement = this ._createPageElement ( ) ;
this ._element. appendChild ( newElement ) ;

this ._lastNavigationPromise. cancel ( ) ;

var me = this ;
this ._lastNavigationPromise = winJs. Promise . as ( ) . then ( function ( ) {
return winJs. Ui . Pages . render ( args. detail . location , newElement , args. detail . state ) ;
} ) . then ( function parentElement ( control ) {
var oldElement = me. pageElement ;
if ( oldElement. winControl ) {
if ( oldElement. winControl . unload ) {
oldElement. winControl . unload ( ) ;
}
oldElement. winControl . dispose ( ) ;
}
oldElement. parentNode . removeChild ( oldElement ) ;
oldElement. innerText = " ;
} ) ;

args. detail . setPromise ( this ._lastNavigationPromise ) ;
} ,

_resized : function ( args ) {
if ( this . pageControl && this . pageControl . updateLayout ) {
this . pageControl . updateLayout . call ( this . pageControl , this . pageElement ) ;
}
}
}
)
} ) ;
} ) ( WinJS , HabraWin ) ;

Page Code
( function ( winJs ) {
'use strict' ;

winJs. Ui . Pages . define ( '/pages/hub/hub.html' , {
processed : function ( element ) {
return winJs. Resources . processAll ( element ) ;
} ,
className : 'client-search-hub' ,
ready : function ( element , options ) {
this . initEnv ( ) ;

this . initAppBar ( element ) ;
this . subscribe ( element ) ;

this . setFormValues ( options ) ;

this . search ( ) ;
} ,
initAppBar : function ( element ) {
var me = this ;
me. appBar = element. querySelector ( '#appbar' ) . winControl ;

this . addRemovableEventListener ( me. appBar . getCommandById ( 'clear' ) , 'click' , function ( ) {
winJs. bus . dispatchEvent ( 'clear-command' ) ;
} , false ) ;
} ,
subscribe : function ( ) {
var me = this ;

var search = me. element . querySelector ( '#search' ) ;

search && me. addRemovableEventListener ( search , 'click' , me. search . bind ( me ) , false ) ;

me. addRemovableEventListener ( winJs. bus , 'client-selected' , function ( item ) {
me. currentClient = item. detail . data ;
//me.editButton.disabled = false;
me. appBar . sticky = true ;
me. appBar . show ( ) ;
} ) ;
me. addRemovableEventListener ( winJs. bus , 'client-unselected' , function ( item ) {
me. currentClient = null ;
//me.editButton.disabled = true;
me. appBar . hide ( ) ;
me. appBar . sticky = false ;
} ) ;
} ,
unload : function ( ) {
this . element . classList . remove ( this . className ) ;

if ( this ._disposed ) {
return ;
}

this ._disposed = true ;
winJs. Utilities . disposeSubTree ( this ._element ) ;
for ( var i = 0 ; i < this ._eventHandlerRemover. length ; ++ i ) {
this ._eventHandlerRemover [ i ] ( ) ;
}
this ._eventHandlerRemover = null ;
} ,
addRemovableEventListener : function ( e , eventName , handler , capture ) {
capture = capture ! == false ? false : true ;

e. addEventListener ( eventName , handler , capture ) ;

this ._eventHandlerRemover. push ( function ( ) {
e. removeEventListener ( eventName , handler ) ;
} ) ;
} ,
updateLayout : function ( element ) {
/// <param name = "element" domElement = "true" />

// TODO: Respond to changes in layout.

// debugger;
} ,
setFormValues : function ( clinetInfo ) {
this . searchForm = this . element . querySelector ( '# main-search-form' ) ;

this . searchForm && this . searchForm . setAttribute ( 'data-win-options' , JSON. stringify ( clinetInfo ) ) ;
this . searchForm && this . searchForm . winControl && this . searchForm . winControl . setValues ( clinetInfo ) ;
} ,
search : function ( ) {
winJs. bus . dispatchEvent ( 'search-command' ) ;
} ,
initEnv : function ( ) {
this . element . classList . add ( this . className ) ;
this ._eventHandlerRemover = [ ] ;
}
} ) ;
} ) ( WinJS ) ;

Localization


If you make the file strings \ en-RU \ resources.resjson
 { "pageHeader": "Habra WinJS 8.1" // … } 

then in the code to use the links:
 <span class="pagetitle" data-win-res="{ textContent: 'pageHeader' }"></span> 

The path to the most appropriate language will be automatically picked up at startup.
It is curious what can be built into the resources of the building.
It is also interesting that for resources a special type of content is used in jsproj
 <PRIResource Include="strings\ru-RU\resources.resjson" /> 

Those. you need to create a resource file using the VS interface, you can’t just rename the existing file, for example, txt to resjson, it will be in jsproj:
 <Content Include=" strings\ru-RU\resources.resjson" /> 

making it impossible to use because It will not load automatically.

Templates, biding


Template example:
 <div class="client-search-item-template" data-win-control="WinJS.Binding.Template" style="display: none;"> <div class="client-item"> <div class="client-info"> <div class="client-photo"><img data-win-bind="alt: name; src: this HabraWin.Converters.clientPhoto;" /></div> <div class="client-name" data-win-bind="innerText: name"></div> </div> <div class="decoration-bottom-line"></div> </div> </div> 

This is the markup to display the user in the list. A special attribute (data-win-bind) specifies a binding to one or another property of the element, as well as an expression for data access.
And in order to make some transformations, for example, in order to show a photo of a client, you can specify a converter:
src: this HabraWin.Converters.clientPhoto
 ; (function (winJs) { 'use strict'; var converters = { clientPhoto: winJs.Binding.converter(function (client) { if (!client || !client.hasPhoto) return '/images/empty-photo.png'; return converters.baseAddress + '/clients/photos/' + client.ID; }) }; winJs.Namespace.define('HabraWin', { Converters: converters }); })(WinJS); 

In order to apply data to the template it is enough:
 WinJS.Binding.processAll(element, data); 

Controls


Creating a WinJS control is very similar to creating a class. For example, the form HabraWin.ClientSearchForm:
 <form role="form" id="main-search-form" data-win-control="HabraWin.ClientSearchForm"> <div class="form-group"><label for="second-name"><span data-win-res="{textContent: 'serachFormSecondNameLabel'}"></span></label><input data-win-res="{attributes: { 'placeholder' : 'serachFormSecondNamePlaceholder' }}" spellcheck="true" type="text" name="secondName" id="second-name" /></div> <div class="main-search-form-buttons form-group"> <button type="button" name="search" class="button" id="search"><span data-win-res="{textContent: 'serachFormSearchButton'}"></span></button> <button type="reset" name="clear" class="button" id="clear"><span data-win-res="{textContent: 'serachFormClearButton'}"></span></button> </div> </form> 

Code to serve events and form controls
; ( function ( winJs ) {
'use strict' ;

var searchForm = winJs. Class . derive ( HabraWin. BaseForm , winJs. Utilities . defaultControlConstructor ( ) , {
init : function ( element , options ) {
var me = this ;

me. initProperies ( ) ;

me. clearForm ( ) ;

me. defineElements ( element ) ;
me. defineEvents ( ) ;
me. subscribe ( ) ;

me. setValues ( options ) ;
me. search ( ) ;
} ,
defineElements : function ( element ) {
var me = this ;

me. fields = {
secondName : element. querySelector ( 'input [name = secondName]' )
} ;

me. buttons = {
clear : element. querySelector ( 'button [name = clear]' ) //,
} ;


var values = this . getValues ( ) ;

this . oldValues = JSON. stringify ( values ) ;
} ,
defineEvents : function ( ) {
var me = this ;

me. buttons . clear . addEventListener ( 'click' , me. clearAndSearch . bind ( me ) ) ;
} ,
setValues : function ( values ) {
if ( ! values ) {
return ;
}
this . changedFields = [ ] ;

for ( var lbl in values )
if ( values. hasOwnProperty ( lbl ) && this . fields . hasOwnProperty ( lbl ) ) {
var field = this . fields [ lbl ] ;
var value = values [ lbl ] ;
var valPropName = field && ( 'type' in field ) && field. type === 'checkbox' ? 'checked' : ( field && 'value' in field ? 'value' : 'current' ) ;

if ( ! field ) {
continue ;
}
field [ valPropName ] = value ;
value && this . changedFields . push ( lbl ) ;
}
} ,
subscribe : function ( ) {
var me = this ;

for ( var lbl in this . fields )
if ( this . fields . hasOwnProperty ( lbl ) ) {
var field = this . fields [ lbl ] ;
var isTextField = 'value' in field ;

field. addEventListener ( isTextField ? 'keydown' : 'change' , me. fieldChanged . bind ( me ) ) ;
field. addEventListener ( isTextField ? 'keydown' : 'change' , isTextField ? me. search . bind ( me ) . defer ( 1000 ) : me. search . bind ( me ) ) ;

if ( isTextField ) {
[ 'cut' , 'paste' , 'change' ] . forEach ( function ( e ) {
field. addEventListener ( e , me. fieldChanged . bind ( me ) ) ;
} ) ;
}
}

winJs. bus . addEventListener ( 'clear-command' , me. clearAndSearch . bind ( me ) ) ;
} ,
clearAndSearch : function ( ) {
this . clearForm ( ) ;
this . search ( ) ;
} ,
addNewClient : function ( ) {
var values = this . getValues ( ) ;

winJs. Navigation . navigate ( "/pages/section/section.html" , values ) ;
} ,
getValues : function ( ) {
var me = this ;
var values = { } ;

this . changedFields . forEach ( function ( lbl ) {
values [ lbl ] = me. getValue ( lbl ) ;
} ) ;

return values ;
} ,
search : function ( ) {
var values = this . getValues ( ) ;

winJs. bus . dispatchEvent ( 'search-client' , values ) ;

this . oldValues = JSON. stringify ( values ) ;
} ,
clearForm : function ( ) {
var me = this ;

var fields = Array . prototype . slice . call ( me. element . querySelectorAll ( 'input [type = text], select' ) , 0 ) ;

fields. forEach ( function ( e ) {
e. value = '' ;
} ) ;

var current = new Date ( ) ;

me. fields && me. fields . birthday && ( me. fields . birthday . current = new Date ( current. setYear ( current. getFullYear ( ) - 24 ) ) ) ;

this . changedFields = [ ] ;
} ,
fieldLabel : function ( field ) {
return field && ( field. getAttribute ( 'name' ) || field. id ) ;
} ,
fieldChanged : function ( e ) {
var field = e && e. currentTarget ;
var lbl = this . fieldLabel ( field ) ;

if ( ! ( lbl in this . fields ) )
return ;

var value = this . getValue ( lbl ) ;

if ( ! value ) {
this . changedFields . remove ( lbl ) ;
return ;
}

if ( this . changedFields . indexOf ( lbl ) === - 1 ) {
this . changedFields . push ( lbl ) ;
}
} ,
initProperies : function ( ) {
}
} ) ;

winJs. Namespace . define ( 'HabraWin' , {
ClientSearchForm : searchForm
} ) ;

} ) ( WinJS ) ;


Promise for example Share


If you used api for asynchronous calls, for example, XmlHttpRequest, and you had to make a chain of dependent calls from each other, then you noticed that it’s difficult to support such a call chain, i.e. read and modify first of all because of nesting. I know two patterns that can get rid of nesting: events or promise.

For example, combining sequential calls:
 share: function(e) { var request = e.request; var deferral = request.getDeferral(); var offering = this.offering; var files = []; var me = this; var text = offering.description.replace(/<[^>]+>/gim, '').replace(/ [\s]+/, ' '); //   : this.fileListControl.selection.getItems() .then(function (items) { //   ,   return items.map(function (item) { var uri = new Windows.Foundation.Uri(item.data.uri); return Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri) .then(function (storageFile) { files.push(storageFile); }); }); }).then(function (promises) { //   ,      return WinJS.Promise.join(promises); }).done(function () { //            request.data.properties.title = offering.name; request.data.properties.description = text; if (files.length) request.data.setStorageItems(files); else me.articlePackage(request.data); deferral.complete(); }); }, 


Data Access - DataSource


To visualize the data, you can use WinJs.UI.ListView. For example, this wonderful control can not load data all at once, but display it as necessary. That saves resources when displaying more than a hundred records. But for this you need to implement your DataSource with support for data loading page by page.

Sample DataSource code for paginating users
; ( function ( winJs , console ) {
'use strict' ;

var clientSearchDataAdapter = winJs. Class . define ( winJs. Utilities . defaultConstructor ( ) , {
def : {
maxCount : 300 ,
maxPageSize : 50 ,
minPageSize : 50
} ,
init : function ( options ) {
this . cache = { } ;
this ._filter = null ;

this . dataSource = options. dataSource ;
} ,
condition : {
get : function ( ) {
return this ._filter ;
} ,
set : function ( value ) {

this ._filter = value ;

this . dataSource && this . dataSource . invalidateAll && this . dataSource . invalidateAll ( ) ;

return value ;
}
} ,
getQuery : function ( ) {
var me = this ;

return new HabraWin. ProxyBuilder ( 'client' ) . then ( function ( proxy ) {
return proxy. search ( me. condition ) ;
} ) ;
} ,
getCount : function ( ) {

var me = this ;
var cacheKey = JSON. stringify ( me. condition ) ;

if ( cacheKey in this . cache )
return winjs. Promise . wrap ( me. cache [ cacheKey ] . length ) ;

var query = me. getQuery ( ) ;
var i = 0 ;

return query
. then ( function ( clients ) {
me. cache [ cacheKey ] = clients. map ( function ( item ) {
return {
key : '' + ( i ++ ) ,
data : item
groupKey : item. secondName . length > 0 ? item. secondName . substring ( 0 , 1 ) . toUpperCase ( ) : '-'
} ;
} ) ;

var filtered = me. applyFilters ( { items : clients , offset : 0 , totalCount : clients. length } ) ;

return filtered. items . length ;
} ) ;
} ,
addFilter : function ( filter ) {
this . filters = this . filters || [ ] ;

this . filters . push ( filter ) ;
} ,
applyFilters : function ( result ) {

if ( ! this . filters || ! this . filters . length )
return result ;

var me = this ;

this . filters . forEach ( function ( filter ) {
result = filter ( result , me. condition ) ;
} ) ;

return result ;
} ,
itemsFromIndex : function ( requestIndex , countBefore , countAfter ) {
var me = this ;

if ( requestIndex > = me. options . maxCount ) {
return winJs. Promise . wrapError ( new winJs. ErrorFromName ( winJs. UI . FetchError . doesNotExist ) ) ;
}

var fetchSize , fetchIndex ;
if ( countBefore > countAfter ) {
countAfter = Math . min ( countAfter , 10 ) ;
var fetchBefore = Math . max ( Math . min ( countBefore , me. options . maxPageSize - ( countAfter + 1 ) ) , me. options . minPageSize - ( countAfter + 1 ) ) ;
fetchSize = fetchBefore + countAfter + 1 ;
fetchIndex = requestIndex - fetchBefore ;
} else {
countBefore = Math . min ( countBefore , 10 ) ;
var fetchAfter = Math . max ( Math . min ( countAfter , me. options . maxPageSize - ( countBefore + 1 ) ) , me. options . minPageSize - ( countBefore + 1 ) ) ;
fetchSize = countBefore + fetchAfter + 1 ;
fetchIndex = requestIndex - countBefore ;
}
var cacheKey = JSON. stringify ( me. condition ) ;
var result = function ( ) {
var cache = me. cache [ cacheKey ] ;
var items = cache. slice ( fetchIndex , fetchIndex + fetchSize ) ;
var offset = requestIndex - fetchIndex ;
var totalCount = Math . min ( cache. length , me. options . maxCount ) ;
var r = {
items : items ,
offset offset ,
totalCount : totalCount ,
} ;
var filtered = me. applyFilters ( r ) ;

return filtered ;
} ;

if ( cacheKey in me. cache ) {
return winjs. Promise . wrap ( result ( ) ) ;
}

var query = me. getQuery ( ) ;

return query
. then ( function ( items ) {

var i = 0 ;

me. cache [ cacheKey ] = items. map ( function ( item ) {
return {
key : '' + ( fetchIndex + i ++ ) ,
data : item
groupKey : item. secondName . length > 0 ? item. secondName . substring ( 0 , 1 ) . toUpperCase ( ) : '-'
} ;
} ) ;

return result ( ) ;
} ) ;
}
} ) ;

var clientsDataSource = winJs. Class . derive ( winJs. UI . VirtualizedDataSource , function ( condition ) {
var dataAdapter = new clientSearchDataAdapter ( {
dataSource : this
} ) ;

this . setCondition = function ( cond ) {
dataAdapter. condition = cond ;
} ;

this . addFilter = function ( filter ) {
dataAdapter. addFilter ( filter ) ;
} ;

this ._baseDataSourceConstructor ( dataAdapter ) ;

this . setCondition ( condition ) ;
} ) ;


winJs. Namespace . define ( 'HabraWin.DataSources' , {
ClientSearch : clientsDataSource
} ) ;

} ) ( WinJS , console ) ;


Tile


In Win8 there is a great opportunity for the applications that the user has added to the start panel to display the most valuable information at one time or another.
In the example below, I use the template TileWideSmallImageAndText03, all possible templates can be seen on msdn
Sample code for updating tiles:
 ; (function(winJs, ui, dom) { winJs.Namespace.define('HabraWin', { Tile: { //  xml  tile- wideSmallImageAndText03: function(img, text) { var tileXmlString = '<tile><visual version="1" lang="ru-RU" branding="logo">' + '<binding template="TileWideSmallImageAndText03">' + '<image id="1" src="' + img + '" alt="logo" />' + '<text id="1">' + text + '</text>' + '</binding>' + '</visual></tile>'; var tileDom = new dom.XmlDocument(); tileDom.loadXml(tileXmlString); //   xml  return new ui.Notifications.TileNotification(tileDom); }, baseUrl: '', //  tile-   updateTile: function() { var tileUpdateManager = ui.Notifications.TileUpdateManager.createTileUpdaterForApplication(); var me = this; var mesageAccepted = WinJS.Resources.getString('tileMessageAccepted').value; var mesageDenied = WinJS.Resources.getString('tileMessageDenied').value; tileUpdateManager.clear(); tileUpdateManager.enableNotificationQueue(true); [ { Creator: { ID: '30BD3259-EF01-4ebb-ACEE-5065EB2885E1', Photo: true }, Description: mesageAccepted }, { Creator: { ID: 'A2021DFE-1271-41d1-9A90-A64039A8A5E6', Photo: true }, Description: mesageDenied } ].forEach(function(comment) { var img = (comment.Creator && comment.Creator.Photo && (me.baseUrl + '/clients/photos/' + comment.Creator.ID)) || 'appx:///images/empty.png'; var text = (comment.Description) || '...'; var tile = me.wideSmallImageAndText03(img, text); tileUpdateManager.update(tile); }); } } }); })(WinJS, Windows.UI, Windows.Data.Xml.Dom); 

Source: https://habr.com/ru/post/216135/


All Articles