пятница, 8 апреля 2016 г.

CustomButtons часть 2-я. Упрощение процесса создания кнопки-меню в CustomButtons

  1. Создаем инструменты
  2. Структура XML-документа
  3. Как все описать, чтобы не запутаться
  4. И что же теперь со всем этим делать?
  5. Несколько слов о коде кнопок

Создаем инструменты



В предыдущей статье я уже описал, как создается кнопка меню, в принципе к структуре кода добавить нечего. Но тут проблема в том, что создание XML-дерева в программном коде - процесс малоприятный и как правило результат содержит массу ошибок, по крайней мере если дерево имеет более-менее сложную структуру и достаточно много элементов. Естественно описывать подобные структуры с помощью XML - намного удобнее, но посредством имеющихся у нас возможностей просто так взять и запихнуть XML-код в нашу кнопку не получится, а значит надо придумать что-то другое. Я эту проблему решил следующим образом: создал простой XML-формат, в котором содержится код, а также XUL-структура кнопки. Кроме того написал код для кнопки-транслятора, которая загружает готовый XML-документ, генерирует из него JavaScript-код инициализации новой кнопки и вставляет его в буфер обмена. Дальше просто создаем новую кнопку, в окне редактирования открываем вкладку Initialization и вставляем туда код из буфера. Задаем кнопке имя, иконку, сохраняем, добавляем на панель с кнопками и можно пользоваться.

Теперь по порядку.

Структура XML-документа



Корневой элемент menu-button, в него вложены три элемента code-before, button и code-after. Первый будет содержать JavaScript-код, предшествующий инициализации кнопки, содержимое второго будет повторять структуру содержимого кнопки и из него будет генерироваться JavaScript-код инициализации меню. В последнем будет код, расположенный после инициализации, в нем надо размещать подписки на события элементов меню.

Пример документа
Кликните здесь для просмотра всего текста
Код xml Выделить

<?xml version="1.0" encoding="utf-8" ?>

<menu-button xmlns="urn:diadiavova-schemas:menu-button" >

  <code-before>

    <![CDATA[

function ael(id, evtName, handler){document.getElementById(id).addEventListener(evtName, handler, false);};

function aeln(name, clickhandler){thisbutton.querySelector('*[name="' + name + '"]').addEventListener("click", clickhandler, false);};

function aeldi(di, clickhandler){thisbutton.querySelector('*[data-id="' + di + '"]').addEventListener("click", clickhandler, false);}

  ]]>

  </code-before>

  <button>

    <menupopup>

      <menuitem id="mi1" label="Cyberforum" tooltiptext="Открывает главную страницу киберфорума" image="http://www.cyberforum.ru/favicon.ico"/>

      <menuitem label="Пункт меню с флажком" autocheck="true" type="checkbox" checked="true" data-id="check-item"/>

      <menuitem label="Что с флажком?" data-id="check-query"/>

      <menu label="Светофор">

        <menupopup>

          <menuitem label="Красный" style="color:red;" autocheck="true" type="radio" name="trafficlight"/>

          <menuitem label="Желтый" style="color:yellow;" autocheck="true" type="radio" name="trafficlight"/>

          <menuitem label="Зеленый" style="color:green;" autocheck="true" type="radio" name="trafficlight"/>

        </menupopup>

      </menu>

      <menu label="Вложенное меню">

        <menupopup>

          <menuitem label="item 2" id="mi2"/>

          <menuitem label="item 3" id="mi3"/>

          <menu label="Глубоко вложенное меню">

            <menupopup>

              <menuitem label="subitem 1" name="si1"/>

              <menuitem label="subitem 2" name="si2"/>

            </menupopup>

          </menu>

        </menupopup>

      </menu>

    </menupopup>

  </button>

  <code-after>

    <![CDATA[

ael("mi1", "click", function(evt){loadURI("http://www.cyberforum.ru/")});

ael("mi2", "click", function(evt){alert("item 2 clicked")});

ael("mi3", "click", function(evt){alert("item 3 clicked")});

aeln('si1', function(evt){alert('subitem 1 clicked');});

aeln('si2', function(evt){alert('subitem 2 clicked');});

aeldi("check-query",

    function(evt)

    {

        var ft = (thisbutton.querySelector("menuitem[data-id='check-item']").getAttribute("checked") == "true") ? "установлен" : "не установлен";

        alert("Флажок " + ft);

    });

]]>

  </code-after>

