Использование возможностей HTML5 для сохранения данных и оффлайновой работы: Часть 1. Реализация оффлайнового редактирования данных и синхронизации данных

Технология HTML5 меняет возможности веб-приложений и бизнес-приложений для повседневной работы. Оффлайновые функции и средства персистентного локального хранения информации позволяют предоставить пользователю одинаково богатые возможности и в онлайновом, и в оффлайновом режимах работы, что ранее было доступно только в закрытых инфраструктурах для разработки приложений для ПК. В предлагаемой статье показано, как применить предоставляемые технологией HTML5 оффлайновые возможности и функции локального сохранения. Использованное в качестве примера учебное приложение показывает, как избежать распространенных проблем.

Брайан Дж. Стюарт, главный консультант, Aqua Data Technologies, Inc.

Фото Брайана СтюартаБрайан Стюарт (Brian J. Stewart) работает главным консультантом в компании Aqua Data Technologies, которую он основал и которая занимается управлением информацией, XML-технологиями и корпоративными клиент/серверными и Web-системами. Он проектирует и разрабатывает корпоративные решения на базе платформ J2EE и .NET.



20.05.2013

Введение

Согласно существующим планам, технология HTML5 (HTML version 5) получит статус Рекомендации организации World Wide Web Consortium (W3C) не ранее 2014 года. Хотя технология HTML5 еще не является официальным стандартом, поставщики веб-браузеров уже добавляют и продвигают HTML5-функции. Технология HTML5 расширяет веб-возможности для сайтов в Интернете и для бизнес-приложений. Многие веб-сайты, такие как Amazon Kindle Cloud Reader, уже используют технологию HTML5. Следующие две ключевые функции HTML5 существенно изменят бизнес-приложения: поддержка оффлайновых приложений и локальное персистентное хранилище. Поскольку технология HTML5 не является официальным стандартом, ее поддержка различными браузерами в лучшем случае не единообразна.

В этой статье описывается поддержка оффлайновых приложений и различные возможности персистентного хранения, предлагаемые будущим стандартом HTML5. В качестве иллюстрации к рассматриваемым функциям приводится учебное приложение .


Учебное приложение

Учебное приложение Contact Manager обеспечивает управление контактной информацией (имена, адреса, номера телефонов). Оно поддерживает онлайновый режим, оффлайновый режим и простую функцию синхронизации данных, которая позволяет синхронизировать локальные изменения данных с сервером после переключения на онлайновый режим. При нахождении приложения в оффлайновом режиме данные размещаются в локальном персистентном хранилище. Описываемое приложение поддерживает четыре базовые функции персистентного хранения — создание/чтение/обновление/удаление (create/read/update/delete, CRUD) — и в онлайновом, и в оффлайновом режимах.

Архитектура

На рисунке 1 показана архитектурная схема приложения Contact Manager. Архитектура сервера состоит из двух сервлетов, которые соответствуют бизнес-сервисам и провайдерам данных. Пользовательский интерфейс состоит из одного HTML-файла, четырех JavaScript-модулей и внешней ссылки на новейшую версию библиотеки jQuery.

Рисунок 1. Архитектурный план приложения
Image showing the application architecture

Модель данных

Модель данных состоит из двух объектов данных, contact и state (см. рис. 2). Таблица contact содержит фактические контактные сведения; таблица state содержит значения словаря для списка выбора состояния.

Рисунок 2. Модель данных
Image showing the data model

Интерфейс сервера

Интерфейс сервера состоит из двух сервлетов: ContactServlet и DictionaryServlet. Эти сервлеты описаны в таблице 1 (реализация этих сервлетов, а также соответствующих бизнес-сервисов и провайдеров данных, выходит за рамки этой статьи).

