Написать расширение для google chrome несложно. Но при написании первого раширения могут возникнуть (и возникают) вопросы. Большинство мануалов по написанию первого расширения расчитаны на использования манифеста первой версии, поддержка которого в скором будущем прекратится.
В этой статье будет рассмотрено:
- Как составлять манифест v.2
- Как работать с удаленными ресурсами
- Как работать с cookies
- Как работать с local storage
- Как работать с уведомлениями
Введение
К концу статьи у нас будет готово расширение-органайзер, в котором будет поле для добавления новой задачи, а так же список задач на текущий день. Обозначим все требования к органайзеру:
- Должен иметь поле для добавления события (дата, время, событие)
- Должен отображать все задачи на текущий день, отсортированные по времени
- Все прошедшие события должен отображать зачеркнутыми
- Должен иметь поле для ввода времени, за сколько надо показывать уведомление, а так же чекбокс разрешающий и запрещающий показывать уведомления
- За указанное время до события должен отображать уведомление о приближающемся событии
Манифест
Начнем создавать расширение с самого начала, то есть с манифеста. Манифест – это тот самый файл, в котром прописываются все параметры расширения. Название, описание, версия, разрешение на доступ к сайтам, разрешение на использование кук, уведомлений, локального хранилища. В общем, манифест – это
manifest.json
{
“name”: “Organizer extension”, // Название расширения
“version”: “1.0”, // Версия расширения.
“manifest_version”: 2 // Версия манифеста
}
Тут есть пара правил:
- Версия манифеста должна быть целочисленной, то есть должна писаться как 2, а не “2”.
- Версия расширения должна быть строковой, но содержать только числа и точки, то есть “1.0” — хорошо, а 1.0 и “0.9 beta” — плохо.
С обязательными полями – все, перейдем к созданию всплывающего окна расширения. Для того, чтобы по нажатию на пиктограмму, открывалось окно, необходимо добавить в манифест поле “browser_action”
manifest.json
{
…
"browser_action": {
"default_title": "Open organizer", // Заголовок. Его видно если навести курсор на иконку в браузере
"default_icon": "icon_small.png", // Путь к иконке расширения
"default_popup": "popup.html" // Путь к странице с попапом
}
}
Теперь создадим всплывающее окно. Это обычная html страница, которая может быть любого размера и цвета, никаких фокусов. Назовем файл “popup.html”. Создать этот файл мало – его надо указать в манифесте. Так мы и сделали: «default_popup»: «popup.html».
popup.html
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>It works!</div>
</body>
</html>
Добавление расширения в браузер
Теперь пришло время проверить работоспособность нашего расширния. Для этого загрузим расширение в браузер. Открываем в хроме меню расширений. Ставим птицу на “Developer mode”.
После этого появятся три кнопки. Нажимаем “Load unpacked extension...”. Выбираем папку с файлами расширения. После этого появится наше расширение. Если все правильно, то по нажатию на иконку – повится окно:
Подключение скриптов
Теперь можно приступить к интересному. Подключим два javascript файла. Первый – popup.js, второй – jquery. С первым проблем не возникнет, но jquery будем подключать не локальный, а удаленный, взятый по адресу ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js. Проблемы возникнут от того, что по умолчанию расширение не имеет доступа к сторонним ресурсам. Чтобы получить доступ, надо его указать в манифесте. Доступ к чему-либо указывается в поле “permissions”. Так же, для удаленных скриптов и css надо указывать доступные удаленные ресурсы.
manifest.json
{
…
"permissions": [
"https://ajax.googleapis.com/*"
],
"content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'"
}
Теперь подключим эти скрипты в popup.html
popup.html
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="popup.js"></script>
Storage
При помощи storage в хроме можно хранить пользовательские данные. И именно в storage наше расширение и будет хранить грядущие события. На то есть две причины. Во-первых, данные, хранищиеся в storage можно синхронизировать, если залогиниться в браузере. А во-вторых, данные можно хранить не только в виде строки, как в cookies, а в любом виде, то есть можно хранить и массивы и объекты. Чтобы это заработало, откроем доступ к storage в манифесте.
manifest.json
{
...
"permissions": [
…
"storage"
]
...
}
Теперь переделаем всплавающее окно. Во всплывающем окне будет поле с сегодняшней датой, три инпута для даты, времени и описания нового события, кнопка для добавления нового события, а так же список всех событий на сегодня.
popup.html
<div id="container">
<div id="today_date">Date</div>
<table class="table">
<tr>
<td>Дата</td>
<td>Время</td>
<td>Задача</td>
</tr>
<tr>
<td><input type="text" id="new_date" value="" class="input_date" /></td>
<td><input type="text" id="new_time" value="" class="input_date" /></td>
<td><input type="text" id="new_task" value="" class="input_task" /></td>
</tr>
</table>
<ul></ul>
</div>
И сразу же добавим отображение даты в блоке #today_date.
popup.js
$(function(){
var today = new Date();
$("#today_date").html(today.getDate()+"."+(parseInt(today.getMonth())+1)+"." + today.getFullYear());
}
Выглядеть должно так:
Итак, при нажатии на кнопку “+” у нас должно добавляться событие. Вначале файла объявим глобальную переменную storage – объект для работы с storage, а так же глобальный массив tasks для хранения событий.
popup.js
var storage = chrome.storage.sync;
var tasks = new Array();
$(function(){
…
$("#add_task").click(function(){
var new_task = new Object();
new_task.date = validateField($("#new_date").val(), "date");
new_task.time = validateField($("#new_time").val(), "time");
new_task.task = $("#new_task").val();
if(!new_task.task || !new_task.date || !new_task.task){
return false;
}
tasks[tasks.length] = new_task;
storage.set({
tasks:tasks
});
});
});
Функция валидации проверяет, что дата записана в формате d.m.yyyy, а время в формате hh:mm, а так же, что в описании события не меньше трех символов.
popup.js
var validateField = function(val, type){
if(type == "date"){
var date = val.split(".");
var year = new Date();
year = year.getFullYear();
if(date.length == 3 && parseInt(date[0]) == date[0] && date[0] <= 31 && parseInt(date[1]) == date[1] && date[1] <= 12 && parseInt(date[2]) == date[2] && date[2] >= year){return val;}
} else if(type == "time"){
var time = val.split(":");
if(time.length == 2 && parseInt(time[0]) == time[0] && time[0] < 24 && parseInt(time[1]) == time[1] && time[1] < 60){return val;}
} else if(type == "task" && type.length >= 3){
return val;
}
return null;
}
С добавлением разобрались, переходим к получению событий на сегодня. Для этого надо получить все события из базы, выбираем из всех только сегодняшние события и сортируем их по времени по возрастанию.
popup.js
$(function(){
…
var now_hours = today.getHours() < 10 ? "0" + today.getHours() : today.getHours();
var now_minutes = today.getMinutes() < 10 ? "0" + today.getMinutes() : today.getMinutes();
var now_time = now_hours + "" + now_minutes;
storage.get("tasks",function(items){
if(items.tasks){
tasks = items.tasks;
var today_tasks = getTodayTasks(tasks);
if(today_tasks.length >0){
for(var i in today_tasks){
var this_time = today_tasks[i].time.replace(":", "");
var add = this_time > now_time ? "" : ' class="done"';
var add_html = '<li'+add+'><strong>'+today_tasks[i].time+'</strong> '+today_tasks[i].task+'</li>';
$("ul").append(add_html);
}
}
}
});
…
});
Функция getTodayTasks() возвращает из общего списка только события с сегодняшней датой.
popup.js
var getTodayTasks = function(tasks){
var today_tasks = new Array();
var today = new Date();
var today_date = today.getDate()+"."+(today.getMonth() + 1 )+ "." + today.getFullYear();
for(var i in tasks){
if(tasks[i].date == today_date){
today_tasks[today_tasks.length] = tasks[i];
}
}
if(today_tasks.length > 0){
today_tasks = sortTasks(today_tasks);
}
return today_tasks;
}
Функция sortTasks() сортирует события по возрастанию времени.
popup.js
var sortTasks = function(tasks){
if(tasks.length > 0){
var swapped = true;
while (swapped) {
swapped = false;
for (var i=0; i < tasks.length-1; i++) {
var this_time = tasks[i].time.replace(":", "");
var next_time = tasks[i+1].time.replace(":", "");
if (this_time > next_time) {
var temp = tasks[i];
tasks[i] = tasks[i+1];
tasks[i+1] = temp;
swapped = true;
}
}
}
}
return tasks;
}
Уведомления
Пришло время настроить отображение уведомлений на экране. Добавим во всплывающее окно специальный чекбокс. Если этот чекбокс будет отмечен – уведомлениея будут показываться, если не будет отмечен – не будут. Так же добавим текстовый инпут. Цифра в этом инпуте будет показывать, за какое время до событя будет показываться уведомление. То есть если у нас событие назначено на 19:00, в этом текстовом инпуте будет 5, значит в 18:55 появится уведомление. Добавим в popup.html код с этими инпутами
popup.html
<div>
<input type="checkbox" id="show_notifications" checked="checked" />
<input type="text" id="when_to_notify" class="input_date" value="" />
Показывать уведомления
</div>
Теперь давайте разберемся с тем, как это будет работать. При нажатии на чекбокс, будет проверяться его атрибут checked, значение атрибута будет записываться в cookie “show_notifications”. Перейдем к текстовому инпуту. По изменению его значения, новое значение будет валидироваться, если оно целочисленное и не больше 120, записываем новое значение в cookie “when_to_notify”.
Но для того, чтобы у нас это заработало, надо открыть доступ к cookies. Для этого заходим в manifest.json и добавляем в “permissions”
manifest.json
{
...
"permissions": [
…
“cookies”
]
...
}
Теперь можно приступать к скрипту. Заходим в popup.js. Для начала установим первоначальные значения в инпутах. По-умолчанию чекбокс не отмечен, то есть уведомления не показываются, а время равно 0. При клике на чекбокс, будет меняться значение cookie “show_notifications”. При изменении значения в тектовом поле, будет меняться значение cookie “when_to_notify”.
popup.js
$(function(){
setCheckbox();
setWhenToNotify(getCookie("when_to_notify"));
...
$("#show_notifications").click(function(){
setCookie("show_notifications", document.getElementById("show_notifications").checked);
});
$("#when_to_notify").change(function(){
setWhenToNotify($(this).val());
});
});
Рассмотрим подробнее функции. Начнем с функций работы с cookies. В данном случае были взяты готовые функции с w3schools.com.
popup.js
var setCookie = function(c_name,value,exdays){
/*
*Взято с http://www.w3schools.com/js/js_cookies.asp
*/
var exdate=new Date();
exdate.setDate(exdate.getDate() + exdays);
var c_value=escape(value) + ((exdays==null) ? "" : "; expires="+exdate.toUTCString());
document.cookie=c_name + "=" + c_value;
}
var getCookie = function(c_name){Позвонить Васе П.
/*
*Взято с http://www.w3schools.com/js/js_cookies.asp
*/
var i,x,y,ARRcookies=document.cookie.split(";");
for (i=0;i<ARRcookies.length;i++){
x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
x=x.replace(/^s+|s+$/g,"");
if (x==c_name){
return unescape(y);
}
}
}
Разберемся с функцией setCheckbox(). Она получает cookie “show_notifications” и проверяет, если полученное значение равно “true”(текстовое, да), то параметр checked у чекбокса true, иначе false.
popup.js
var setCheckbox = function(){
var val = getCookie("show_notifications")
document.getElementById('show_notifications').checked = ((val == "true") ? true : false);
}
Теперь рассмотрим setWhenToNotify(). Она принимает новое значение таймера. Если это значение – целочисленное и не больше 120 минут, то в cookie “when_to_notify” устанавливается новое значение. Если переменная не прошла эту валидацию, то в инпут возвращается предыдущее значение из cookies “when_to_notify”.
popup.js
var setWhenToNotify = function(val){
var last_val = getCookie("when_to_notify");
last_val = last_val != "undefined"? last_val: 0;
val = (parseInt(val)==val && val <=120) ? val : last_val;
setCookie("when_to_notify", val);
$("#when_to_notify").val(val);
}
Перейдем к самим уведомлениям. Для этого откроем доступ к уведомлениям и подключим фоновый background.js. Нужно подключить именно фоновый файл, так как если уведомления вызывать из popup.js, то уведомления будут появляться только если открыто всплывающее окно.
manifest.json
{
...
"permissions": [
…
"notifications"
],
"background": { "scripts": ["background.js"] },
"web_accessible_resources": ["icon_small.png"],
...
}
Последняя строчка дает доступ к удаленному файлу. Дело в том, что картинка, которая отображается в уведомлении обязательно должна быть доступна расширению удаленно. В данном случае файл локальный, но доступ открывать все равно надо. Теперь возьмемся за background.js. Объявим переменную storage и пустой массив tasks. Далее раз в минуту скрипт будет получать список сегодняшних событий и получать из них список задач, которые должны произойти через указанное время. После этого для каждой такой задачи будет показано уведомление.
background.js
var storage = chrome.storage.sync;
var tasks = new Array();
setInterval(function() {
storage.get("tasks",function(items){
if(items.tasks && getCookie("show_notifications") == "true"){
tasks = getTodayTasks(items.tasks);
if(window.webkitNotifications){
var texts = getNextTask(tasks);
for(var i in texts){
show(texts[i]);
}
}
}
});
}, 60000);
Функции getTodayTasks() и getCookie() взяты из popup.js. Так что начнем разбор с функции getNextTask(). Функция сравнивает текущее время и время события, если оно равно тому значению, которое хранится в cookie “when_to_notify”, то в массив next дописывается строка из времени события и его описания. После проверки всех событий возвращет массив next.
background.js
var getNextTask = function(tasks){
var now = new Date();
now = now.getTime();
var next = new Array();
for(var i in tasks){
var date = tasks[i].date.split(".");
var time = tasks[i].time.split(":");
var task_date = new Date(parseInt(date[2]), parseInt(date[1]-1), parseInt(date[0]), parseInt(time[0]), parseInt(time[1]), 0, 0);
task_date = task_date.getTime();
var delta = Math.round((task_date-now)/60000);
if(delta == getCookie("when_to_notify")){
next[next.length] = tasks[i].time + " - " + tasks[i].task;
}
}
return next;
}
Функция show() показывает уведомление с заданным текстом.
background.js
var show = function(text) {
var notification = window.webkitNotifications.createNotification('icon_small.png','Есть дело', text);
notification.show();
}
Результатом работы этого скрипта будет такое вот уведомление:
Послесловие
Как и обещано, в конце статьи у нас есть готовое расширение-органайзер для Google Chrome.
Теперь добавим расширение в Chrome Web Store. Загружать надо расширение, запакованное в .zip-архив. Для начала заходим в Web Store. Для этого заходим в хроме на вкладку “Приложения” и нажимаем кнопку “Web Strore”
Теперь заходим в меню. Для этого нажимаем шестиренку и открываем “Developer dashboard”
Нажимаем большую кнопку “Add new item”. После этого надо будет выбрать zip-архив с расширением нажать “upload”
Далее надо заполнить небольшую форму с описанием расширения. Теперь есть выбор либо сохранить расширение в черновил либо опубликовать. Просто так опубликовать его не получится так как регистрация в Web Store стоит 5 $. Плату за регистрацию надо платить один раз для аккаунта, а не для каждого расширения. После оплаты появляется позможность опубликовать расширение, но и тут надо быть готовым к тому, что его будут валидировать несколько дней.
Полезные ссылки
Описание полей манифеста: developer.chrome.com/trunk/extensions/manifest.html
Работа с storage: developer.chrome.com/trunk/extensions/storage.html
Информация по уведомлениям: developer.chrome.com/extensions/notifications.html
Исходники расширения: github.com/bozheville/orginaizer_extension
Автор: bozheville