Часть 4. Создание скриптов. JavaScript.

В то время как XUL нужен для того, чтоб задать структуру надстройки для нашего тулбара, JavaScript — это то, что наш тулбар оживит. К счастью, JavaScript — это язык программирования, с которым просто разобраться, если раньше с ним никогда не сталкивались (ага, щас, какже — прим.пер.). Тем, кто уже имеет опыт работы с JavaScript, наши дальнейшие действия покажутся достаточно скучными.

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

Я рекомендую к имени каждой переменной и функции добавлять префикс, состоящий из начальных букв имени вашего расширения. В нашем случае, все переменные и функции снабжаются префиксом "GBLTut_". Например, имя поисковой функции — "GBLTut_Search". Добавив этот префикс, мы удостоверимся, что все наши переменные и функции имеют действительно уникальные имена и не будут конфликтовать с остальными.

Имя файла скрипта для нашего пример — "gbltutorial.js". Он должен располагатсья в папке content вместе с XUL файлом, о котором шла речь в предыдущем разделе. На данный момент, вся файловая структура должна представлять из себя следующее:

+- GBLTutorial/
   +- install.rdf
   +- chrome/
      +- content/
         +- contents.rdf
         +- gbltutorial.xul
         +- gbltutorial.js

Этот файл содержит необходимый код, который оживит наш тулбар. Перед тем, как исследовать файл скрипта, давайте разберемся, как связать XUL и JavaScript.

Связываем XUL и JavaScript

Чтобы пользоваться кодом JavaScript, нам для начала нужно в XUL-разметке указать, с каким файлом скриптов мы будем работать. Для этого нужен элемент script, выглядит он вот так:

<script type="application/x-javascript"
        src="chrome://gbltutorial/content/gbltutorial.js"/>

Этот элемент надо расположить внутри элемента overlay в нашем XUL файле (я вставляю его прямо после открывающего тега overay). После добавления элемента script наш XUL стал вот таким: [XUL, вариант 6].

Как видите, атрибут type указывает, что мы работаем с JavaScript, а src просто содержит chrome-путь до используемого файла.

Добавляем функциональность к нашим кнопкам

Атрибут oncommand, представленный в предыдущем разделе, это тот механизм, посредством которого определяется функциональность обычных кнопок. Помните, что oncommand — это на самом деле событие, которое срабатывает, когда активируется объект (в данном случае — кнопка меню). И хотя событие onclick тоже будет работать, лучше все-таки использовать oncommand, который позволяет также отследить нажатие кнопки с клавиатуры. Разметка для этого атрибута проста:

oncommand="GBLTut_Search(event, 'web')"

Вот та же разметка в контексте обычной кнопки:

<toolbarbutton id="GBLTut-TB-Web" tooltiptext="Search the Web"
               label="Web Search" oncommand="GBLTut_Search(event, 'web')" />

Значение, представленное в этом примере, показывает вызов JavaScript-функции GBLTut_Search(event, 'web'). При активации этой кнопки будет вызвана функция GBLTut_Search() и, соответственно, выполнен ее код. Взглянем на первый вариант JavaScript-файла: [JS, вариант 1]. Его код довольно подробно прокомментирован, так что обойдемся без длинных объяснений. Этот пример просто должен показать, насколько быстро можно перейти к весьма сложным скриптам.

Вот собственно и все добавление функциональности к кнопкам. Просто, не так ли? Используя событие oncommand, вы можете выполнять любой код, какой захотите. Кстати, есть еще много всяких доступных для вас событий (например, упомянутый ранее onclick). Полный список событий лежит на XUL planet, так что — не стесняйтесь, выбирайте то, что вам надо.

Стоит отдельно упомянуть вот этих наших партнеров:

Специальное замечание про кнопки "кнопка-меню".

Перед тем как перейти к следующей теме, надо сделать важное замечание касательно кнопок "кнопка-меню". Вспомните, что этот тип кнопок совмещает и простую кнопку, и всплывающее меню (например, кнопки навигации "вперед" и "назад"). Следовательно, этот тип кнопок состоит из элемента toolbarbutton, в который вложен элемент menupopup (который, в свою очередь, содержит несколько элементов menuitem). У элемента toolbarbuttom, как и у всех элементов menuitem, имеется атрибут oncommand. Когда кнопка активируется (пользователь на ней кликает), как вы догадываетесь, выполняется код, указанный в атрибуте oncommand элемента toolbarbutton. А вот что будет, если пользователь активирует один из элементов menuitem?

Предположив, что в этом случае просто сработает событие oncommand элемента menuitem, вы будете отчасти неправы. Хотя это событие и сработает, вместе с ним сработает и событие oncommand элемента toolbarbutton! А поскольку событие для кнопки сработает последним, будет выполнен код, связанный именно с ним. Если вы не видите в этом никакого смысла (парень, в точку! — прим.пер.), изучите следующий пример:

Комбинированное меню поиска Googlebar Lite — это кнопка "кнопка-меню". Когда вы кликаете по этой кнопке, выполняется обычный поиск. Различные пункты меню из всплывющего меню позволяют пользователю выполнять другие типы поиска. Если бы событие oncommand для каждого menuitem было оформленно неправильно, выбор любого пункта меню приводил бы к тому же обычному поиску (поскольку выполнялось бы событие oncommand для элемента toolbarbutton). Так как же быть?

К счастью, есть решение проблемы через удобную функцию DOM (объектной модели документа):

<menuitem id="GBLTut-TB-Combined-Web" label="Web Search"
          class="menuitem-iconic" tooltiptext="Search the Web"
          oncommand="GBLTut_Search(event, 'web'); event.preventBubble();" />

Добавляя вызов event.preventBubble() в конец кода, мы можем предотвратить распространение события oncommand вверх по дереву DOM. Именно из-за этого эффекта у нас всегда выполнялся код для элемента toolbarbutton. Поскольку toolbarbutton — предок каждого menuitem, он перехватывает событие oncommand и оно передается вверх по дереву DOM.

Короче говоря, всегда помните следующее правило: создавая кнопку типа "кнопка-меню", обязательно добавляйте event.preventBubble() к атрибуту oncommand каждого элемента menuitem.

Динамически заполняем меню

(приведенный пример кажется мне довольно бессмысленным, однако техническую суть дела демонстрирует вполне наглядно — прим. пер.).

Возможность динамически заполнять меню пунктами черезвычайно полезна. В качестве примера возьмем меню поля ввода нашего тулбара. Это меню нужно обновлять каждый раз, когда пользователь переходит к новой странице (не понял, зачем? — прим. пер.). К счастью, динамическое наполнение меню — задача простая. Добавим пять динамических пунктов к выпадающему меню нашего тулбара. Вот разметка, которую мы обсуждали в разделе 3:

<toolbaritem id="GBLTut-SearchTerms-TBItem" persist="width">
<menulist id="GBLTut-SearchTerms" editable="true" flex="1"
          minwidth="100" width="250"
          onkeypress="if(event.which == 13) { GBLTut_Search(event, 'web'); }">
  <menupopup id="GBLTut-SearchTermsMenu" onpopupshowing="GBLTut_Populate()" />
</menulist>
</toolbaritem>

Единственный интересующий нас элемент — это элемент menupopup. В разметке мы указали, что функция GBLTut_Populate() должна быть выполнена каждый раз, когда нужно показать выпадающее меню. Таким образом мы динамически создадим пункты меню. Вот, собственно говоря, отвечающий за это JavaScript: [функция GBLTut_Populate()].

Как и предыдущий пример, этот хорошо прокомментирован, так что просмотрите его, чтоб понять, что происходит. Суть кода в следующем:

Сначала мы определяем элемент, с которым собираемся работать, используя функцию getElementById() объектной модели документа. Обратившись к этому элементу, мы удаляем все пункты меню, указанные ранее. Мы проходим по списку потомков элемента, делая вызов простой DOM-функции removeChild, чтоб удалить все пункты меню.

Теперь, очистив меню, можно начать заполнять его новыми пунктами. В этом примере мы для начала создаем массив, содержащий пять поисковых запросов. Из него будут динамически загружаться пункты меню. Мы проходим по всему массиву и создаем пункт меню для каждого элемента, используя функцию createElement(). Функция setAttribute() нужна для того, чтоб установить атрибуты label и tooltiptext для каждого пункта меню. Наконец, при помощи функции appendChild() мы добавляем в меню только что созданный пункт.

Добавим получившуюся функцию в наш файл JavaScript, в результате чего он станет вот таким: [JS, вариант 2].

Единственное, о чем мы не сказали — как заставить каждый пункт меню выполнять какой-то код, будучи выбранным. Это делаеся очень просто: еще раз вызываем функцию setAttribute(), передаем ей событие и код, который хотим с ним связать. Вот пример для события oncommand:

tempItem.setAttribute("oncommand", "GBLTut_SomeFunction()");

Вот и все, что нужно для динамического заполнения меню! Штука очень полезная и легко реализуемая.

Динамически добавляем кнопки тулбара

(Эта тема не включена в код учебного тулбара)

Динамически добавлять кнопки так же просто, как заполнять меню, и делается это по той же схеме. Для начала нам нужен контейнер для наших кнопок. В Googlebar Lite я использую элемент toolbaritem, чтоб хранить набор кнопок с поисковыми запросами. В XUL это выглядит так:

<toolbaritem id="GBLTut-DynButtonContainer" />

Теперь можно добавить элементы toolbarbutton при помощи такой вот функции (в этом примере мы добавим 5 кнопок, так же, как сделали это с пунктами меню):

function GBLTut_AddDynamicButtons() {
    var container = document.getElementById("GBLTut-DynButtonContainer");
    
    for(i=container.childNodes.length; i > 0; i--) {
        container.removeChild(container.childNodes[0]);
    }

    for(var i=0; i<5; i++) {
        var tempButton = null;
        tempButton = document.createElement("toolbarbutton");
        tempButton.setAttribute("label", "Button " + i);
        tempButton.setAttribute("tooltiptext", "Button " + i);
        tempButton.setAttribute("oncommand", "GBLTut_SomeFunction()");
        container.appendChild(tempButton);
    }
}

Единственное отличие этой функции от использованной для заполнения меню — первый цикл. Я просто показываю другой способ удалять предыдущие элементы. Все остальное — также. Но вот события onpopupshowing у нас нет. Так как же нам эту функцию вызвать?

А просто. Всякий раз, когда данные обновляются, можно вызвать функцию создания кнопок. Например, по событию oninput, когда пользователь начинает вводить текст в поисковую форму. (Пример, опять же, бестолковый, однако показвает, что есть пространство для воображения — прим. пер.).

Динамически включаем-выключаем кнопки

(Эта тема не включена в код учебного тулбара)

Часто возникают ситуации, когда нужно динамически сделать кнопку тулбара доступной или недоступной. Например, кнопка подсветки в Googlbar Lite должна быть недоступна, если нет поисковых слов, которые надо подсветить. Как только такие слова появляются, кнопка подсветки должна автоматически становиться доступной.

В следующем примере мы создадим пункт меню, который будет переключать статус обычной кнопки между доступным и недоступным. Вот разметка:

<menuitem label="Toggle Web Button" tooltiptext="Toggle Web Button"
          oncommand="GBLTut_ToggleWebButton()" />

А вот код функции GBLTut_ToggleWebButton():

function GBLTut_ToggleWebButton() {
    var button = document.getElementById("GBLTut-TB-Web");
    var value = button.disabled;
    if(value == "true")
        button.disabled = false;
    else
        button.disabled = true;
}

Сначала получаем доступ к интересующей нас кнопке, используя getElementById(). Далее, выясняем ее текущее состояние, используя свойство disabled объекта button. Если оно установлено в true, меняем на false, и наоборот. FireFox сам делает кнопку недоступной, и теперь по ней хоть кликай, хоть не кликай — ничего не произойдет. (При этом картинка серой не сделается — об этом нужно позаботиться отдельно, работая со скином, об этом в следующем разделе). Полезная фишка, пригодится в любом тулбаре, а выполнить ее просто, как и все остальные.

Динамически прячем-показываем кнопки

(Эта тема не включена в код учебного тулбара)

Googlebar Lite позволяет пользователю выбирать, какие кнопки он хочет видеть на своем тулбаре. Т.е. тулбар дожен уметь динамически показывать-прятать кнопки. Как и предыдущие, эта возможность реализуется крайне просто. Вот кусок JavaScript-кода, который сделает все, что нужно:

var TB_Web = document.getElementById("GBLTut-TB-Web");
TB_Web.setAttribute("hidden", !GBLTut_TB_ShowWeb);

Я опять использую функцию getElementById(), чтоб получить интересующую меня кнопку. Потом вызываю функцию setAttribute, чтоб изменить значение атрибута hidden. Обратите внимание, я присваиваю ему значение, противоположенное значению переменной GBLTut_TB_ShowWeb. Это булева переменная, в которой хранится информация, хочет ли пользователь видеть кнопку "web search". Ее значение устанавливается где-то в коде Googlebar Lite, после чтения опции "Web Search Button" из пользовательских настроек. Читайте дальше, если интересно, как это делается.

Читаем и сохраняем настройки пользователя

(эта тема не включена в код учебного тулбара)

Для того, чтоб прочитать сохраненные настройки, нам нужно создать "переменную настроек". Вот кусок кода, который это делает (отформатирован, чтоб влезть в экран):

const GBLTut_PrefService =
      Components.
	    classes["@mozilla.org/preferences-service;1"].
		  getService(Components.interfaces.nsIPrefService);

Когда у нас есть эта переменная, мы можем получить доступ к ветке настроек для нашего расширения:

const GBLTut_Branch = GBLTut_PrefService.getBranch("gbl_tutorial.");

Все настройки нашего расширения начинаются с "gbl_tutorial." (обратите внимание на точку на конце), поэтому такое значение передается функции getBranch() (встроенная функция FireFox). Наконец, вот код, используемый для считывания пользовательских настроек:

if(GBLTut_Branch.prefHasUserValue("buttons.web"))
    GBLTut_TB_ShowWeb = GBLTut_Branch.getBoolPref("buttons.web");
else
{
    GBLTut_Branch.setBoolPref("buttons.web", false);
    GBLTut_TB_ShowWeb = false;
}

Сначала я проверяю, существует ли такая запись в дереве настроек. Если да, я использую функцию getBoolPref(), чтоб прочитать ее значение и сохранить в переменную GBLTut_TB_ShowWeb. Если нет, устанавливаю значение по умолчанию — и для дерева настроек, и для нашей глобальной переменной.

Сохранение настроек производится также, как их чтение. Вот код, записывающий только что считанное значение:

GBL_Branch.setBoolPref("buttons.web", GBLTut_TB_ShowWeb);

Чтение и запись настроек — удобный способ сохранения установок вашего тулбара. Большинство расширений используют эту возможность, так что существует полно рабочих примеров, которые вы можете изучить. В дополнение, существует великолепное расширенное пособие по настройкам в базе знаний MozillaZine.