Таблица 1. Описание сервлетов
Имя сервлетаОперацияПараметрыОписание
DictionaryServletcode>getstatesОтсутствуютВозвращает массив состояний в формате JSON (JavaScript Object Notation).
ContactServletgetallcontactsОтсутствуютВозвращает массив записей контактов в формате JSON.
ContactServletdeletecontactId - Идентификатор (ID) контакта, подлежащего удалению.Удаляет указанную запись контакта; возвращает JSON-объект с логическим флагом, который служит индикатором успешности операции.
"{"result": true/false"}
ContactServletsave
  • contactId - Идентификатор (ID) контакта, подлежащего сохранению (если значение больше нуля, то имеет место операция обновления).
  • firstName - Значение поля name.
  • lastName - Значение поля last name.
  • street1 - Значение поля street 1.
  • street2 - Значение поля street 2.
  • city - Значение поля city.
  • state - Значение поля state.
  • zipCode - Значение поля zipCode.
Возвращает JSON-объект с логическим флагом, который служит индикатором успешности операции, и новый или обновленный идентификатор контакта.
"{"contactId": <id>, "result": <true/false>"}

Вызов интерфейса сервера

Код в листинге 1 показывает, как осуществить асинхронное обращение к сервлету contact с целью извлечения контактных сведений, хранящихся в онлайновой базе данных. Этот код использует jQuery-функцию getJSON для вызова сервлета contact.

Листинг 1. Извлечение контактных данных из сервера
function loadOnlineContacts() 
{
	$('#contactList').empty();
	$('#contactList').append('Loading contact data...');
	
	var url = '/html5app/contact?operation=getallcontacts';
	
	$.getJSON(url, function(data) {
		saveOfflineContactData(data);
		displayContactData(data);		
	});
}

Код в листинге 2 показывает, как сохранить новый или обновленный контакт на сервере. Код использует jQuery-функцию ajax. Код отправляет данные в сервлет contact с помощью HTTP-запроса POST.

Листинг 2. Сохранение контактных данных на сервере
function postEditedContact(dataString) {
	postEditedContact(dataString, false);	
}

function postEditedContact(dataString, suppressAlert) {
var contactId = $('input[name="contactId"]')[0].value;

$.ajax({  
type: "POST",  
url: "/html5app/contact",  
data: dataString,  
cache: false,
dataType: "json",
success: function(data) {
	var result = data.result;
	
	if (result) 
	{
		if (contactId > 0)
		{
		if (!suppressAlert) {
		alert("Contact was successfully updated.");
		}
		var lastModifyDate = data.lastModifyDate;
		$('input[name="lastModifyDate"]')[0].value = lastModifyDate;
		}
		else 
		{
		if (!suppressAlert) {
			alert("Contact was successfully created.");
		}
		
		var lastModifyDate = data.lastModifyDate;
		$('input[name="lastModifyDate"]')[0].value = lastModifyDate;
		$('input[name="contactId"]')[0].value = data.contactId;
		}
		
		loadOnlineContacts();

		hideEditForm();
	}
	else 
	{
		alert('An error occurred saving contact ' + contactId + '.');
	}
}
});  
}

Последняя функция состоит в удалении записи из онлайновой базы данных. В листинге 3 показано, как удалить запись с сервера. Чтобы вызвать сервлет contact, код использует jQuery-функцию getJSON.

Листинг 3. Удаление контактных сведений на сервере
function deleteOnlineContact(contactId, suppressAlert){
	var url = '/html5app/contact?operation=delete&contactId=' + contactId;
	
	$.getJSON(url, function(data) {
		var result = data.result;
		if (result) {
			if (!suppressAlert) {
				alert('Contact deleted');
			}
			loadOnlineContacts();
		}
		else {
			alert('Contact ' + contactId + 'not deleted');
		}
	});		
}

Создание локального провайдера данных

Локальный провайдер данных сохраняет весь список выбора и все контактные сведения на локальной системе. Спецификация HTML5 содержит несколько опций для реализации персистентного хранилища. Выбор используемой технологии зависит от требований, предъявляемых к хранилищу данных и к поддержке браузеров. В следующих разделах рассматриваются три технологии персистентного хранения, а также реализация локального провайдера данных с использованием технологии персистентного хранения, которую поддерживают все ведущие веб-браузеры.

