Я предполагаю, что эта статья будет интересна тем, кто знает и умеет HTML&JavaScript, но не пробовал силы в разработке приложений для Win8. Для того, чтобы пройти эту статью и кодить в сласть необходимо иметь на борту VS 2013.
В статье будут рассмотрены ключевые аспекты разработки приложений для платформы Win 8.1. А именно: создание своих источников данных, темплейтов, контролов используя WinJS.
Для того, чтоб раскрыть все обозначенные темы я создал приложение, которое будет отображать список контактов.
Если вы уже смотрите исходники, то обратите внимание, что я немного изменил 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) {
Я сторонник приложений на одной «странице». WinJS предлагает богатый набор возможностей для реализации современных сценариев взаимодействия с пользователем.
Web aka WinJS приложение нуждается в отдельном объекте для обслуживания истории переходов, по страницам, обслуживания жизненного цикла страницы.
Т.е. каждую страницу при переходе на неё нам необходимо будет рендерить в её элемент, обязательно избавляясь от предыдущей, а именно убирая слушателей событий, открытые ресурсы и т.д.
Как должен выглядеть жизненный цикл страницы:
Применение ресурсов, байдингов (processed),
Инициализация (ready), срабатывает тогда, когда готовы все контролы на странице и применены ресурсы, здесь можно:
Подписаться на события контролов страницы, в том числе AppBar,
Сделать какую-то работу, например первый поиск,
Обработка обновления страницы, например, при изменении размеров,
Выгрузка страницы (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.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;
Путь к наиболее подходящему языку будет автоматически подхвачен при запуске. Любопытно, что можно встроить в ресурсы байдинг.[3]
Так же интересно, что для ресурсов используется специальный тип контента в jsproj
Т.е. необходимо создать ресурсный файл пользуясь интерфейсом VS, совсем нельзя переименовать существующий файл, напрмиер, txt в resjson, он будет в jsproj:
Это разметка для отображения пользователя в списке. Специальным атрибутом (data-win-bind) указываются привязка к тому или иному свойству элемента, а также выражение для доступа к данным.
А для того чтоб произвести некоторые преобразования, например, для того, чтоб показать фотографию клиента можно указать конвертер:
src: this HabraWin.Converters.clientPhoto
Код для обслуживания событий и элементов управления формы
; (function (winJs) {
'use strict';
var searchForm = winJs.Class.derive(HabraWin.BaseForm, winJs.Utilities.defaultControlConstructor(), {
init: function (element, options) {
var me = this;
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;
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 () {
}
});
Если вы пользовались 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 для постраничной загрузки пользователей
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);
};
В Win8 есть замечательная возможность для приложений, которые пользователь добавил себе на стартовую панель, показывать наиболее ценную информацию в тот или иной момент.
В примере ниже я использую темплейт TileWideSmallImageAndText03, все возможные варианты темплейтов можно увидеть на msdn[4]
Пример кода для обновления 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);
[3] Любопытно, что можно встроить в ресурсы байдинг.: http://code.msdn.microsoft.com/windowsapps/Application-resources-and-cd0c6eaa/sourcecode?fileId=43559&pathId=1789706597
[4] все возможные варианты темплейтов можно увидеть на msdn: http://msdn.microsoft.com/library/windows/apps/windows.ui.notifications.tiletemplatetype