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

CustomButtons часть 4-я. Добавим оверлеи в кнопку

  1. Оверлеи
  2. Внедрение оверлея в код кнопки
  3. Схема XUL
Дополнять контекстное меню одним-двумя элементами можно и программно, как это было показано в предыдущей статье, но создавать код более-менее сложной структуры вручную - как-то не хочется. Кроме того, пример с контекстным меню был просто иллюстрацией возможностей, другие элементы интерфейса, которые нам могут понадобиться, скорей всего окажутся значительно сложнее меню. Можно было бы для каждого такого элемента интерфейса добавить элемент в XML-документ, который был создан для генерации меню кнопки, но платформа Mozilla предлагает более универсальный механизм - оверлеи.

Оверлеи



Документация по оверлеям находится здесь.
XUL Overlays - Mozilla | MDN

В двух словах: оверлей - это фрагмент XUL-кода интерфейса, который располагается в отдельном файле, но подгружается при загрузке отдельного окна. При этом его код, добавляется в элементы окна. Сам документ с корневым элементом overlay заполняется другими XUL-элементами, причем эти элементы должны иметь id такой, чтобы в основном документе также присутствовал такой же элемент с таким же id. При загрузке оверлея, содержимое такого элемента будет заполнено содержимым такого же элемента из оверлея. Иными словами: если нам надо добавить элемент в контекстное меню, как в предыдущей статье, то мы создаем оверлее <menupopup id="contenAreaContextMenu"> и вставляем все что нужно туда.

Вообще оверлеи загружаются вместе с документом, но есть способ сделать это программно, когда документ уже загружен.
Document.loadOverlay() - Интерфейсы веб API | MDN

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

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

<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <script type="application/x-javascript">

    <![CDATA[

    function correctVkLink_onclick ()

    {

        var menu = document.getElementById("contentAreaContextMenu");

        var ctx = menu.triggerNode;

        ctx = ctx.tagName == "A" ? ctx : ctx.parentElement

        var startHref = "https://vk.com/away.php?to=";

 

        if (ctx.tagName == "A" && ctx.href.startsWith(startHref))

        {

            var startLen = startHref.length;

            var url = ctx.href.substring(startLen, ctx.href.indexOf("&"));

            ctx.href = decodeURIComponent(url);

        }

    }

    

    var correctVkLink_onPopupShowing = function ()

    {

        var corItem = document.getElementById("correct_vk_outer_link");

        if (corItem)

        {

            var menu = document.getElementById("contentAreaContextMenu");

            var ctx = menu.triggerNode;

            ctx = ctx.tagName == "A" ? ctx : ctx.parentElement;

            var startHref = "https://vk.com/away.php?to=";

 

            if (ctx.tagName == "A" && ctx.href.startsWith(startHref))

            {

                corItem.setAttribute("hidden", "false");

            }

            else

            {

                corItem.setAttribute("hidden", "true");

            }

 

        }

 

    }

    ]]>

  </script>

  <menupopup id="contentAreaContextMenu" onpopupshowing="correctVkLink_onPopupShowing();">

    <menuitem label="Исправить ссылку vk" onclick="correctVkLink_onclick ()" id="correct_vk_outer_link"/>

  </menupopup>

</overlay>

Сохраняем файл с расширением .xul. Теперь создадим кнопку, для загрузки оверлея.
Кликните здесь для просмотра всего текста
Код 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;

    }

 

}

 

document.loadOverlay("file:///" + queryFileName(), null);

Жмем кнопку, выбираем только что созданный файл и получаем такой же результат как и раньше.

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

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

Внедрение оверлея в код кнопки



Сначала я хотел решить эту задачу, используя псевдооверлеи. То есть воспользоваться XML-описанием кнопки, описанным в одной из предыдущих статей, добавить туда блок overley и генерировать код, который будет создавать все элементы, описанные в нем, тем же способом, которым создавалась кнопка-меню. Недостаток этого способа в том, что при его реализации пришлось бы отказаться от некоторых возможностей оверлеев, например от возможности использования скриптов и on-атрибутов. Поэтому способ, который я выбрал - предельно прост: код оверлея будет включен в код кнопки как текст, а для того, чтобы его загрузить, он сначала будет сохраняться во временный файл, из которого его можно будет грузить с помощью document.loadOverlay. Таким образом задача сводится к созданию кнопки(транслятора), которая будет на основе оверлея генерировать код другой кнопки, способной этот оверлей сохранить и загрузить.

Вот код такой кнопки
Кликните здесь для просмотра всего текста
Код 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;

    }

 

}

 

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 saveAndLoadOverlay(code)

{

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

 

    var file = FileUtils.getFile("TmpD", ["temp_overlay_file.xul"]);

    file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);

    var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].

                   createInstance(Components.interfaces.nsIFileOutputStream);

    foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);

    var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"].

                    createInstance(Components.interfaces.nsIConverterOutputStream);

    converter.init(foStream, "UTF-8", 0, 0);

    converter.writeString(code);

    converter.close();

    document.loadOverlay("file:///" + file.path, null);

}

 

 

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

{

    var buttonCode = "var overlayCode = " + JSON.stringify(data) + ";" + saveAndLoadOverlay + ";saveAndLoadOverlay(overlayCode);";

    gClipboard.write(buttonCode);

});

Размещаем этот код во вкладке CODE новой кнопки(это и будет транслятор), жмем, в появившемся файл-диалоге выбираем файл оверлея и нужный код оказывается в буфере обмена. Теперь надо создать новую кнопку, разместить на ее вкладке инициализации полученный код из буфера обмена и дело в шляпе. Вот пример кода, который получится из приведенного выше кода оверлея
Кликните здесь для просмотра всего текста
Код javascript Выделить

var overlayCode = "<?xml version="1.0" encoding="utf-8" ?>\n<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">\n  <script type="application/x-javascript"><![CDATA[\n    function correctVkLink_onclick ()\n    {\n        var menu = document.getElementById("contentAreaContextMenu");\n        var ctx = menu.triggerNode;\n        ctx = ctx.tagName == "A" ? ctx : ctx.parentElement\n        var startHref = "https://vk.com/away.php?to=";\n \n        if (ctx.tagName == "A" && ctx.href.startsWith(startHref))\n        {\n            var startLen = startHref.length;\n            var url = ctx.href.substring(startLen, ctx.href.indexOf("&"));\n            ctx.href = decodeURIComponent(url);\n        }\n    }\n    \n    var correctVkLink_onPopupShowing = function ()\n    {\n        var corItem = document.getElementById("correct_vk_outer_link");\n        if (corItem)\n        {\n            var menu = document.getElementById("contentAreaContextMenu");\n            var ctx = menu.triggerNode;\n            ctx = ctx.tagName == "A" ? ctx : ctx.parentElement;\n            var startHref = "https://vk.com/away.php?to=";\n \n            if (ctx.tagName == "A" && ctx.href.startsWith(startHref))\n            {\n                corItem.setAttribute("hidden", "false");\n            }\n            else\n            {\n                corItem.setAttribute("hidden", "true");\n            }\n \n        }\n \n    }\n    ]]></script>\n  <menupopup id="contentAreaContextMenu" onpopupshowing="correctVkLink_onPopupShowing();">\n    <menuitem label="Исправить ссылку vk" onclick="correctVkLink_onclick ()" id="correct_vk_outer_link"/>\n  </menupopup>\n</overlay>";function saveAndLoadOverlay(code)

    {

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

 

        var file = FileUtils.getFile("TmpD", ["temp_overlay_file.xul"]);

        file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);

        var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].

                      createInstance(Components.interfaces.nsIFileOutputStream);

        foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);

        var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"].

                        createInstance(Components.interfaces.nsIConverterOutputStream);

        converter.init(foStream, "UTF-8", 0, 0);

        converter.writeString(code);

        converter.close();

        document.loadOverlay("file:///" + file.path, null);

    };saveAndLoadOverlay(overlayCode);

 

Схема XUL

Вопрос не принципиальный, но все-таки при наличии схемы создавать оверлеи было бы намного удобнее. Существует вот такой проект
XUL Schema
Нельзя сказать, что это полноценная схема, но она вполне юзабельна.

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

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