Спецификация HTML5 включает следующие три технологии персистентного хранения.

  • localStorage - Технология localStorage обеспечивает простое хранение данных с помощью плоского хранилища типа ключ-значение. Все ведущие веб-браузеры, включая Apple® Safari®, Google Chrome™, Microsoft® Windows® Internet Explorer®, Mozilla® Firefox® и Opera™, поддерживают технологию localStorage. В спецификации HTML5 технология localStorage является синхронной; на данный момент это единственный механизм для хранения базы данных, который поддерживается на нескольких платформах и в нескольких браузерах.
  • WebSQL - Технология WebSQL первоначально была ориентирована на использование веб-браузера для работы с базой данных на основе Transact-SQL. Эта технология не требует больших усилий на обучение, поскольку она имеет большое сходство с такими реляционными СУБД, как IBM® DB2®, Microsoft SQL Server®, Oracle® MySQL® Server и Oracle Database. Технологию WebSQL поддерживают несколько браузеров, включая Safari, Chrome и Opera. Она не поддерживается браузерами Firefox и Internet Explorer. По всей видимости, эта технология постепенно выйдет из употребления, поскольку предложенная ранее спецификация WebSQL больше не развивается.
  • Indexed database (Indexed DB) - Технология IndexedDB предоставляет индексированное иерархическое хранилище типа ключ-значение, подобное многим коммерческим предложениям для облачного хранения данных. Технология WebSQL была отвергнута в пользу технологии IndexedDB и в настоящее время технология IndexedDB уже поддерживается браузерами Firefox и Chrome, а в будущем будет поддерживаться браузером Internet Explorer 10. API-интерфейс для IndexedDB является асинхронным и поддерживает индексацию, запросы и транзакции.

Включенное в эту статью учебное приложение использует технологии JSON и localStorage для персистентного хранения преимущественно по причине их широкой поддержки браузерами.

Локальный провайдер данных

Подход localStorage сохраняет контактные и словарные данные посредством их сериализации в виде строки в формате JSON и сохранения этой строки в хранилище localStorage. После извлечения данных они десериализуются в виде массива JSON-объектов и обрабатываются соответствующим образом.

Локальное сохранение данных

В листинге 4 показано, как сохранить контактные сведения в хранилище localStorage. JavaScript-функция JSON.stringify используется для сериализации JSON-данных, которые сервер возвращает в строке, после чего они могут быть сохранены в хранилище localStorage.

Листинг 4. Сохранение данных в localStorage
// fetch data from server
...
			
// convert JSON data to a string
var contactDataString = JSON.stringify(data);

// persist contact data to localstorage
localStorage.setItem("contactData", contactDataString);

Локальное извлечение данных

В листинге 5 показано, как извлечь данные из localStorage. Первый шаг состоит в получении JSON-строки из localStorage. Затем эта строка преобразуется в JSON-объекты с помощью JavaScript-функции eval, которая осуществляет десериализацию строки в массив JSON-объектов. Данные отображаются с помощью специальной JavaScript-функции displayContactData.

Листинг 5. Чтение данных из localStorage
function loadOfflineContacts() 
{
	$('#contactList').empty();
	$('#contactList').append('Loading contact data...');
	
	var dataStr = localStorage.getItem("contactData");
	var data = eval('(' + dataStr + ')');
	
	displayContactData(data);
}

Локальное удаление записи

В листинге 6 показано, как удалить запись из localStorage.

Листинг 6. Удаление записи из localStorage
function deleteOfflineContact(contactId) {
	var dataStr = localStorage.getItem("contactData");
	var data = eval('(' + dataStr + ')');
	
	var recordUpdated = false;
	$.each(data, function(i,item){
		if (item.id == contactId) {
			item.isDeleted=true;
			recordUpdated = true;
			return false;
		}	
	});
	
	if (recordUpdated) {
		dataStr = JSON.stringify(data);
		localStorage.setItem("contactData", dataStr);
		alert("Contact was successfully deleted.");
		
		loadOfflineContacts();
	}
}

Представленный код выполняет следующие действия.

  • Читает базу данных из локального хранилища и осуществляет ее десериализацию.
  • Выполняет итеративное прохождение по записям, пока не находит contactId.
  • Устанавливает флаг isDeleted в состояние true.
  • Учитывает флаг isDeleted в функции синхронизации данных (обратитесь к разделу под названием Синхронизация данных).
  • Сохраняет данные в localStorage и обновляет структуру данных.

Локальное обновление и создание записей

