Win 8.1 App использование HTML & WinJS

в 11:12, , рубрики: javascript, win8, метки: ,

Я предполагаю, что эта статья будет интересна тем, кто знает и умеет HTML&JavaScript, но не пробовал силы в разработке приложений для Win8. Для того, чтобы пройти эту статью и кодить в сласть необходимо иметь на борту VS 2013.

В статье будут рассмотрены ключевые аспекты разработки приложений для платформы Win 8.1. А именно: создание своих источников данных, темплейтов, контролов используя WinJS.

Что будет рассмотрено:

  1. Жизненный цикл работы приложения;
  2. Promise;
  3. Работа с DataSource;
  4. Создание собственных контролов;
  5. Работа с темплейтами;
  6. Tile-ы;
  7. Share;

Для тех кто не любит читать, как я, например, исходники я выложил на github.com/Sigura/HubraWin,

Для того, чтоб раскрыть все обозначенные темы я создал приложение, которое будет отображать список контактов.

Если вы уже смотрите исходники, то обратите внимание, что я немного изменил default.js, для того чтоб там не было общего кода по запуску приложения и вынес его в app.js. Оставив в default.js только настройки и непосредственно запуск. Так же я дополнил WinJs.Utilities скромным набором «удобств» и шиной сообщений.

Работа с объектами

В пространстве WinJS есть специальный набор способов создать класс, добавить ему методов, расширить и сделать доступным.

например, объявление класса — шины сообщений:

// делаем замыкание, так же для того чтоб описать зависимости
// и иметь возможность подменить их, если когда-нибудь мы захотим использовать
// этот код в другом приложении
(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

Фактически это web приложение у которого есть свой хостинг (WWAHost.exe). Свой framework для работы с ресурсами OS и приложения в пространстве имён WinJS, Application, Windows, … И набор контролов в WinJS.UI.
Я сделал «свой» класс для приложения, для того чтоб использовать его в других проектах. По мимо стандартного набора настроек этот класс создаёт события для обработки запуска (activated с информацией о запуске), прекращения работы и прочего (oncheckpoint, before-start, after-start).

класс приложения
; (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);

Тогда сам запуск приложения (default.js) будет выглядеть так:


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

Навигация по страницам

Я сторонник приложений на одной «странице». WinJS предлагает богатый набор возможностей для реализации современных сценариев взаимодействия с пользователем.
Web aka WinJS приложение нуждается в отдельном объекте для обслуживания истории переходов, по страницам, обслуживания жизненного цикла страницы.
Т.е. каждую страницу при переходе на неё нам необходимо будет рендерить в её элемент, обязательно избавляясь от предыдущей, а именно убирая слушателей событий, открытые ресурсы и т.д.
Как должен выглядеть жизненный цикл страницы:

  • Применение ресурсов, байдингов (processed),
  • Инициализация (ready), срабатывает тогда, когда готовы все контролы на странице и применены ресурсы, здесь можно:
    1. Подписаться на события контролов страницы, в том числе AppBar,
    2. Сделать какую-то работу, например первый поиск,
  • Обработка обновления страницы, например, при изменении размеров,
  • Выгрузка страницы (unload) для очистки всех обработчиков событий, и прочих ресурсов.

Контрол для обслуживания навигации

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

Код страницы
(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);

Локализация

Если сделать файл stringsru-RUresources.resjson


{
	"pageHeader": "Habra WinJS 8.1"
// …
}

то в коде пользоваться ссылками:

<span class="pagetitle" data-win-res="{ textContent: 'pageHeader' }"></span>

Путь к наиболее подходящему языку будет автоматически подхвачен при запуске.
Любопытно, что можно встроить в ресурсы байдинг.
Так же интересно, что для ресурсов используется специальный тип контента в jsproj

<PRIResource Include="stringsru-RUresources.resjson" />

Т.е. необходимо создать ресурсный файл пользуясь интерфейсом VS, совсем нельзя переименовать существующий файл, напрмиер, txt в resjson, он будет в jsproj:

<Content Include=" stringsru-RUresources.resjson" />

что сделает невозможным использование т.к. он не будет подгружаться автоматически.

Темплейты, байдинг

Пример темплейта:

<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>

Это разметка для отображения пользователя в списке. Специальным атрибутом (data-win-bind) указываются привязка к тому или иному свойству элемента, а также выражение для доступа к данным.
А для того чтоб произвести некоторые преобразования, например, для того, чтоб показать фотографию клиента можно указать конвертер:
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);

Для того чтоб применить к темплейту данные достаточно:


WinJS.Binding.processAll(element, data);

Контролы

Создание контрола в WinJS очень похоже на создание класса. Например, форма 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>

Код для обслуживания событий и элементов управления формы

; (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 на примере Share

Если вы пользовались api для асинхронных вызовов, например, XmlHttpRequest и вам надо было выполнить цепочку зависимых друг от друга вызовов, то вы обращали внимание на то что такую цепочку вызовов сложно поддерживать, т.е. читать и изменять в первую очередь из-за вложенности. Я знаю два паттерна, которые могут избавить от вложенности: события или promise.

Например, объединение последовательных вызовов:


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

                });
        },

Доступ к данным — DataSource

Для визуализации данных можно использовать WinJs.UI.ListView. Например, этот замечательный контрол умеет загружать данные не все сразу, а по мере необходимости отображать. Что бережет ресурсы при отображении более сотни записей. Но для этого необходимо реализовать свой DataSource с поддержкой загрузки данных постранично.

Пример кода DataSource для постраничной загрузки пользователей

; (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

В Win8 есть замечательная возможность для приложений, которые пользователь добавил себе на стартовую панель, показывать наиболее ценную информацию в тот или иной момент.
В примере ниже я использую темплейт TileWideSmallImageAndText03, все возможные варианты темплейтов можно увидеть на msdn
Пример кода для обновления tile-ов:


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

Автор: Sigura

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js