</menu-button>


Здесь требуются кое-какие пояснения. В первом блоке кода (code-before) я определил три функции, все они предназначены для подписки на события.
ael - добавляет обработчик события к элементу с заданным id, причем ищет его по всему документу
id - значение атрибута id искомого элемента
evtName - имя события, на которое надо подписаться
handler - функция-обработчик

aeln - добавляет обработчик события click эелементу с заданным атрибутом name, но поиск осуществляется в пределах данной кнопки. Это может быть полезно для того, чтобы не использовать лишний раз, поскольку этот атрибут должен быть уникальным в пределах документа.

aeldi - то же самое, что и предыдущий вариант, только вместо атрибута name, который может использоваться для других целей (как это показано в меню Светофор), используется специально выдуманный атрибут data-id.

В блоке code-after выполняется подписка путем вызова этих функций.

Внутри элемента button - обычный XUL-код нашего меню. Основные возможности здесь показаны. "Пункт меню с флажком" устанавливает и удаляет флажок при клике, а следующий за ним пункт показывает состояние. Это просто демонстрация того, как можно использовать флажки и проверять их значение программно. На примере меню "Светофор" показана работа меню типа "radio" ну и применение стилей. Остальное особых вопросов вызвать не должно.

Как все описать, чтобы не запутаться



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

Вот код схемы, в соответствии с которой составлен представленный выше документ.
Кликните здесь для просмотра всего текста
Код xml Выделить

<?xml version="1.0" encoding="utf-8"?>

<xs:schema id="MultyButton"

    targetNamespace="urn:diadiavova-schemas:menu-button"

    elementFormDefault="qualified"

           attributeFormDefault="unqualified"

    xmlns="urn:diadiavova-schemas:menu-button"

    xmlns:mstns="urn:diadiavova-schemas:menu-button"

    xmlns:xs="http://www.w3.org/2001/XMLSchema"

> 

  <xs:element name="menu-button">

    <xs:complexType>

      <xs:sequence>

        <xs:element name="code-before" type="string-element-content"/>

        <xs:element name="button">

          <xs:complexType>

            <xs:sequence>

              <xs:element name="menupopup" type="menupopupDef"/>

            </xs:sequence>

            <xs:attribute name="accesskey" type="accesskeyDef"/>

            <xs:attribute name="autocheck" type="xs:boolean"/>

            <xs:attribute name="checkState" >

              <xs:simpleType>

                <xs:restriction base="xs:int">

                  <xs:enumeration value="0"/>

                  <xs:enumeration value="1"/>

                  <xs:enumeration value="2"/>

                </xs:restriction>

              </xs:simpleType>

            </xs:attribute>

            <xs:attribute name="checked" type="xs:boolean"/>

            <xs:attribute name="command" type="xs:string"/>

            <xs:attribute name="crop" type="cropDef"/>

            <xs:attribute name="dir">

              <xs:simpleType>

                <xs:restriction base="xs:string">

                  <xs:enumeration value="normal"/>

                  <xs:enumeration value="reverse"/>

                </xs:restriction>

              </xs:simpleType>

            </xs:attribute>

            <xs:attribute name="disabled" type="xs:boolean"/>

            <xs:attribute name="dlgtype">

              <xs:simpleType>

                <xs:restriction base="xs:string">

                  <xs:enumeration value="accept"/>

                  <xs:enumeration value="cancel"/>

                  <xs:enumeration value="help"/>

                  <xs:enumeration value="disclosure"/>

                  <xs:enumeration value="extra1"/>

                  <xs:enumeration value="extra2"/>

                </xs:restriction>

              </xs:simpleType>

            </xs:attribute>

            <xs:attribute name="group" type="xs:string"/>

            <xs:attribute name="icon" type="xs:anyURI"/>

            <xs:attribute name="image" type="xs:anyURI"/>

            <xs:attribute name="label" type="xs:string"/>

            <xs:attribute name="open" type="xs:boolean"/>

            <xs:attribute name="orient">

              <xs:simpleType>

                <xs:restriction base="xs:string">

                  <xs:enumeration value="horizontal"/>

                  <xs:enumeration value="vertical"/>

                </xs:restriction>

              </xs:simpleType>

            </xs:attribute>

            <xs:attribute name="tabindex" type="xs:int"/>

            <xs:attribute name="type">

              <xs:simpleType>

                <xs:restriction base="xs:string">

                  <xs:enumeration value="checkbox"/>

                  <xs:enumeration value="menu"/>

                  <xs:enumeration value="menu-button"/>

                  <xs:enumeration value="panel"/>

                  <xs:enumeration value="radio"/>

                  <xs:enumeration value="repeat"/>

                </xs:restriction>

              </xs:simpleType>

            </xs:attribute>

          </xs:complexType>

        </xs:element>

        <xs:element name="code-after" type="string-element-content" minOccurs="0"/>

      </xs:sequence>

    </xs:complexType>

  </xs:element>

  <xs:complexType name="menupopupDef">

    <xs:choice maxOccurs="unbounded">

      <xs:element name="menuitem" type="menuitemDef"/>

      <xs:element name="menuseparator" type="menuseparatorDef"/>

      <xs:element name="menu" type="menuDef"/>

    </xs:choice>

    <xs:attribute name="ignorekeys"/>

    <xs:attribute name="left"/>

    <xs:attribute name="onpopuphidden"/>

    <xs:attribute name="onpopuphiding"/>

    <xs:attribute name="onpopupshowing"/>

    <xs:attribute name="onpopupshown"/>

    <xs:attribute name="position"/>

    <xs:attribute name="top"/>

    <xs:attributeGroup ref="common-attributes"/>

  </xs:complexType>

   

  <xs:complexType name="menuDef">

    <xs:all>

      <xs:element name="menupopup" type="menupopupDef"/>

    </xs:all>

    <xs:attribute name="acceltext" type="xs:string"/>

    <xs:attribute name="accesskey" type="accesskeyDef"/>

    <xs:attribute name="allowevents" type="xs:boolean"/>

    <xs:attribute name="command" type="xs:string"/>

    <xs:attribute name="crop" type="cropDef"/>

    <xs:attribute name="disabled" type="xs:boolean"/>

    <xs:attribute name="image" type="xs:anyURI"/>

    <xs:attribute name="label" type="xs:string"/>

    <xs:attribute name="menuactive" type="xs:boolean"/>

    <xs:attribute name="open" type="xs:boolean"/>

    <xs:attribute name="sizetopopup">

      <xs:simpleType>

        <xs:restriction base="xs:string">

          <xs:enumeration value="none"/>

          <xs:enumeration value="prefs"/>

          <xs:enumeration value="always"/>

        </xs:restriction>

      </xs:simpleType>

    </xs:attribute>

    <xs:attribute name="tabindex" type="xs:int"/>

    <xs:attribute name="value" type="xs:string"/>

    <xs:attributeGroup ref="common-attributes"/>

  </xs:complexType>

   

  <xs:complexType name="menuitemDef">

    <xs:attribute name="acceltext" type="xs:string"/>

    <xs:attribute name="accesskey" type="accesskeyDef"/>

    <xs:attribute name="allowevents" type="xs:boolean"/>

    <xs:attribute name="autocheck" type="xs:boolean"/>

    <xs:attribute name="checked" type="xs:boolean"/>

    <xs:attribute name="closemenu">

      <xs:simpleType>

        <xs:restriction base="xs:string">

          <xs:enumeration value="auto"/>

          <xs:enumeration value="single"/>

          <xs:enumeration value="none"/>

        </xs:restriction>

      </xs:simpleType>

    </xs:attribute>

    <xs:attribute name="command" type="xs:string"/>

    <xs:attribute name="crop" type="cropDef"/>

    <xs:attribute name="description" type="xs:string"/>

    <xs:attribute name="disabled" type="xs:boolean"/>

    <xs:attribute name="image" type="xs:anyURI"/>

    <xs:attribute name="key" type="xs:IDREF"/>

    <xs:attribute name="label" type="xs:string"/>

    <xs:attribute name="name" type="xs:string"/>

    <xs:attribute name="selected" type="xs:boolean"/>

    <xs:attribute name="tabindex" type="xs:int"/>

    <xs:attribute name="type">

      <xs:simpleType>

        <xs:restriction base="xs:string">

          <xs:enumeration value="checkbox"/>

          <xs:enumeration value="radio"/>

        </xs:restriction>

      </xs:simpleType>

    </xs:attribute>

    <xs:attribute name="validate">

      <xs:simpleType>

        <xs:restriction base="xs:string">

          <xs:enumeration value="always"/>

          <xs:enumeration value="never"/>

        </xs:restriction>

      </xs:simpleType>

    </xs:attribute>

    <xs:attribute name="value" type="xs:string"/>

    <xs:attributeGroup ref="common-attributes"/>

  </xs:complexType>

   

  <xs:complexType name="menuseparatorDef">

    <xs:attribute name="acceltext"/>

    <xs:attribute name="accesskey"/>

    <xs:attribute name="allowevents"/>

    <xs:attribute name="command"/>

    <xs:attribute name="crop"/>

    <xs:attribute name="disabled"/>

    <xs:attribute name="image"/>

    <xs:attribute name="label"/>

    <xs:attribute name="selected"/>

    <xs:attribute name="tabindex"/>

    <xs:attribute name="value"/>

    <xs:attributeGroup ref="common-attributes"/>

  </xs:complexType>

   

   

  <xs:simpleType name="string-element-content">

    <xs:restriction base="xs:string"/>

  </xs:simpleType>

   

   

  <xs:simpleType name="accesskeyDef">

    <xs:restriction base="xs:string">

      <xs:length value="1"/>

    </xs:restriction>

  </xs:simpleType>

   

  <xs:simpleType name="cropDef">

    <xs:restriction base="xs:string">

      <xs:enumeration value="start"/>

      <xs:enumeration value="end"/>

      <xs:enumeration value="center"/>

      <xs:enumeration value="left"/>

      <xs:enumeration value="right"/>

      <xs:enumeration value="none"/>

    </xs:restriction>

  </xs:simpleType>

   

  <xs:attributeGroup name="common-attributes">

    <xs:attribute name="id" type="xs:string"/>

    <xs:attribute name="tooltiptext" type="xs:string"/>

    <xs:attribute name="style" type="xs:string"/>

    <xs:attribute name="data-id" type="xs:string"/>

  </xs:attributeGroup>

   

</xs:schema>

Я не стремился предусмотреть в этой схеме все, прото нужно было создать такую схему, которая бы упростила задачу создания XML. На данный момент это неплохо работает.

И что же теперь со всем этим делать?



Теперь нам потребуется еще одна кнопка, которую можно создать стандартными средствами и в редакторе кода которой, на вкладке Code мы разместим следующий код.
Кликните здесь для просмотра всего текста
Код javascript Выделить

/*CODE*/

 

function queryFileName()

{

    const nsIFilePicker = Components.interfaces.nsIFilePicker;

    var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);

    fp.init(window, "Dialog Title", nsIFilePicker.modeOpen);

    fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterXML);

    var rv = fp.show();

    if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace)

    {

        var file = fp.file;

        // Get the path as string. Note that you usually won't

        // need to work with the string paths.

        var path = fp.file.path;

        // work with returned nsILocalFile...

 

        return path;

    }

 

}

 

function ReadTextFromFile(fileName, charset, callback)

{

 

    var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);

    file.initWithPath(fileName);

    Components.utils.import("resource://gre/modules/NetUtil.jsm");

 

    var channel = NetUtil.newChannel(file);

    channel.contentType = "application/xml";

 

    NetUtil.asyncFetch(channel, function (inputStream, status)

    {

        callback(NetUtil.readInputStreamToString(inputStream, file.fileSize, { charset: charset }));

    });

 

}

 

function createGen(domElement)

{

    /// <param name="domElement" type="Element">Description</param>

    function createGenerator(domElement)

    {

        /// <param name="domElement" type="Element">Description</param>

        var result = "element = cont.ownerDocument.createElement('" + domElement.tagName + "');";

        result += "if(stack.length>0){stack.last().appendChild(element);}else{cont.appendChild(element)};";

        result += "stack.push(element);"

        var atts = domElement.attributes;

        for (var i = 0; i < atts.length; i++)

        {

            if (atts[i].specified)

            {

                var attname = atts[i].name;

                if (atts[i].name == 'image')

                {

                    result += "stack.last().classList.add('menuitem-iconic');";

                }

                result += "stack.last().setAttribute('" + atts[i].name + "','" + atts[i].value + "');";

            }

        }

 

        var chnodes = domElement.childNodes;

        for (var i = 0; i < chnodes.length; i++)

        {

            if (chnodes[i].nodeType == 1)

            {

                result += createGenerator(chnodes[i], result);

            }

            else if (chnodes[i].nodeType == 3 && chnodes[i].nodeValue.trim().length > 0)

            {

                result += "stack.last().appendChild(cont.ownerDocument.createTextNode(" + JSON.stringify(chnodes[i].nodeValue) + "));";

            }

        }

        result += "stack.pop();";

        return result

    }

    var result = "function generateCode(cont){var element;var stack=[];stack.last = function(){return this[this.length - 1];};"

    result += createGenerator(domElement, "") + "}";

    return result;

}

 

var initText = "this.type='menu-button';var thisbutton=this;"

 

function loadXml()

{

    ReadTextFromFile(queryFileName(), "utf-8", function (data)

    {

        var parser = new DOMParser();

        var doc = parser.parseFromString(data, "application/xml");

        initText += doc.querySelector("code-before").textContent;

        initText += createGen(doc.querySelector("button menupopup")) + "generateCode(this);";

        initText += doc.querySelector("code-after").textContent;

        gClipboard.write(initText);

    });

}

loadXml();

Назвать кнопку можно как угодно и иконку выбрать тоже можно любую. Сохраняем, добавляем на панель, запускаем. Появляется файлпикер, выбираем наш XML-файл и вуаля - код новой кнопки скопирован в буфер обмена. Теперь уже создаем ту самую кнопку, ради которой все и затевалось, в редакторе кода во вкладке Initialization размещаем код из буфера обмена. Сохраняем, размещаем, наслаждаемся.

Код, полученный из представленного документа выглядит так
Кликните здесь для просмотра всего текста
Код javascript Выделить

this.type = 'menu-button'; var thisbutton = this;

 

function ael(id, evtName, handler) { document.getElementById(id).addEventListener(evtName, handler, false); };

function aeln(name, clickhandler) { thisbutton.querySelector('*[name="' + name + '"]').addEventListener("click", clickhandler, false); };

function aeldi(di, clickhandler) { thisbutton.querySelector('*[data-id="' + di + '"]').addEventListener("click", clickhandler, false); }

 

function generateCode(cont) { var element; var stack = []; stack.last = function () { return this[this.length - 1]; }; element = cont.ownerDocument.createElement('menupopup'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('class', 'menuitem-iconic'); stack.last().setAttribute('id', 'mi1'); stack.last().setAttribute('label', 'Cyberforum'); stack.last().setAttribute('tooltiptext', 'Открывает главную страницу киберфорума'); stack.last().classList.add('menuitem-iconic'); stack.last().setAttribute('image', 'http://www.cyberforum.ru/favicon.ico'); stack.pop(); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'Пункт меню с флажком'); stack.last().setAttribute('autocheck', 'true'); stack.last().setAttribute('type', 'checkbox'); stack.last().setAttribute('checked', 'true'); stack.last().setAttribute('data-id', 'check-item'); stack.pop(); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'Что с флажком?'); stack.last().setAttribute('data-id', 'check-query'); stack.pop(); element = cont.ownerDocument.createElement('menu'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'Светофор'); element = cont.ownerDocument.createElement('menupopup'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'Красный'); stack.last().setAttribute('style', 'color:red;'); stack.last().setAttribute('autocheck', 'true'); stack.last().setAttribute('type', 'radio'); stack.last().setAttribute('name', 'trafficlight'); stack.pop(); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'Желтый'); stack.last().setAttribute('style', 'color:yellow;'); stack.last().setAttribute('autocheck', 'true'); stack.last().setAttribute('type', 'radio'); stack.last().setAttribute('name', 'trafficlight'); stack.pop(); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'Зеленый'); stack.last().setAttribute('style', 'color:green;'); stack.last().setAttribute('autocheck', 'true'); stack.last().setAttribute('type', 'radio'); stack.last().setAttribute('name', 'trafficlight'); stack.pop(); stack.pop(); stack.pop(); element = cont.ownerDocument.createElement('menu'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'Вложенное меню'); element = cont.ownerDocument.createElement('menupopup'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'item 2'); stack.last().setAttribute('id', 'mi2'); stack.pop(); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'item 3'); stack.last().setAttribute('id', 'mi3'); stack.pop(); element = cont.ownerDocument.createElement('menu'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'Глубоко вложенное меню'); element = cont.ownerDocument.createElement('menupopup'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'subitem 1'); stack.last().setAttribute('name', 'si1'); stack.pop(); element = cont.ownerDocument.createElement('menuitem'); if (stack.length > 0) { stack.last().appendChild(element); } else { cont.appendChild(element) }; stack.push(element); stack.last().setAttribute('label', 'subitem 2'); stack.last().setAttribute('name', 'si2'); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); } generateCode(this);

 

ael("mi1", "click", function (evt) { loadURI("http://www.cyberforum.ru/") });

ael("mi2", "click", function (evt) { alert("item 2 clicked") });

ael("mi3", "click", function (evt) { alert("item 3 clicked") });

aeln('si1', function (evt) { alert('subitem 1 clicked'); });

aeln('si2', function (evt) { alert('subitem 2 clicked'); });

aeldi("check-query", function (evt)

{

    var ft = (thisbutton.querySelector("menuitem[data-id='check-item']").getAttribute("checked") == "true") ? "установлен" : "не установлен";

    alert("Флажок " + ft);

});



Несколько слов о коде кнопок



Главное отличие кода, написанного для этих кнопок от кода, используемого в букмарклетах или Greasemonkey или даже на веб-странице - это контекст. Здесь так же как и там сослаться на глобальный объект можно с помощью слова window, вот только значение у него немного другое. И с документом та же проблема - document ссылается вовсе не на документ страницы. Поэтому, естественно, возникает вопрос: "Kак обратиться объектам страницы?", - ведь в конечном итоге, все затевается именно ради возможности взаимодействовать со страницей. Простейший способ достучаться до страницы - использование объекта content (или window.conent), это ссылка на объект window страницы загруженной в активную вкладку браузера. Через него можно получить доступ ко всем остальным объектам: content.document - будет ссылаться на объект document этой страницы; а content.varname - на тот же объект, что и переменная varname, объявленная на этой странице.

Знание слова content вполне решает проблему доступа к содержимому страницы, так что останавливаться на нем не вижу особого смысла, гораздо интереснее посмотреть, что можно сделать из того, что нельзя из кода веб-страницы, но можно из привилегированного кода.

Полезные ссылки

Для начала несколько ссылок.
НОУ ИНТУИТ | Разработка приложений с помощью Mozilla | Информация
Хорошая книга о платформе, на русском языке не видел ничего лучше. Но кое-что из написанного уже устарело, так что надо сверяться с документацией. Например, там предлагается установить мозиллу для запуска stanalone-приложений, но этого проекта уже давным-давно не существует, точнее он переименован и теперь называется XulRunner, если этого не знать, то можно много времени потратить на поиски. Но по архитектуре платформы большинство информации еще актуально. Требуется авторизация на сайте, но книгу можно найти и в других местах, например здесь
Программирование - N.McFarlane: Приложения на XUL с использованием Mozilla

Документация по XUL
XUL - Mozilla | MDN
Информация об элементах XUL
XUL Reference - Mozilla | MDN

Описание основных технологий
XUL Tutorial - Mozilla | MDN

Документация по XPCOM (масса дополнительных возможностей появляется именно при использовании этих объектов).
XPCOM - Mozilla | MDN

Список интерфейсов(там в описаниях есть примеры кода)
XPCOM Interface Reference - Mozilla | MDN

Ну и несколько важных примеров.
Файлы
О доступе к файловой системе можно прочитать здесь.
File I/O - Mozilla | MDN