В листинге 7 показано, как обновить существующую запись или создать новую запись в localStorage.

Листинг 7. Обновление записи в localStorage
function updateLocalContact() {
	var dataStr = localStorage.getItem("contactData");
	var data = eval('(' + dataStr + ')');
	
	var contactId = $('input[name="contactId"]')[0].value;
	
	var recordUpdated = false;
	if (contactId > 0) {
		$.each(data, function(i,item){
			if (item.id == contactId) {
				item.isDirty=true;
				item.firstName = $('input[name="firstName"]')[0].value;
				item.lastName = $('input[name="lastName"]')[0].value;
				item.street1 = $('input[name="street1"]')[0].value;
				item.street2 = $('input[name="street2"]')[0].value;
				item.city = $('input[name="city"]')[0].value;
				item.state = $('select[name="state"]')[0].value;
				item.zipCode = $('input[name="zipCode"]')[0].value;	
				recordUpdated = true;
				return false;
			}	
		});
	}
	else {		
		var newContactId = 0;
		var nextId = 0;
		while(newContactId == 0) {
			var found = false;

			nextId = nextId - 1;
			$.each(data, function(i,item){
				if (item.id == nextId) {
					found = true;
					return false;
				}
			});
			if (!found) {
				newContactId = nextId;
			}
		}
		var lastModifyDate = "";
		var newContact = {"street2": $('input[name="street2"]')[0].value,
				"id":newContactId,
				"street1":$('input[name="street1"]')[0].value,
				"lastName":$('input[name="lastName"]')[0].value,
				"isDirty":true,
				"zipCode":$('input[name="zipCode"]')[0].value,
				"state":$('select[name="state"]')[0].value,
				"lastModifyDate": lastModifyDate,
				"isDeleted":false,
				"firstName":$('input[name="firstName"]')[0].value,
				"city":$('input[name="city"]')[0].value};
		var nextIndex = data.length;
		data[nextIndex] = newContact;
		recordUpdated=true;
	} 
	
	if (recordUpdated) {
		dataStr = JSON.stringify(data);
		localStorage.setItem("contactData", dataStr);
		alert("Contact was successfully updated.");
	}
	
	hideEditForm();
}

Показанный в листинге 7 программный код выполняет следующие действия.

  • Читает базу данных из localStorage и осуществляет ее десериализацию.
  • Если идентификатор contactId записи, подлежащей сохранению, не равен нулю (т.е. произошло обновление), код выполняет итеративное прохождение по записям, пока не найдет запись contactId. После этого код производит ее надлежащее обновление.
  • В качестве альтернативного варианта, если запись является новой (идентификатор contactId не равен нулю), код находит следующий неиспользованный отрицательный идентификатор contactId.
  • Присваивает его новой записи.
  • Добавляет новую запись в базу данных.

Затем данные сериализуются в виде JSON-строки и сохраняются в localStorage. В процессе синхронизации с сервером идентификатору contactId присваивается допустимое значение (больше нуля). Отрицательные значения имеют временные идентификаторы, используемые для идентификации новых записей.

Необходимо учитывать следующие особенности технологии localStorage.

  • Размеры хранилища ограничены величиной 5 МБ (если требуется хранилище данных большего объема, следует использовать технологию IndexedDB).
  • Данная технология поддерживается всеми ведущими веб-браузерами.
  • Технология работает только со строковыми значениями.

Следующий шаг состоит в построении пользовательского интерфейса с помощью технологии HTML5.


Построение пользовательского интерфейса с помощью технологии HTML5

Учебное приложение Contact Manager имеет простой пользовательский интерфейс с единственной страницей. Этот интерфейс поддерживает операции редактирования и удаления для каждой записи, а также предоставляет возможность создания новых записей. Технологии CSS (Cascading style sheets) и dynamic HTML (посредством jQuery) используются для того, чтобы по мере необходимости скрывать и показывать субформы для создания/редактирования.

С целью предоставления пользователю единообразных возможностей одна и та же страница используется и в онлайновом, и в оффлайновом режимах; единственная разница состоит в том, вызов какого провайдера данных осуществляется при выполнении операции. Учебное приложение показано на рис. 3.

