Форум программистов, компьютерный форум, киберфорум
Наши страницы
diadiavova
Войти
Регистрация
Восстановить пароль
Оценить эту запись

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

Запись от diadiavova размещена 25.02.2016 в 13:32
Обновил(-а) diadiavova 12.04.2016 в 15:37

  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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<?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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/*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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
1
2
3
4
ReadTextFromFile("file:///C:/myfile.txt", "utf-8", function(data)
{
    alert(data);
});
Файл-диалог
Указывать файл вручную неудобно, так что неплохо было бы файл-диалогом разжиться.
nsIFilePicker - Mozilla | MDN

Опять-таки простой пример исползования
Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
1
gClipboard.write("Какой-то текст.");
Чтение текста из буфера
Javascript
1
alert(gClipboard.read());
Конфигурация Firefox

Preferences - Mozilla | MDN

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

Программно сбросить редактор можно так.
Javascript
1
2
3
4
    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
1
2
3
4
5
6
7
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
1
2
3
4
5
6
7
8
9
10
11
12
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
1
2
3
4
    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")
>>
Размещено в CustomButtons
Просмотров 767 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru