Пишем HTML-виджет для приложения IBOOK Author

в 20:14, , рубрики: apple, html, javascript, JSONP, метки: , ,

Ни для кого не секрет, что печатные книги уходят в прошлое. Им на смену приходит новое поколение устройств (а вместе с ними и программ), которые позволяют не только прочитать материал, подобно книге, но и разнообразить чтение интерактивными вставками. Это может быть как и некий динамически изменяющийся материал (лента твиттера и т.п.), так и некое интерактивное содержание (анимация работы конденсатора и т.п.)
Компания Apple выпустило приложение IBOOK Author, которое позволяет делать интерактивные книги для приложения IBOOKS устройств IPAD. Сегодня я предлагаю познакомиться с тем, как же создавать HTML-виджеты для данного приложения на примере виджета твиттера.
Для создания виджетов существует множество программ, стоимость которых варьируется от 0 (Dashcode) до 60 (Tumult Hype).
Создавать же свой виджет мы будем «вручную», без использования специализированных программ.
Теперь предлагаю перейти от слов к делу.

Структура виджета

HTML виджет для IBOOK — это папка с расширением wdgt.
При создании HTML виджета можно выделить следующие обязательные файлы:

  • Default.png = Файл — превью виджета. Данное изображение будет демонстрироваться пользователю, пока он не запустит виджет. Имя файла строгое и регистрозависимое. Разрешение виджета (если не задано иных параметров) будет равно разрешению данной картинки.
    Retina: Если создается виджет, поддерживающий Retina дисплеи, то должен быть создан также файл Default@2x.png, который имеет в два раза большее разрешение, чем оригинальный Default.png.
  • Основной HTML файл — название данному файлу можно задать любое. Является основной функциональной частью виджета. Может включать как и css, так и js файлы (здесь ограничений нет, все аналогично написанию веб-страниц)
    Retina: Для поддержки необходимо писать аналогично веб-страницам для Retina дисплеев (на хабре уже мелькали статьи на эту тему )
  • Info.plist — файл конфига для виджета. Содержит информацию о названии виджета, его разрешении, основном HTML файле и т.д. Остановимся на нем немного подробнее...

Info.plist — что в конфиге?

Данный файл конфига представляет собой ничто иное, как xml файл, в котором параметры записываются как

<key>Параметр</key>
  <тип данных>Значение</тип данных>

Используемые параметры:

  • CFBundleDevelopmentRegion — Необязательный параметр, как правило, описывает родной язык создателя виджета. Его значениями могут быть English, Russain
  • CFBundleDisplayName — Обязательный параметр, значением которого является строка с названием виджета. В нашем примере укажем Twitter
  • CFBundleIdentifier — Обязательный параметр — строка, однозначно идентифицирующая виджет. Используется инвертированное доменное имя. Например: ru.MySite.widgets.Twitter
  • CFBundleName — Необязательный параметр, представляется строкой определяющее название виджета.
  • CFBundleVersion и CFBundleShortVersionString — необязательные параметры — строки, содержащие версию виджета. «длинную» и «короткую» версии соответственно
  • Height и Width — необязательные параметры. — значением данных параметров являются числа, определяющие высоту и ширину виджетов
  • IBNotifiesOnReady — Необязательный параметр. При установке данного параметра в true — виджет должен сообщить приложению, когда необходимо перейти к HTML коду, убрав картинку (при загрузке виджета, пока он не будет готов, отображается Default.png) Данный параметр целесообразно ставить в true, в случае если перед отображением данных виджет должен сделать «сложные вычисления», либо собрать данные с нескольких серверов… (в общем случае — когда виджет будет «подготавливать информацию» некоторое время). В остальных случаях его лучше поставить в false. Переключение от картинки к HTML коду происходит по widget.notifyContentIsReady ()
  • MainHTML — представляется строкой, определяет имя основного html файла виджета

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

Теперь приступим непосредственно к реализации виджета.

Непосредственно реализация виджета