Рисунок 3. Приложение Contact Manager
Image showing the Contact Manager application

JavaScript-модули

Приложение состоит из четырех специальных JavaScript-модулей:

  • core.js – поддерживает обычные функции JavaScript и используется другими модулями.
  • formEvents.js – предоставляет обработчиков событий для кнопок и форм. Он направляет операции базы данных к надлежащему провайдеру данных в соответствии с онлайновым или оффлайновым режимом.
  • onlinedb.js – поддерживает функции для взаимодействия с сервером при нахождении в онлайновом режиме.
  • offlinedb.js поддерживает функции для локального хранения данных.

Кроме того, все модули используют последнюю версию библиотеки jQuery для прослеживания данных, для асинхронных веб-запросов и для поддержки dynamic HTML. Клиент взаимодействует с сервером с помощью JSON.

Манифест оффлайнового приложения

Оффлайновые возможности HTML5 обеспечивают кэширование статических файлов и ресурсов. Файл манифеста оффлайнового приложения (.appcache) – это важнейший файл для поддержки оффлайнового приложения для веб-приложения. Файл манифеста содержит следующую информацию.

  • Ресурсы и страницы, доступные в оффлайновом режиме.
  • Ресурсы, доступные только в онлайновом режиме.
  • Для ресурсов, которые не доступны в оффлайновом режиме, отображается fallback-страница.

Файл манифеста состоит из трех разделов: CACHE, NETWORK, FALLBACK. Страницы и ресурсы, перечисленные в разделе CACHE, кэшируются на локальной системе. Страницы и ресурсы, перечисленные в разделе NETWORK, никогда не кэшируются и доступны только в онлайновом режиме. Страница, указанная в разделе FALLBACK, отображается, если требуемая страница недоступна в оффлайновом режиме. Символ (*) в разделе NETWORK указывает, что все остальные страницы и сервлеты доступны только в онлайновом режиме. Если символ (*) отсутствует, вызовы сервлета окажутся неудачными (даже в онлайновом режиме). В листинге 8 показан файл манифеста для приложения Contact Manager.

Листинг 8. Манифест оффлайнового приложения
CACHE MANIFEST
# Revision 1
CACHE:
default.html
list.html
scripts/core.js
scripts/localdb.js
scripts/onlinedb.js
scripts/formEvents.js
http://code.jquery.com/jquery-1.7.2.min.js
NETWORK:
*
FALLBACK:
/ offline.html

Важная информация для работы с оффлайновыми приложениями.

  • Расширение файла манифеста оффлайнового приложения .appcache должно быть отображено на MIME-тип text/cache-manifest. В среде Apache Tomcat эта задача решается посредством добавления элемента mime-mapping в файл web.xml сервера (а не в файл web.xml веб-приложения). Большинство браузеров молча игнорирует манифест оффлайнового приложения, если его MIME-тип является некорректным.
  • Если файл манифеста оффлайнового приложения присутствует, всегда используется ресурс, кэшированный на локальной системе (даже при нахождении в онлайновом режиме).
  • Локальный ресурс обновляется только в том случае, если изменяется файл манифеста оффлайнового приложения; как правило, посредством изменения номера версии в комментариях внутри файла манифеста. Изменения HTML-ресурсов или CSS-ресурсов не отражаются в веб-браузере до тех пор, пока файл манифеста приложения не будет изменен.
  • Каждая страница, поддерживающая оффлайновое использование, должна иметь следующую строку:
    <html lang="en" manifest="app.appcache">

Онлайновое или оффлайновое приложение

JavaScript позволяет обнаружить, является ли приложение онлайновым или оффлайновым. Эта задача решается с помощью логической переменной navigator.onLine. Если приложение является онлайновым, то эта переменная возвращает значение True.

События форм (обработка в онлайновом и в оффлайновом режиме)

В приложении Contact Manager одна и та же форма используется и в онлайновом, и в оффлайновом режиме. Чтобы это решение работало, необходимы обработчики событий форм и кнопок. Проверьте значение переменной navigator.onLine с целью определения того, какую операцию следует вызвать (локальную или онлайновую). В листинге 9 показан пример для загрузки контактных данных.