Чтобы сразу дать рабочий пример, приведу код, читающий текст из файла
Код javascript Выделить

function ReadTextFromFile(fileName, charset, callback)

{

    var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);

    file.initWithPath(fileName);

    Components.utils.import("resource://gre/modules/NetUtil.jsm");

 

    var channel = NetUtil.newChannel(file);

    channel.contentType = "application/xml";

 

    NetUtil.asyncFetch(channel, function (inputStream, status)

    {

        callback(NetUtil.readInputStreamToString(inputStream, file.fileSize, { charset: charset }));

    });

}

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

Код javascript Выделить

ReadTextFromFile("file:///C:/myfile.txt", "utf-8", function (data)

{

    alert(data);

});

Файл-диалог
Указывать файл вручную неудобно, так что неплохо было бы файл-диалогом разжиться.
nsIFilePicker - Mozilla | MDN

Опять-таки простой пример исползования
Код javascript Выделить

function queryFileName()

{

    const nsIFilePicker = Components.interfaces.nsIFilePicker;

    var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);

    fp.init(window, "Dialog Title", nsIFilePicker.modeOpen);

    fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterXML);

    var rv = fp.show();

    if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace)

    {

        var file = fp.file;

        // Get the path as string. Note that you usually won't

        // need to work with the string paths.

        var path = fp.file.path;

        // work with returned nsILocalFile...

 

        return path;

    }

}

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

Буфер обмена

Using the clipboard - Mozilla | MDN

Использование буфера обмена так, как это описано по ссылке выше - немного муторно. В большинстве случаев требуется записать текст в буфер или получить текст из буфера. Если нужно только это, то это можно реализовать проще.
Запись текста в буфер
Код javascript Выделить

gClipboard.write("Какой-то текст.");

Чтение текста из буфера
Код javascript Выделить
alert(gClipboard.read());
Конфигурация Firefox

Preferences - Mozilla | MDN

Ну опять-таки реальный пример. Я уже писал о том, что есть у CustomButtons проблема с назначением внешнего редактора кода: если один раз его назначить, то уже на другой не поменяешь. Сбросить редактор или назначить новый можно в конфигурации. Перейти на страницу about:config и там найти ветвь extensions.custombuttons.external_editor и там либо удалить значение и получить таким образом возможность задать новый редактор при попытке вызова внешнего редактора, либо указать адрес исполняемого файла той программы, которую надо назначить внешним редактором.

Программно сбросить редактор можно так.
Код javascript Выделить

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

ps = ps.QueryInterface(Components.interfaces.nsIPrefBranch);

var cbps = ps.getBranch("extensions.custombuttons.");

cbps.clearUserPref("external_editor");

Установить другой редактор программно можно с помощью такой функции.
Код javascript Выделить

function setExternalEditor(exeFileName)

{

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

    ps = ps.QueryInterface(Components.interfaces.nsIPrefBranch);

    var cbps = ps.getBranch("extensions.custombuttons.");

    cbps.setCharPref("external_editor", exeFileName);

}

Реестр windows

Accessing the Windows Registry Using XPCOM | MDN

Небольшой пример
Код javascript Выделить

function getVSPath(version)

{

    /// <summary>Возвращает путь к каталогу установки заданной версии Visual Studio</summary>

    /// <param name="version" type="String">Версия Visual Studio. Должна быть указана в полном формате, например: 11.0</param>

    /// <returns type="String" />

    var v7 = "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7";

    var wrk = Components.classes["@mozilla.org/windows-registry-key;1"].createInstance(Components.interfaces.nsIWindowsRegKey);

    wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, v7, wrk.ACCESS_READ);

    var result = wrk.readStringValue(version);

    wrk.close();

    return result;

}

Можно эту функцию использовать для получения пути к испольняемому файлу Visual Studio заданной версии и использовать этот путь для установки внешнего редактора.
Код javascript Выделить

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

ps = ps.QueryInterface(Components.interfaces.nsIPrefBranch);

var cbps = ps.getBranch("extensions.custombuttons.");

cbps.setCharPref("external_editor", getVSPath("11.0") + "Common7\\IDE\\devenv.exe");

Комментариев нет :

Отправить комментарий