- PVSM.RU - https://www.pvsm.ru -
В прошлом топике [1] я постарался рассказать, что такое Chrome app, и зачем их писать. В этом, как обещал, я опишу процесс создания простого Chrome-приложения. В качестве примера будет использован текстовый редактор. Во-первых, его можно написать очень коротко, так чтобы практически весь код поместился в статью. Во-вторых, в текстовом редакторе будут использоваться несколько характерных для Chrome (и других основанных на Chromium браузеров) программных интерфейсов. В-третьих, да, я уже писал текстовый редактор для Chrome [2].
Полный код редактора доступен на гитхабе [3]. Готовый редактор можно установить из магазина приложений Chrome [4].
Для тестирования приложения, которое вы разрабатываете, необходимо будет добавить его в свой браузер. Для этого на странице chrome://extensions
нужно отметить чекбокс «Режим разработчика» («Developer mode»). После этого станет возможным добавить ваше расширение или приложение.
Код любого приложения для Chrome, как и любого расширения, начинается с файла manifest.json [5]. В нём описывается вся мета-информация приложения. Приведу целиком манифест редактора [6]:
{
"name": "Simple Text",
"description": "An extremely simple text editor (sample Chrome app)",
"version": "0.1",
"icons": {
"48": "icon/48.png",
"128": "icon/128.png"
},
"manifest_version": 2,
"minimum_chrome_version": "31.0",
"offline_enabled": true,
"app": {
"background": {
"scripts": ["js/background.js"]
}
},
"permissions": [
{"fileSystem": ["write"]}
],
"file_handlers": {
"text": {
"title": "Simple Text",
"types": ["application/javascript",
"application/json",
"application/xml",
"text/*"],
"extensions": ["c", "cc", "cpp", "css", "h", "hs", "html", "js", "json", "md", "py", "textile", "txt", "xml", "yaml"]
}
}
}
Разберём поля, которые тут встретились. С названием и описанием всё ясно. Версия является обязательным полем — Chrome Web Store будет требовать, чтобы она менялась, когда вы загружаете обновление вашего приложения.
Стандарные размеры иконок [7], требующихся для приложения — 48×48 и 128×128 пикселов. Также в некоторых случаях используется иконка размера 16×16. Кроме этого, другие размеры иконки могут потребоваться в случаях, когда она будет показываться на дисплеях высокого разрешения, как на Chromebook Pixel и новых MacbookPro.
"manifest_version"
— версия формата файла manifest. В данный момент следует использовать значение 2.
"offline_enabled"
— как можно ожидать, данный флаг установлен для приложений, работающих вне зависимости от наличия доступа к интернету.
Следующая конструкция — главная в файле:
"app": {
"background": {
"scripts": ["js/background.js"]
}
},
Тут браузеру сообщается, как запускать приложение. В отличие от расширений, для которых background page является необязательным атрибутом, в приложении он всегда есть. Логика работы такова: при запуске приложения сначала загружается код background page. Он может регистрировать обработчики тех или иных событий, в частности, события onLaunched
, который затем стартует, когда пользователь тем или иным способом открывает приложение.
В разделе "permissions"
описываются настройки доступа для приложения. В нашем случае добавлена возможность сохранять файлы.
Наконец, в разделе "file_handlers"
описаны типы файлов, открываемых приложением. Для разных типов файлов в файловом менеджере Chrome OS могут показывать разные строчки в меню. Например, для одних файлов пункт в меню может выглядеть «Смотреть изображение в СуперПрограмме», а для других — «Редактировать текст в СуперПрограмме».
Назначение Chrome-приложения программой для открытия того или иного типа файлов работает пока только в Chrome OS.
Весь код, реализующий background page находится в файле js/background.js [8]. Вот он:
var entryToLoad = null;
function init(launchData) {
var fileEntry = null
if (launchData && launchData['items'] && launchData['items'].length > 0) {
entryToLoad = launchData['items'][0]['entry']
}
var options = {
frame: 'chrome',
minWidth: 400,
minHeight: 400,
width: 700,
height: 700
};
chrome.app.window.create('index.html', options);
}
chrome.app.runtime.onLaunched.addListener(init);
Background page работает в фоновом режиме независимо от окон приложения. Большую часть времени он не загружен в память. При запуске системы его код исполняется и может установить обработчики тех или иных событий, самое распространённое из которых — onLaunched. Когда обработчики установлены, background page, как правило, выгружается из памяти и запускается обратно только если произошло одно из событий, на которые он подписан.
Когда пользователь кликает на иконку приложения, или открывает в нём какой-то файл, в background page запускается событие onLaunched [9]. В него передаются параметры вызова, в частности, файл(ы), которые приложение должно открыть. Код entryToLoad = launchData['items'][0]['entry']
сохраняет переданный в приложение файл в локальной переменной, откуда его потом возьмёт код редактора. Событие onLaunched может прийти и тогда, когда приложение уже открыто. В этом случае код в background page может сам решить, открывать ли новое окно, или совершить какие-то действия в уже открытом окне.
Метод chrome.app.window.create [10] создаёт новое окно приложения. Первый параметр — путь к открываемому в нём html-файлу (относительно директории приложения). Второй — параметры окна. Остановлюсь на одном из них. frame: 'chrome'
создаёт окно с обычным для текущей операционной системы оформлением. Другой вариант здесь — frame: 'none'
. В этом случае приложение запускается в «голом» окне, и разработчик должен будет сам позаботиться о добавлении кнопок для закрытия, свёртывания и развёртывания окна, а также области, за которую окно можно будет таскать по экрану.
В HTML и CSS файлах, входящих в состав приложений Chrome, нет ничего специфического. Единственная особенность, которую можно отметить — это отсутствие необходимости заботиться о межбраузерной соместимости.
<!DOCTYPE html>
<html>
<head>
<title>Simple Text</title>
<link href="main.css" rel="stylesheet">
<script src="js/jquery-2.1.0.min.js" type="text/javascript"></script>
<script src="js/main.js" type="text/javascript"></script>
</head>
<body>
<header>
<button id="open">Open</button>
<button id="save">Save</button>
<button id="saveas">Save as</button>
</header>
<textarea></textarea>
</body>
</html>
Мы воспользуемся jQuery [11], чтобы немного упростить код. Для редактирования мы будем использовать поле <textarea>
. В настоящем редакторе вместо это будет использоваться более интеллектуальный модуль редактирования. Наиболее распространённые варианты: CodeMirror [12] и Ace [13].
Для полноты картины приведу CSS:
body {
margin: 0;
}
header {
background-color: #CCC;
border-bottom: 1px solid #777;
-webkit-box-align: center;
-webkit-box-orient: horizontal;
-webkit-box-pack: left;
display: -webkit-box;
height: 48px;
padding: 0px 12px 0px 12px;
}
button {
margin: 8px;
}
textarea {
border: none;
-webkit-box-sizing: border-box;
font-family: monospace;
padding: 4px;
position: absolute;
top: 48px;
bottom: 0px;
left: 0px;
right: 0px;
width: 100%;
}
textarea:focus {
outline: none !important;
}
Так как в нашем примере мы для простоты ограничимся минимальным набором возможностей, то основной код редактора будет посвящён почти исключительно работе с файлами. Для этого используется несколько API, часть из которых уже находится на пути к стандартизации W3C. File API и сопутствующие интерфейсы — большая тема, заслуживающая отдельной статьи. В качестве хорошего введения рекомендую эту статью на html5rocks.com [14].
Итак, разберём код в js/main.js
. Я буду приводить его фрагментами, полный код — на Гитхабе [15].
function init(entry) {
$('#open').click(open);
$('#save').click(save);
$('#saveas').click(saveAs);
chrome.runtime.getBackgroundPage(function(bg) {
if (bg.entryToLoad)
loadEntry(bg.entryToLoad);
});
}
$(document).ready(init);
Задача функции инициализации — добавить обработчики к кнопкам и получить из background page файл для открытия. Контекст background page получается из основного окна асинхронно с помощью chrome.runtime.getBackgroundPage [16].
Обработчики нажатий на кнопки:
var currentEntry = null;
function open() {
chrome.fileSystem.chooseEntry({'type': 'openWritableFile'}, loadEntry);
}
function save() {
if (currentEntry) {
saveToEntry(currentEntry);
} else {
saveAs();
}
}
function saveAs() {
chrome.fileSystem.chooseEntry({'type': 'saveFile'}, saveToEntry);
}
Текущий FileEntry мы будем хранить в глобальной переменной currentEntry.
Единственная специфичная особенность в приведённом выше коде — это метод chrome.fileSystem.chooseEntry [17]. С помощью этого метода открывается окно выбора файлов (своё на каждой системе). Как и все прочие функции для работы с файловой системой, этот метод асинхронный и получает callback для продолжения работы (в нашем случае функции loadEntry и saveToEntry, описанные ниже).
Чтение файла:
function setTitle() {
chrome.fileSystem.getDisplayPath(
currentEntry,
function(path) {
document.title = path + ' - Simple Text';
});
}
function loadEntry(entry) {
currentEntry = entry;
setTitle();
entry.file(readFile);
}
function readFile(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
$('textarea').val(this.result);
};
reader.readAsText(file);
}
В функции setTitle()
мы меняем заголовок окна, чтобы показать путь к текущему файлу. То, как будет отображаться этот заголовок, зависит от системы. На Chrome OS он вообще не показывается. chrome.fileSystem.getDisplayPath [18] — наиболее корректный способ получить путь файлу, подходящий, чтобы показывать его пользователю. Другое представление пути доступно через entry.fullPath
.
В File API есть два различных объекта, описывающих файл: FileEntry и File. Грубо говоря, FileEntry олицетворяет путь к файлу, а File — данные, в нём содержащиеся. Следовательно, для того, чтобы прочитать файл, необходимо по Entry получить объект File. Это достигается с помощью асинхронного метода entry.file()
.
FileReader [19] — отдельный объект, предназначеный для чтения файлов. Он позволяет достаточно гибко управлять процессом чтения, но нам от него в данном случае нужно просто прочесть всё содержимое файла.
Запись файла, как и чтение, не содержит специфичного для Chrome кода:
function saveToEntry(entry) {
currentEntry = entry;
setTitle();
var blob = new Blob([$('textarea').val()], {type: 'text/plain'});
entry.createWriter(function(writer) {
writer.onwrite = function() {
writer.onwrite = null;
writer.write(blob);
}
writer.truncate(blob.size);
});
}
Прежде чем писать данные, их необходимо привести к виду Blob [20]. Один дополнительный шаг, который понабится нам при записи — это обрезание файла на случай, если он уже существует и имеет большую длину. Если бы мы были точно уверены, что это новый файл, код записи упростился бы до:
entry.createWriter(function(writer) {
writer.write(blob);
});
На этом код нашего приложения закончен. К сожалению, управление файлами в JavaScript устроено несколько неинтуитивно, и, вероятно, является наиболее сложной частью приложения. Но, как я уже писал выше, эти API не специфичны для Chrome, а реализованы во всех современных браузерах [21]
Код этого примера сделан максимально коротким, чтобы уместить его в формат статьи. Если вы хотите посмотреть на более развёрнутые примеры того, как используются те или иные возможности Chrome API, на Гитхабе опубликован большой набор примеров Chrome apps [22]. Официальная документация по всем программным интерфейсам — на developer.chrome.com [23]. Основное место, где можно получить ответы на конкретные вопросы по программированию Chrome-приложений — тэг google-chrome-app на StackOverflow [24].
Автор: eterevsky
Источник [25]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/54745
Ссылки в тексте:
[1] прошлом топике: http://habrahabr.ru/company/google/blog/211346/
[2] текстовый редактор для Chrome: https://github.com/GoogleChrome/text-app/
[3] доступен на гитхабе: https://github.com/eterevsky/simple-text
[4] установить из магазина приложений Chrome: https://chrome.google.com/webstore/detail/eemaijgmlecljjagojapeeaohllhobke
[5] файла manifest.json: http://developer.chrome.com/apps/manifest.html
[6] манифест редактора: https://github.com/eterevsky/simple-text/blob/master/manifest.json
[7] иконок: http://developer.chrome.com/apps/manifest/icons.html
[8] js/background.js: https://github.com/eterevsky/simple-text/blob/master/js/background.js
[9] onLaunched: https://developer.chrome.com/apps/app_runtime.html#event-onLaunched
[10] chrome.app.window.create: http://developer.chrome.com/apps/app_window.html#method-create
[11] jQuery: http://jquery.com/
[12] CodeMirror: http://codemirror.net/
[13] Ace: http://ace.c9.io/
[14] эту статью на html5rocks.com: http://www.html5rocks.com/en/tutorials/file/filesystem/
[15] на Гитхабе: https://github.com/eterevsky/simple-text/blob/master/js/main.js
[16] chrome.runtime.getBackgroundPage: https://developer.chrome.com/apps/runtime.html#method-getBackgroundPage
[17] chrome.fileSystem.chooseEntry: http://developer.chrome.com/apps/fileSystem.html#method-chooseEntry
[18] chrome.fileSystem.getDisplayPath: http://developer.chrome.com/apps/fileSystem.html#method-getDisplayPath
[19] FileReader: https://developer.mozilla.org/en-US/docs/Web/API/FileReader
[20] Blob: https://developer.mozilla.org/en-US/docs/Web/API/Blob
[21] во всех современных браузерах: http://www.html5rocks.com/en/features/file_access
[22] большой набор примеров Chrome apps: https://github.com/GoogleChrome/chrome-app-samples
[23] на developer.chrome.com: http://developer.chrome.com/apps/about_apps.html
[24] тэг google-chrome-app на StackOverflow: http://stackoverflow.com/tags/google-chrome-app/info
[25] Источник: http://habrahabr.ru/post/212107/
Нажмите здесь для печати.