|
Будьте добры сообщите какой срок проверки заданий и каким способом я буду оповещен! |
Работа в офф-лайне и файл настроек
HTML + JS: Практическое занятие №3
Файлы и работа offline
Задание: продолжая проект, полученный в результате выполнения второго практического задания, вынесите список источников новостей в отдельный и добавьте кеширование данных и вывод информации о наличии интернет-соединения в приложении.
- В качестве подтверждения выполнения лабораторной работы от вас потребуется предоставить скриншот первой страницы приложения, отображающей данные при отсутствии интернет-соединения.
ЗАМЕЧАНИЕ: напоминаем, что ваше приложение должно быть уникальным:
- Использовать в качестве источников данных источники, отличные от тех, которые приводятся в инструкциям к практическим занятиям
- Внешний вид приложения должен соответствовать выбранной вами тематике приложения
Настройки и файлы
В первом практическом упражнении мы научились подключать к нашему приложению внешние источники данных, используя для этого RSS-потоки в качестве источника и XMLHTTPRequest (в обертке WinJS.xhr) для запроса данных.
Если вы вернетесь к написанному коду (data.js), вы увидите, что список источников со всеми необходимыми атрибутами прописан непосредственно в приложении.
var blogs = [
{
key: "MyBlogKey",
url: "http://myblog.com/rss",
title: 'My Blog Title, rsstitle: 'tbd', updated: 'tbd',
dataPromise: null
},
…
]Во многих сценариях это может быть неудобно и непрактично. Фактически, в текущем варианте данные смешаны с кодом и, что еще более важно, эти данные неудобно обновлять. Например, легко можно представить ситуацию, когда вы хотите добавить в список еще один источник или заменить ссылку на какой-либо из источников, причем сделать это необходимо удаленно, без повторного размещения приложения в Windows Store. Одним из шагов к решению такой задачи будет вынесение списка источников в отдельный файл, который потом можно будет, например, синхронизировать с облачным хранилищем или вашим веб-сервером.
Давайте попробуем вынести эти настройки в отдельный файл внутри нашего проекта. Первым делом, необходимо определиться с форматом данных. Для JavaScript одним из наиболее подходящих форматов является JSON – его легко парсить средствами самого JavaScript, более того, он в точности соответствует нашему описанию списка блогов.
Создайте внутри проекта новую папку data и внутри новый пустой файл, дав ему соответствующее название, например, "sources.js" (так как формат описания данных повторяет формат описания в самом языке JavaSсript, то расширение .js позволяет автоматически использовать подсветку синтаксиса из VS, но можно дать и другое расширение, например, .json).
Чтобы составить описание в виде JSON, который будет стабильно распознаваться при парсинге, лучше всего поручить это какому-либо автоматизированному средству. Например, можно вызвать JavaScript-консоль внутри VS во время выполнения проекта (Ctrl+Alt+V, C), либо открыть инструменты разработчика в любом современном браузере (обычно F12) и переключиться там на JavaScript-консоль.
После чего, достаточно вызвать функцию JSON.stringify(…), передав в нее в качестве параметра значение blogs. Например, в IE10 это выглядит так:
Сначала объявить переменную blogs
Далее в консоли вызвать функцию JSON.stringify:
В результате работы функция выдаст тест в правильном формате (внутри кавычек):
[{"key":"BBlogging","url":"http://blogs.windows.com/windows/b/bloggingwindows/rss.aspx",
"title":"Blogging Windows","rsstitle":"tbd","updated":"tbd","dataPromise":null},
{"key":"AExperience","url":"http://blogs.windows.com/windows/b/windowsexperience/rss.aspx",
"title":"Windows Experience","rsstitle":"tbd","updated":"tbd","dataPromise":null},
{"key":"CExtreme","url":"http://blogs.windows.com/windows/b/extremewindows/rss.aspx",
"title":"Extreme Windows","rsstitle":"tbd","updated":"tbd","dataPromise":null}] Такой текст и нужно вставить в наш файл описания источников данных. Как видите, в целом он повторяет описание коллекции данных непосредственно в JavaScript.
Замечание: не забудьте заменить этот текст-описание блогов на ваш собственный, отвечающий вашим предпочтениям.
Замечание: будьте внимательны – функция JSON.parse, которую мы будем использовать ниже, чувствительна к кодировке и связанными с ней переносам строки.
Вернитесь в файл data.js и удалите исходное описание списка блогов:
var blogs = […];
Перейдите к функции getBlogPosts, в которой мы использовали этот список для загрузки соответствующих rss-потоков:
function getBlogPosts(postsList) {
blogs.forEach(function (feed) {
…
});
return postsList;
}Как видите, здесь мы проходимся по списку блогов внутри переменной blogs, которую мы только что удалили. Нам необходимо получить список блогов из файла и подставить его вместо переменной blogs.
Для этого создайте рядом новую функцию getBlogsList, которая будет считывать локальный файл sources.js, обрабатывать его и передавать дальше список блогов:
function getBlogsList() {
var sourcesFile = "data/" + "sources.js";
var sourceUri = new Windows.Foundation.Uri("ms-appx:///" + sourcesFile);
var listPromise = Windows.Storage.StorageFile.getFileFromApplicationUriAsync(sourceUri).then(function (file) {
return Windows.Storage.FileIO.readTextAsync(file, Windows.Storage.Streams.UnicodeEncoding.utf8);
}).then(function (text) {
return JSON.parse(text);
});
return listPromise;
}Обратите внимание на два важных момента:
- Адресацию локальных файлов проекта с использованием протокола ms-appx
- Постоянное использование асинхронных операций (через Promise) для получения файла и чтения данных из него.
Функция getBlogsList возвращает новый Promise-объект, которые после выполнения вернет JavaScript-объект описания данных.
Замечание: попробуйте также добавить обработчик ошибки при чтении файла на случай, если у вас будет ошибка в формате JSON-файла.
Замечание: помимо использования системных API для чтения файлов, в принципе, мы также могли воспользоваться XMLHTTPRequest (WinJS.xhr) для считывания данных.
Еще раз вернитесь к функции getBlogPosts, теперь надо внутри вызвать функцию getBlogsList и после возвращения результата проделать все остальные операции. Обновленная функция должна выглядеть следующим образом:
function getBlogPosts(postsList) {
getBlogsList().then(function (list) {
list.forEach(function (feed) {
// Создание Promise
…
});
});
return postsList;
}Обратите внимание, что фактически весь внутренний код мы обернули функцией then, доступной из Promise, который в свою очередь возвращает функция getBlogsList.
Попробуйте запустить проект и убедитесь, что он работает также, как и раньше.
В будущем вы можете синхронизировать эти настройки с внешним сервисом, например, расположенном с облаке.
Теперь давайте научимся сохранять полученные с сервера данные локально и использовать их как кеш.
Сохранение и кеширование
Для сохранения данных в локальном хранилище, Windows предоставляет специальное API, доступное через объект Windows.Storage.ApplicationData. Подобное хранилище предоставляется для каждого приложения.
В нашем приложении мы будем реализовывать следующее поведение:
- Если приложение подключено к интернету, оно загружает данные и сохраняет их локально.
- Если подключения к интернету нет, приложение пытается использовать локальный кеш, если он есть.
- Если локального кеша нет, выводится сообщение о необходимости подключения.
Давайте разбираться по порядку. И начнем с сохранения локальных данных. Для этого снова вернемся к функции getBlogPosts, в которой мы загружаем данные.
Найдите код обработки результата запроса к внешнему сервису:
function (response) {
if (response) {
var syndicationXML = response.responseXML || (new DOMParser()).parseFromString(response.responseText, "text/xml");
processRSSFeed(syndicationXML, feed, postsList);
}
},Этот код вызывается в цепочке событий и, чтобы мы могли ее продолжить для локального сохранения файлов, изнутри функции должен возвращаться результат. Давайте "наружу" передадим текстовый ответ, который мы постараемся сохранить:
function (response) {
if (response) {
var syndicationXML = response.responseXML || (new DOMParser()).parseFromString(response.responseText, "text/xml");
processRSSFeed(syndicationXML, feed, postsList);
return response.responseText;
}
},Теперь добавьте в конец цепочки Promise еще одну функцию:
feed.dataPromise = WinJS.xhr({ url: feed.url }).then(
…
).then(function(text) {
});Внутри добавьте следующий код для сохранения локального кеша (мы записываем в настройки дату последнего сохранения и в файл сам текст):
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
var localFolder = Windows.Storage.ApplicationData.current.localFolder;
if (text) {
localSettings.values[feed.key] = Date.now.toString();
localFolder.createFileAsync(feed.key + ".rss", Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file){
Windows.Storage.FileIO.writeTextAsync(file, text);
});
}Здесь мы записываем в настройки для каждого источника дату сохранения (на самом деле, нам достаточно записать любой параметр, по которому мы сможем понять, что у нас есть локальный кеш) и далее сохраняем полученный текст в локальный файл.
Теперь давайте перейдем ко второй задаче, если нет интернета, будем пытаться отобразить локальную копию данных. Для этого найдите код, в котором мы проверяем наличие интернет-соединения:
function tryUpdateData() {
if (isInternetConnection()) {
getBlogPosts(list);
} else {
showConnectionError("Please check your internet connection. ");
}
};Давайте добавим дополнительную логику:
function tryUpdateData() {
if (isInternetConnection()) {
getBlogPosts(list);
} else {
getLocalBlogPosts(list);
}
}; Здесь мы ссылаемся на некоторую локальную функцию getLocalBlogPosts, которую еще необходимо написать. Эта функция похожа на обычную функцию загрузки постов с той лишь разницей, что данные мы будем брать локально:
function getLocalBlogPosts(postsList) {
getBlogsList().then(function (list) {
// Локальные настройки
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
// Локальная папка с файлами
var localFolder = Windows.Storage.ApplicationData.current.localFolder;
list.forEach(function (feed) {
if (localSettings.values[feed.key]) {
// Доступ к файлу
localFolder.getFileAsync(feed.key + ".rss").then(function (file) {
// Чтение файла
return Windows.Storage.FileIO.readTextAsync(file);
}).then(function (responseText) {
// Обработка результата
var syndicationXML = (new DOMParser()).parseFromString(responseText, "text/xml");
processRSSFeed(syndicationXML, feed, postsList);
});
}
});
});
}Внутри мы сначала проверяем через настройки, сохраняли ли мы файл с нужным именем (в целом это быстрее, чем проверить наличие файла). Если файл сохраняли, мы пытаемся получить к нему доступ и далее считываем информацию в текстовую переменную, после чего пытаемся обработать точно также, как мы это делали в случае удаленного запроса.
Наконец, осталось обработать ситуацию, в которой, никакого локального кеша нет. Для этого мы можем завести аккумулирующую переменную hasCache, но нам также понадобится собрать все отдельные Promise обработки файлов в общую коллецию (используя join), чтобы после этого обработать результат:
function getLocalBlogPosts(postsList) {
var hasCache = false;
getBlogsList().then(function (list) {
// Локальные настройки
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
// Локальная папка с файлами
var localFolder = Windows.Storage.ApplicationData.current.localFolder;
var filePromises = [];
list.forEach(function (feed) {
if (localSettings.values[feed.key]) {
// Доступ к файлу
var filePromise = localFolder.getFileAsync(feed.key + ".rss").then(function (file) {
// Чтение файла
return Windows.Storage.FileIO.readTextAsync(file);
}).then(function (responseText) {
// Обработка результата
var syndicationXML = (new DOMParser()).parseFromString(responseText, "text/xml");
processRSSFeed(syndicationXML, feed, postsList);
hasCache = true;
return hasCache;
});
filePromises.push(filePromise);
}
});
return WinJS.Promise.join(filePromises);
}).then(function () {
if (!hasCache) showConnectionError("Please check your internet connection. ");
}, function error(e) {
showConnectionError("Please check your internet connection. ");
});
}Последний штрих. Давайте при работе offline, сообщим пользователю, что он работает в offline-режиме. Откройте файл groupedItems.html и добавьте небольшой фрагмент html-кода (aside):
<div class="fragment groupeditemspage">
<aside class="online" id="offlineNotification">offline</aside>
…
</div>Далее добавьте в сопутствующем файле groupedItems.css необходимые стили:
#offlineNotification {
background: red;
color:white;
position:absolute;
top:0;
right: 50px;
padding: 8px;
font-size: 11pt;
}
#offlineNotification.online {
display:none;
}Осталось добавить дополнительную логику. Перейдите к определению глобальной переменной Data и добавьте внизу информацию о статусе подключения:
WinJS.Namespace.define("Data", {
items: groupedItems,
groups: groupedItems.groups,
getItemReference: getItemReference,
getItemsFromGroup: getItemsFromGroup,
resolveGroupReference: resolveGroupReference,
resolveItemReference: resolveItemReference,
online: isInternetConnection()
});Наконец, откройте файл groupedItems.js и добавьте в нем переключение внешнего вида нотификации в зависимости от наличия подключения к интернету (это можно сделать внутри функции _initializeLayout):
if (Data.online) {
document.getElementById("offlineNotification").className = "online";
} else {
document.getElementById("offlineNotification").className = "";
}Отключите интернет (например, перейдя в Airplane-режим) и попробуйте запустить приложение. Если вы до этого кешировали данные, то должен отобразиться закешированный контент:
Если вы запустили приложение впервые, то должно появиться сообщение об ошибке:
Попробуйте самостоятельно:
- добавить сообщение об отсутствии интернет-соединения (при закешированном контенте) на других страницах;
- добавить обновление контента при подключении интернета (это можно отследить через событие Windows.Networking.Connectivity.NetworkInformation.onnetworkstatuschanged)




