「キーワードの追加・削除」機能の実装

今回も、Thunderbird用アドオンのSubjectHelperの改造をする。

実装後のファイル構成はこんな感じ。

subjecthelper
 ├ install.rdf
 ┠ chrome.manifest
 ┠ defaults
 │ └ preferences
 │  └ preferences.js
 └ content
   ┠ overlay.xul
   ┠ subjectHelper.css
   ┠ subjectHelper.xml
   ┠ subjectHelper.js
   └ img
    └ icon.png

defaults配下を追加、contentのsubjectHelper.js、overlay.xulを変更した。

キーワードの保持方法

今回は、キーワードを保持するために、Mozilla設定システムを利用した。

Mozilla設定システムとは、FireFoxのアドレスバーに「about:config」を入力した際に、表示されるアレのこと。

今回追加したdefaults配下のpreferences.jsは、初回起動時に、初期値を設定するための定義が記述されている。

設定した初期値はこんな感じ。

pref( "extensions.subjecthelper.keyword.list", "要返信,年休連絡,時間外" );

※これは、[編集]->[設定]->[詳細]->「設定エディタ] から確認できる。

この「extensions.subjecthelper.keyword.list」の値を書き換えることで、キーワードの追加・削除を行う。

overlay.xulの修正

修正後のoverlay.xulは以下のような感じ。

<?xml version="1.0"?>

<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <script type="application/x-javascript" src="chrome://subjecthelper/content/subjectHelper.js"/>

  <textbox id="msgSubject">
    <button popup="_child" type="menu" id="subject-helper-button"
     style="-moz-binding: url('chrome://subjecthelper/content/subjectHelper.xml#subjectHelper');">
      <menupopup>
        <menuitem label="設定" accesskey="0" oncommand="SubjectHelper.setMenu();"/>
      </menupopup>
    </button>
  </textbox>
</overlay>

Mozilla設定システムにキーワードを保持しているため、予めmenuitem要素で記述していた既存のキーワードを除去した。

そのかわりに、Mozilla設定システムのキーワードを操作するための関数をcommandイベントに付加したmenuitem要素を追加した。

subjectHelper.jsの修正

今回の実装では、SubjectHelperオブジェクトに以下の関数を追加した。
・createMenu
・createMenuItem
・getKeywordList
・init
・setKeywordList
・setMenu

まず、取得したキーワードでメニューを新しく作成するcreateMenu。

    createMenu: function( keywordList ) {
        const delimiter = ",";

        var button = document.getElementById( "subject-helper-button" );
        var menu   = button.firstChild;

        // メニューの削除
        for( var index = menu.childNodes.length - 1; index >= 1; index-- ) {
            menu.removeChild( menu.childNodes[index] );
        }

        // メニューの追加
        var keywords = keywordList.split( delimiter );

        for( index in keywords ) {
            // 前後の空白を削除
            keywords[index] = keywords[index].replace( /^\s+|\s+$/g, "" );

            if( keywords[index].length > 0 ) {
                var menuItem = this.createMenuItem( keywords[index] );

                menu.appendChild( menuItem );
            }
        }
    }

メニューの削除部分は、わざわざ後ろから順番に消去しているが、これはコードの可読性を考慮している。

これを前から順番に消していく処理にすると、

        for( var index = 1; index <= menu.childNodes.length - 1; index++ ) {
            menu.removeChild( menu.childNodes[1] );
        }

となり、一目ではmenu.childNodes[1]あたり何をしているかわかりにくい。

JavaScriptにあまり慣れていないせいかもしれないが、removeChildによるダルマ落とし的な削除では、forをインクリメントで回す場合、どうしてもこういう書き方になる。

インデックスが1の要素を消していくより、後ろから順に消していく方が、自分としてはわかりやすかったので、そうした。

次は、menuitem要素を作成するcreateMenuItem関数。

    createMenuItem: function( keyword ) {
        var menuItem = document.createElement( "menuitem" );

        menuItem.setAttribute( "label", keyword );
        menuItem.setAttribute( "oncommand", "SubjectHelper.insert( this );" );

        return menuItem;
    }

これは、名前のまんま、引数で与えられたキーワードをラベルとしてmenuitem要素を作成する。

次、getKeywordListはMozilla設定システムからキーワードを取得する関数で中身はこんな感じ。

    getKeywordList: function() {
        var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                        .getService( Components.interfaces.nsIPrefService );

        prefs = prefs.getBranch( "extensions.subjecthelper." );

        var keywordList = prefs.getComplexValue( "keyword.list",
                            Components.interfaces.nsISupportsString ).data;

        return keywordList;
    }

initは、メール作成画面起動時にメニューを作成するための関数。

    init: function() {
        var keywordList = this.getKeywordList();

        this.createMenu( keywordList );
    }

setKeywordListは、Mozilla設定システムにキーワードを設定するための関数。

    setKeywordList: function( keywordList ) {
        var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                        .getService( Components.interfaces.nsIPrefService );

        prefs = prefs.getBranch( "extensions.subjecthelper." );

        var str = Components.classes["@mozilla.org/supports-string;1"]
                    .createInstance( Components.interfaces.nsISupportsString );

        str.data = keywordList;

        prefs.setComplexValue( "keyword.list"
                                , Components.interfaces.nsISupportsString, str );
    }

setMenuは、メニューの「設定」を押した場合に、キーワードを入力するダイアログを表示させる関数。

    setMenu: function() {
        var keywordList = this.getKeywordList();

        keywordList = prompt( "プロジェクト名をカンマ区切りで設定", keywordList );

        this.createMenu( keywordList );

        this.setKeywordList( keywordList );
    }

以上、6つの関数を追加した。

ソースをgithubに公開しようと思ったけど、フォークやらライセンスやら、わからないことがあるので、やめとく。

前よりは、コードをきれいにかけた気がする・・・気のせいかもしれない。

addEventListenerの引数の部分で、「function(){SubjectHelper.init();}」と書く場合と、「SubjectHelper.init」と書く場合で、挙動が違い、なぜ違うのかイマイチわからない。なんでだろ?


JavaScript難しい><

参考

Preferences/MDN