Эта статья продолжает серию материалов (первая часть, вторая часть), посвященных азам разработки WinRT-приложений на HTML/JS для Windows 8. В этой части мы постараемся улучшить надежность получения и качество отображения данных, а также немного поговорим о контрактах.
Напомню, что в предыдущей части мы остановились на том, что научились получать данные из внешних RSS-потоков и изменили стили отображения данных для различных состояний приложения, включая snapped-режим.
Ограничение вывода данных
Если вы попробуете еще раз проследить, как получаются и отображаются данные, вы легко заметите, что мы их никак не ограничиваем. Приложение получает на входе потоки данных из RSS, преобразовывает их во внутреннюю структуру, используемую для связывания данных, и далее проецирует их в коллекцию на экране. Фактически, мы сразу видим все-все данные прямо с первого экрана.
Отмечу, что особенностью данного решения является то, что фактически мы всегда имеем на руках всю доступную информацию. В сложном проекте с бо́льшим количеством данных, возможно, серверной логикой и наличием специальных API, скорее всего, имело бы смысл запрашивать данные по мере необходимости, подгружая сначала только заголовки. Но это уже другая история. :)
В конечном счете, в нашем случае вместо того, чтобы показывать быстро и целиком всю картину последних событий, для чего было бы достаточным выводить 6-8 последних записей, мы сразу обрушиваем на пользователя весь поток новостей. Большой набор данных также не дает возможности быстро переключаться на соседние группы — приходится слишком долго прокручивать (забегая сильно вперед, скажу, что частично эту проблему сглаживает Semantic Zoom).
Таким образом, наша первоочередная задача — ограничить поток данных на первом экране.
Для этого откройте файл pagesgroupedItemsgroupedItems.js.
В начале функции в области переменных создайте еще две:
var groupedItems;
var tilesLimit = 6;
В первой мы будем хранить отфильтрованную коллекцию. Вторая указывает, сколько максимум плиток можно выводить (в идеале, подобные параметры нужно выносить в отдельные файлы настроек или аналогичные структуры данных, описывающих параметры проекта).
Далее найдите следующую строчку:
_initializeLayout: function (listView, viewState) {
Эта функция вызывается при инициализации страницы и прописывает, какие данные необходимо вывести на экран в зависимости от используемого режима отображения. В качестве источника данных используется глобальный объект Data, описанный в файле data.js.
Добавьте в начале функции следующие строчки:
groupedItems = Data.items.createFiltered(function (item) {
return item.index < tilesLimit;
}).createGrouped(
function groupKeySelector(item) { return item.group.key; },
function groupDataSelector(item) { return item.group; }
);
Напомню, что в свойстве items объекта Data прописана коллекция данных (WinJS.Binding.List), сгруппированных по принадлежности к тем или иным RSS-потокам.
Чтобы отфильтровать данные, мы берем эту коллекцию и оставляем только элементы, индекс которых меньше нашего ограничения, после чего заново группируем коллекцию в соответствии с группами.
Замечание: в данном случае мы явно ограничиваем количество элементов шестью (tilesLimit), однако, в общем случае это неправильный подход. На большом мониторе количество плиток может оказаться меньше желаемого, к тому же могут появляться одиноко висящие плитки в конце списка. Самое правильное: динамически рассчитывать лимит в зависимости от доступного пространства.
Далее в этой же функции замените встречающиеся ниже ссылки на Data и Data.items на переменную groupedItems:
if (viewState === appViewState.snapped) {
listView.itemDataSource = <mark>groupedItems</mark>.groups.dataSource;
listView.groupDataSource = null;
listView.layout = new ui.ListLayout();
} else {
listView.itemDataSource = <mark>groupedItems</mark>.dataSource;
listView.groupDataSource = <mark>groupedItems</mark>.groups.dataSource;
listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });
}
Замечание: будьте внимательны с другими вхождениями Data на данной странице. В данном случае мы их не изменяем, так как они используются при переходе ко внутренним страницам, а выше мы всего лишь сделали фильтр по данным. Но в общем случае, если вы меняете источник данных, его, возможно, надо менять везде, либо, что правильнее, вынести отдельно слой работы с данными.
Запустите проект:
Отлично! Теперь мы сразу видим все самое свежее.
Проверка интернет-соединения
Следующий важный пункт: убедиться, что приложение корректно себя ведет при отсутствии интернет-соединения. Если вы попробуете запустить текущую версию приложения с отключенной сетью, например, включив Airplane-режим, оно должно вызвать ошибку из-за невозможности скачать нужные поток. (Возможно, они успели закешироваться, поэтому для надежности эксперимента можно подключить какие-нибудь другие потоки.)
Кроме того, если поток окажется недоступным, также возникнет проблема.
Чтобы проверить наличие интернета, откройте файл jsdata.js и добавьте в него следующую функцию:
function isInternetConnection() {
var connectionProfile = Windows.Networking.Connectivity.NetworkInformation.getInternetConnectionProfile();
return (connectionProfile != null);
}
Здесь мы обращаемся к WinRT, чтобы узнать текущее состояние сети. Это уже хорошая информация, хотя она и не дает 100% гарантии, что доступ к интернету и нужному потоку действительно есть.
Теперь давайте добавим функцию, которая будет выводить сообщение об ошибке при наличии проблем:
function showConnectionError(msg, donotexit) {
msg = (msg != undefined) ? msg : "";
var messageDialog = new Windows.UI.Popups.MessageDialog(msg, "Can not connect");
messageDialog.commands.append(new Windows.UI.Popups.UICommand("Ok", null, 1));
messageDialog.showAsync().done(function (command) {
if (!donotexit && command.id == 1) {
MSApp.terminateApp({
number: 0,
stack: "",
description: "No internet connection"
});
}
});
}
Данная функция, используя WinRT API, выводит заданное сообщение об ошибке и, по умолчанию, если не выставлен флаг donotexit (или он равен false), завершает приложение.
Замечание: в реальном приложении логика поведения может отличаться. Например, приложение может намеренно кешировать данные или предлагать пользователю попробовать еще раз скачать потоки с сервера, если была временная потеря соединения.
Следующий шаг: запустить проверку наличия соединения, для этого замените строчку
list = getBlogPosts(list);
на следующие:
function tryUpdateData() {
if (isInternetConnection()) {
list = getBlogPosts(list);
} else {
showConnectionError("Please check your internet connection. ");
}
};
tryUpdateData();
Фактически, мы обернули обращение данных в проверку наличия интернет-соединения. Если его нет, приложение выдает сообщение об ошибке и закрывается:
Теперь давайте вернемся к функции getBlogPosts. Здесь тоже есть вероятность получения ошибки, например, если какой-то RSS-поток перестал работать, в этом случае наша обертка над XHR вызовет исключение, которое нужно перехватить.
Попробуйте заменить одну из ссылок на RSS на неправильную и запустить приложение (здесь мы предполагаем, что ответ сервера будет соответствующим неправильной ссылке).
Чтобы обработать исключительную ситуацию, в описании Promise нужно добавить функцию, вызываемую при возникновении ошибки. Сразу после внутренней анонимной функции function (response) {…} добавьте через запятую:
function (error) {
showConnectionError("Can't get rss updates for " + feed.title + ". Used source: " + feed.url, true);
}
В данном случае мы выводим сообщение об ошибке, но не закрываем приложение: возможно, другие потоки обработаются нормально.
Попробуйте сделать одну из ссылок неправильной и запустить приложение:
Ура! Теперь мы можем радовать пользователя злостными сообщениями. Тут я хочу повторить еще раз важную мысль: подобное информирование пользователя — это всего лишь полумера. В идеале нужно кешировать данные и информировать пользователя о наличии проблем с соединением менее разрушительным способом. См. например, как работает приложение Bing News в Windows 8.
Делимся информацией
Напоследок в этой статье мы научимся делиться информацией из приложения наружу, используя контракты общего доступа (sharing) и печати.
Откройте файл pagesitemDetailitemDetail.js. Добавьте в начале переменную для хранения обработчика событий:
var sharingHandler;
Добавьте также в начале или ближе к концу (вне WinJS.UI.Pages.define()) следующие функции:
1. setupSharing — регистрация на передачу данных
function setupSharing(item) {
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
sharingHandler = onSharingDataRequested.bind(item);
dataTransferManager.addEventListener("datarequested", sharingHandler);
}
Здесь мы обращаемся через WinRT к менеджеру передачи данных и вешаем обработчик события запроса данных.
2. onSharingDataRequested — событие для обработки запроса
function onSharingDataRequested(e) {
var request = e.request;
var item = this;
var text = item.title;
if (item.link) {
text += "nr" + item.link;
request.data.setUri(new Windows.Foundation.Uri(item.link));
}
request.data.properties.title = text;
request.data.properties.description = "Some awesome news on Windows!";
request.data.setText(text);
}
Здесь мы описываем, какие данные мы передаем приложению-получателю: ссылку, текст и т.п. (подробности передачи данных можно посмотреть здесь: общий доступ к содержимому и его получение).
Внутри функции ready в самом конце добавьте вызов регистрации на поддержку контракта:
setupSharing(item);
Запустите приложение, перейдите к посту и через панель чудо-кнопок попробуйте передать новость в другое приложение:
Осталось добавить еще одну очень важную деталь. Если вы попробуете вернуться на уровень выше (уйти с текущей страницы) и используете контракт общего доступа (Sharing), вы увидите, что он по-прежнему «реагирует» и предлагает пользователю несколько вариантов на выбор. Это не то, что мы бы ожидали в качестве правильного поведения приложения.
Чтобы такого не происходило, необходимо сбросить обработчики событий в менеджере передачи данных. Это можно делать на входе в каждую страницу, либо наоборот на выходе. Добавьте следующую функцию:
function clearContracts() {
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
dataTransferManager.removeEventListener("datarequested", sharingHandler);
}
Здесь мы просто удаляем соответствующий обработчик событий. Для вызова функции после описания свойства ready через запятую добавьте функцию для unload:
unload: function () {
clearContracts();
}
Готово, теперь передача данных работает только там, где она и должна работать.
Печатаем статьи
Теперь давайте добавим поддержку печати. Прежде всего, надо отметить, что по умолчанию ваше приложение не умеет печатать. Для передачи документов на печать используется системный контракт печати (Print), доступный пользователям через чудо-кнопку «Устройства»:
Чтобы добавить возможность печати в том же файле pagesitemDetailitemDetail.js (это наш контекст печати), необходимо подписаться на соответствующий менеджер аналогично тому, как мы это делали для общего доступа. Добавьте переменную для хранения обработчика:
var printTaskHandler;
функцию для подписи на контракт печати:
function setupPrinting(item) {
var printManager = Windows.Graphics.Printing.PrintManager.getForCurrentView();
printTaskHandler = onPrintTaskRequested.bind(item)
printManager.addEventListener("printtaskrequested", printTaskHandler);
}
и обработчики событий:
function onPrintTaskRequested(printEvent) {
var printTask = printEvent.request.createPrintTask(this.group.title, function (args) {
args.setSource(MSApp.getHtmlPrintDocumentSource(document));
});
}
Здесь вы также можете настраивать параметры печати и подписаться на дополнительные события,
например, обработать событие окончания печати.
Внутри функции ready в самом конце добавьте вызов регистрации на поддержку контракта:
setupPrinting(item);
и не забудьте в clearContracts удалить обработчик запроса печати:
var printManager = Windows.Graphics.Printing.PrintManager.getForCurrentView();
printManager.removeEventListener("printtaskrequested", printTaskHandler);
Попробуйте запустить приложение и отправить статью на печать:
Если вы достаточно наблюдательны, вы должны сразу увидеть возникшую проблему: на печать отправлена только одна страница, причем в том виде, как выглядит страница на экране (с горизонтальной прокруткой).
Аналогично тому, как бы вы решали эту проблему для веба, здесь нам на помощь приходят media-запросы и CSS-правила для печати. (Вы также можете передать на печать специально сгенерированную страницу, фрагмент, iframe и т.п.)
Добавьте в проект новый файл pagesitemDetailprint.css, либо просто запишите приведенный ниже код в css-файл, привязанный к нужной странице (itemDetail.css). Добавьте в него следующие строчки:
@media print {
/* изменяем отступы страницы*/
.itemdetailpage.fragment {
-ms-grid-rows: 40px 1fr;
}
/* изменяем расположение заголовка страницы*/
header[role="banner"] {
-ms-grid-columns: 40px 1fr !important;
}
/* прячем кнопку "назад" */
.win-backbutton {
display:none;
}
/* изменяем отступы заголовка и шрифт страницы*/
header[role="banner"] h1.titlearea {
margin: 0px !important;
font-size: 1em;
text-transform: uppercase;
}
/* изменяем высоту контента и перехлесты для вертикальной прокрутки */
.itemdetailpage.fragment, .itemdetailpage .content, .itemdetailpage .content article {
height:auto !important;
overflow: visible !important;
}
/* изменяем отступы контента*/
.content {
padding-left: 40px;
}
/* убираем многоколоночную верстку */
.itemdetailpage .content article {
column-width: auto;
width: auto;
margin-left: 0px;
margin-top: 40px;
}
}
Запускаем приложение и пробуем отправить на печать:
На этом мы закончим текущую статью.
Проект
Готовый проект на текущей стадии можно скачать тут: http://sdrv.ms/V1InN9.
Далее
В следующей части мы продолжим разбираться с отображением и посмотрим, как использовать
более сложные шаблоны и добавить поддержку контекстного масштабирования.
Предыдущие части
Автор: kichik