Планируем работу виджета

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

  1. Виджет при запуске отправляет запрос на сервер, получает список пользователейхештегов, которые должен отобразить
  2. Формирует данные в нужнный для твиттера «формат»
  3. «Импортирует» скрипт Twitter API, ожидает его ответа
  4. Оформляет данные в понятное для пользователя представление

В данном виджете будем использовать библиотеку JQuery (для упрощения работы с JSON и создания AJAX запросов)

HTML-файл

Не представляет «практической» пользы в данном примере. Содержит контейнер для будущих твитов:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
	<script type="text/javascript" src="jquery.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>

    <input type="hidden" id="ID" value="SomeWidgetIDForServerQuery" />
    <div id='tweet-container'>
	</div>
	<script type="text/javascript" src="ServerQuery.js"></script>
</body>
</html>

JavaScript начинка

Главной задачей скрипта будет отправка AJAX запросов к серверам. Здесь существует одно ограничение. В прямом виде такие запросы отправить нам не получится. Точнее запрос уйдет на сервер, сервер его обработает, но принять данные виджет не сможет. Поэтому выходом из данной ситуации будет использование JSONP.
Что это такое? Это паттерн, который позволит нам отправить данные на удаленный сервер и «обойти» политику ограничения доменов.
Как выглядит данный способ?
Виджет создает GET запрос, в параметрах которого(помимо нужной информации) указывает функцию callback. Сервер возвращает данные, обернутые в вызов JavaScript функции в формате

callback-функция(Данные в формате JSON);

Определим данные, которые будет отправлять сервер: допустим данные будут содержать только один параметр results, который будет представлен как массив элементов name (включаем сюда, как пользователей, так и хештеги)
Примерный ответ сервера:

jsonp_callback({"results":[{"name":"user1"},{"name":"#hashCode1"}]});

Напишем скрипт, который будет делать запрос на сервер и обрабатывать полученные данные, переводя в «нужный» для твиттера формат:
(формат простой — твиты пользователя определяются как from: пользователь, связь между различными фильтрами поиска проводится посредством "+OR+", хеш-теги не меняются за исключением переноса # в символ %23)

//В этой переменной накапливаем строку для запроса
var requestStr = "";

//Переводит один "поисковой фильтр" в часть запроса
function PrepareItem(item)
{
	if (requestStr != "")
		requestStr += '+OR+';
	if (item[0] != "#")
	{//перевод поиска твитов по пользователю
		requestStr += 'from:' + item;
	}
	else
	{//перевод поиска по хеш-тегу
		requestStr += '%23';
			for (var index = 1; index < item.length; index++)
			    requestStr += item[index];
	}
}

$(document).ready(function()
{
$.ajax({
  //Определение формата передачи данных
  dataType: 'jsonp',
              jsonp: 'jsonp_callback', //в GET запрос под этим названием и будет функция-callback
  url: "http://mySite.ru/widgets/twitter.php",
  data: { ID:$('#ref').val() },
 success: function( result ) {
			$(result.results).each(function(item)
			{ 
				PrepareItem(item.name);
			});
			//Организуем запрос к твиттеру (чуть ниже будет ее описание)
			CreateQueryToTwitter(requestStr);
          }
});

Однако JSONP предполагает и второй способ передачи данных (физически он не отличается от первого — разница только в программных реализациях) — именно вторым способом мы и воспользуемся для передачи запроса твиттеру.

Данный способ заключается в добавлении в HTML код элемента «script», тело которого пусто, а src содержит такой же GET запрос с указанием функции callback и других параметров.

После отправки запроса нам остается только разобрать ответ твиттера (самая полезная информация находится в элементе results) и отформатировать его.

function CreateQueryToTwitter(request)
{
	var newScript = document.createElement('script');	
	newScript.setAttribute("type", "text/javascript");//организуем скрипт
	newScript.setAttribute("src", "http://search.twitter.com/search.json?q=" + request + "&callback=tweetResponse&rpp=40");
	//добавляем его к HTML коду
	document.getElementsByTagName("head")[0].appendChild(newScript);
}
 
function tweetResponse(result) {
	var container=$('#tweet-container');
	container.html('');	
	//обрабатываем ответ
	$(result.results).each(function () {
	    var str = ('	<div class="tweet">
			<div class="avatar"><a href="http://twitter.com/'+this.from_user+'" target="_blank"><img src="'+this.profile_image_url+'" alt="'+this.from_user+'" /></a></div>
			<div class="user"><a href="http://twitter.com/'+this.from_user+'" target="_blank">'+this.from_user+'</a></div>
			<div class="txt">' + formatTweet(this.text) + '</div>
        ');
	container.append(str);	
	});	
	
}
 
 //Создадим гиперссылки на людей, другие ссылки и хештеги
function formatTweet(str) {
	str=' '+str;
	str = str.replace(/((ftp|https?)://([-w.]+)+(:d+)?(/([w/_.]*(?S+)?)?)?)/gm,'<a href="$1" target="_blank">$1</a>');
	str = str.replace(/([^w])@([w-]+)/gm,'$1@<a href="http://twitter.com/$2" target="_blank">$2</a>');
	str = str.replace(/([^w])#([а-я,А-Я,a-z,A-Z,-]+)/gm,'$1<a href="http://twitter.com/search?q=%23$2" target="_blank">#$2</a>');
	return str;
}

Остается только прописать стили для виджета и HTML код готов:

body
{
	font-family: Tahoma,Arial,Verdana,sans-serif;	
	width:1024px;
    margin: auto;
	height:768px;
}

#tweet-container
{
	overflow-y:scroll;
	height:768px;
	width:660px;
    margin: auto;
}

.tweet
{
	color:black;
    margin-top:5px;
    background-color:#F0F1F4;
    border-bottom:3px solid #5ea8de;
    padding:10px;
    margin-left:5px;
    width:600px;
    height:autopx;
}

.avatar
{
	float:left;
}

.avatar:hover
{
	opacity:0.5;
}

.user
{
	
	float:left;
	padding-left:10px;
}

.user a
{
text-decoration:none;
	color:black;
	font-size:1.1em;
}
.user a:hover
{
color:#DB4FDB
}

.txt
{
	clear:left;
}

Теперь добавим к данной веб-страничке файл Info.plist. В моем виджете он выглядит так:

	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
	<plist version="1.0">
	<dict>
		<key>BackwardsCompatibleClassLookup</key>
		<true/>
		<key>CFBundleDevelopmentRegion</key>
		<string>Russian</string>
		<key>CFBundleDisplayName</key>
		<string>twitter</string>
		<key>BRNotifiesOnReady</key>
		<true/>
		<key>CFBundleIdentifier</key>
		<string>ru.mySite.widget.twtter</string>
		<key>CFBundleName</key>
		<string>twitter</string>
		<key>CFBundleShortVersionString</key>
		<string>1.0</string>
		<key>CFBundleVersion</key>
		<string>1.0</string>
		<key>KFNotifiesOnReady</key>
		<false/>
		<key>Height</key>
		<integer>768</integer>
		<key>MainHTML</key>
		<string>main.html</string>
		<key>Width</key>
		<integer>1024</integer>
		<key>IBNotifiesOnReady</key>
		<false/>
	</dict>
	</plist>

Таким образом, остается только переименовать папку, добавив к ней расширение .wdgt и его можно смело использовать в Ibook Authors.

В заключении

В данном how-to я постарался разобрать этапы создания простейшего виджета и описать наиболее «опасные» места, такие как связь виджета с сервером. Конечно же, если убрать связь виджета с неким сервером, который задает ему имена и хеш-теги и прописав их в виджете — можно уменьшить количество зависимостей данного виджета.

Надеюсь данная статья не просто заняла Ваше время, но и будет чем-то полезна. :)

Автор: xnim

Источник

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


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