Листинг 9. Загрузка данных (в событии onLoad)
if (navigator.onLine) 
{	
	// selection list needs to be populated prior to synchronizing data
	// the list is updated from the online dictionary later
	populateOfflineStates(); 
	
	setStatusText("Synchronizing contact data with server...");
	synchronizeContacts();

	setStatusText("Loading dictionary data from server...");		
	populateOnlineStates();
	
	setStatusText("Loading contact data from server...");
	loadOnlineContacts();
}
else 
{
	alert('You are currently offline.');
	populateOfflineStates();
	setStatusText("Loading contact data from local storage...");
	loadOfflineContacts();
}

Синхронизация данных

В онлайновом режиме все CRUD-операции используют сервлеты для операций создания, изменения и удаления. Если изменяется онлайновая база данных, то локальный кэш также обновляется.

В оффлайновом режиме все CRUD-операции для сохранения изменений используют локального провайдера данных. После восстановления соединения с сервером производятся следующие действия.

  • Все записи, которые были созданы на локальной системе, сохраняются на сервере.
  • Все записи, которые были изменены на локальной системе, обновляются на сервере.
  • Все записи, которые были удалены на локальной системе, удаляются на сервере.

Полный текст метода синхронизации показан в листинге 10. В процессе синхронизации одни и те же онлайновые функции используются для операций создания, обновления и удаления. Первый шаг состоит в итеративном прохождении по локальным записям с помощью jQuery-функции $.each.

Записи, которые были обновлены или созданы на локальной системе, помечаются с помощью свойства isDirty. Операция Save (сохранить) идентифицируется как новая, если ее уникальный идентификатор записи имеет отрицательное значение (т.е. база данных MySQL не присвоила ему какого-либо значения). Записи, которые были удалены на локальной системе, помечаются с помощью свойства isDeleted.

Листинг 10. Синхронизация оффлайновых изменений с сервером
var recordsUpdated = 0;
var recordsCreated = 0;
var recordsDeleted = 0;

$.each(data, function(i,item){
	if (item.isDeleted) {
		deleteOnlineContact(item.id, true);
		recordsDeleted++;
	}		
	else if (item.isDirty && !item.isDeleted) {
		$('input[name="contactId"]')[0].value = item.id;
		$('input[name="firstName"]')[0].value = item.firstName;
		$('input[name="lastName"]')[0].value = item.lastName;
		$('input[name="street1"]')[0].value = item.street1;
		$('input[name="street2"]')[0].value = item.street2;
		$('input[name="city"]')[0].value = item.city;
		$('select[name="state"]')[0].value = item.state;
		$('input[name="zipCode"]')[0].value = item.zipCode;
		
		var dataString = $("#editContactForm").serialize();
		postEditedContact(dataString, true);
		if (item.id > 0) {
			recordsUpdated++;
		}
		else {
			recordsCreated++;
		}			
	}
});

var msg = "Synchronization Summary\n\tRecords Updated: " + recordsUpdated + 
"\n\tRecords Created: " + recordsCreated +"\n\tRecords Deleted: " + recordsDeleted;
alert(msg);

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


Заключение

На примере учебного приложения в статье были продемонстрированы рекомендации по поддержке онлайнового и оффлайнового режимов работы. Единообразные пользовательские возможности обеспечиваются посредством использования одной и той же HTML-страницы для онлайнового и оффлайнового режимов, а также посредством вызова надлежащих онлайновых/оффлайновых провайдеров данных в обработчиках событий форм в соответствии с текущим состоянием (онлайновым или оффлайновым).

Алгоритм синхронизации данных обеспечивает надежный фундамент; он осуществляет синхронизацию результатов оффлайнового создания, удаления и изменения записей. Следует, однако, отметить, что этот программный код еще не готов к использованию в производственной среде. Например, он не обрабатывает конфликтов, когда одна и та же запись изменена вами в локальной системе и другим пользователем на сервере.

Ресурсы

Научиться

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура
ArticleID=930431
ArticleTitle=Использование возможностей HTML5 для сохранения данных и оффлайновой работы: Часть 1. Реализация оффлайнового редактирования данных и синхронизации данных
publish-date=05202013