本日は Drupal ドキュメント日本語翻訳シリーズの一環で Drupal.org の「 Drupal JavaScript API 」というページを翻訳してご紹介できればと思います。

JavaScript は現代のウェブの「共通言語」です。 今日のウェブ開発・ホームページ制作において「 JavaScript コードを一切見ずにプロジェクトが完了する」なんてことはまれで、サーバサイドや他の部分に使う言語はプロジェクトごとにまちまちでもフロントエンドに使う言語は JavaScript 、というのは世界共通かと思います(近年は CoffeeScriptTypeScript といった AltJS もたくさん出てきていますが、最終的にどの言語を使うことになったとしても JavaScript を押さえるというのは必須のような気がします)。

Drupal も他のフレームワークに違わず、重量級 CMS として JavaScript とサーバサイドとをスムーズに連携させるための仕組みを豊富に用意しています。 これから Drupal 入門をされる方については、もし Drupal のコンセプト(ちなみに Drupal 用語集(その 1 その 2 )もありますのでよろしければ)とモジュール開発の基本的なやり方がひととおり理解できたら、次のステップとしては Drupal の JavaScript API を押さえることをおすすめします。 バックエンドとフロントエンド、それぞれの基本の部分を押さえれば、実際のサイト開発をスタートさせることができます。

その意味でいうと、先日翻訳した「 Drupal で JavaScript をテーマやモジュールに追加する方法 日本語訳」と今回の記事の内容をしっかり押さえれば、ひとまず Drupal の JavaScript / jQuery コーディングについては一歩踏み出せる感じになるかと思いますので、 Drupal の JavaScript 開発をやってみたいとお考えの方はぜひ上掲の記事もあわせてご覧いただければと思います。

では、以下が翻訳文となります。 ちなみに、最終更新日が 2014 年 06 月 24 日のバージョンをもとにしています。 以下のポイントを認識された上で読んでいただければ幸いです。

  • 内容が少し古めで Drupal 6 の話題が中心になっています(ただもちろん D7 のことも載っています)
  • (例のごとく)日本語文としてのわかりやすさを心がけ、直訳・意訳を織り交ぜています

Drupal の JavaScript API

このページでは Drupal における JavaScript の実装方法について説明しています。 drupal.js ファイルについて、特に Drupal の js オブジェクトの初期化について掘り下げて見ていきます。

メモ:以下で取り上げるコードサンプルはわかりやすさのために JSDoc をつけていません。

Drupal コアの drupal.js の中の JavaScript コードの最初の行はオブジェクトの宣言です:

var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {} };

このコードでは、 Drupal は自分自身と等しいオブジェクト、または、未定義の場合には 4 つのオブジェクト( settings behaviors themes locale )をプロパティに持つオブジェクト { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {} } に等しいものとして宣言されています。 このコード行はオブジェクトイニシャライザです。 この Drupal オブジェクトとそのプロパティは、他のモジュールから拡張することができます。 このことを理解するのに最適な方法は、相異なるプロパティをひとつずつ見て、 Drupal モジュールでの使われ方を見ることです。

各セクションへのショートカット:

Drupal.settings

Drupal.settings は PHP コードから JavaScript コードへの情報の受け渡しを可能にするものです。 これはあなたのモジュールによって JavaScript の動作を変更することができるということを意味します。 たとえば、シンプルに JavaScript にベースパスを教えたいとします。 これを行うには、設定を格納した PHP 配列を作るだけで大丈夫です。

$my_settings = array(
  'basePath' => $base_path,
  'animationEffect' => variable_get('effect', 'none')
 );

メモ:配列のキーは JavaScript コーディングスタンダードにのっとって、キャメルケーススタイル( CamelCasing )でセットします。

@see: https://drupal.org/node/172169#camelcasing

つづいて、 drupal_add_js() ( Drupal 8 では非推奨)を呼び出してこの配列を渡します。 第 2 引数には "setting" を渡しましょう。

drupal_add_js(array('myModule' => $my_settings), 'setting');

これは純粋に名前空間目的で別の配列の中につめ込まれている点に注目してください: 別のモジュールでも basePath が定義されているかもしれません。 こう書いておくと、次の形で JavaScript コードの中でこれらの設定にアクセスすることができます:

var basePath = Drupal.settings.myModule.basePath;
var effect = Drupal.settings.myModule.animationEffect;

これらは JavaScript においては文字列であって、文字列オブジェクトではありません。 あなたが drupal_add_js() に渡した配列のキーの値はコンマ区切りでこの文字列の末尾にくっつきます。

メモ: Drupal 7 では settings をローカルに渡します (訳注:こちら下で述べられているとおり、 Drupal 7 では Drupal.behaviors.myModuleBehaviorattach プロパティで定義される関数の第 2 引数で渡す形になっているため Drupal 7 では注意が必要です)

Drupal.behaviors

私たちの多くが jQuery を初めて学ぶとき、すべてのコードを $(document).ready() 関数の中に置くことを学びます:

$(document).ready(function () {
  // しゃれたことをします
});

こうすると、私たちのコードは DOM がロードされるや否や実行され、要素の操作やイベントへのビヘイビアのバインディングを指示どおりに行うことができます。 しかしながら、 Drupal 6 の場合は $(document).ready() 関数を jQuery コードに含める必要はありません。 そのかわりに、すべてのコードを Drupal.behaviors のプロパティに割り当てた関数の中に入れます。 上述のとおり、 Drupal.behaviors オブジェクトはそれ自身 Drupal オブジェクトのプロパティです。 モジュールに jQuery のビヘイビアを新たに追加したいときは単純にこのオブジェクトを拡張します。 モジュールの jQuery コードの全体像は次のような構成になります:

Drupal.behaviors.myModuleBehavior = function (context) {
  // しゃれたことをします
};

Drupal 7.x の場合:

Drupal.behaviors.myModuleBehavior = {
  attach: function (context, settings) {
    $('input.myCustomBehavior', context).once('myCustomBehavior', function () {
      // myCustomBehaviour の効果を特定の要素に一度だけ適用します
    });
  }
};

@see: https://drupal.org/update/modules/6/7#jquery_once

Drupal.behaviors のプロパティで定義されたすべての関数が DOM がロードされたときに実行されます。 drupal.js は $(document).ready() 関数を持っており、これは Drupal.attachBehaviors() 関数を呼び出します。 そして Drupal.attachBehaviors()Drupal.behaviors オブジェクトのプロパティをひとつずつ呼び出す形でループを回します。 Drupal.behaviors のプロパティは上記のとおりさまざまなモジュールで宣言された関数であり document を context として渡します。

こういう方法が採られている理由は、もし jQuery コードがページに DOM エレメントを追加するような AJAX コールを行うなら、新しいコンテンツにもビヘイビアをくっつけたい可能性があるからです(たとえば h3 要素をすべて隠すなど)。 しかし、 DOM がロードされ Drupal.attachBehaviors() が実行されたときにはその要素は存在しないので、ビヘイビアがくっつけられることはありません。 しかし、上述のセットアップですべきことは Drupal.behaviors.myModuleBehavior(newcontext) の呼び出しだけです。 newcontext は AJAX によって生成された新しい要素です。 こうすることで、 document 全体に再度ビヘイビアがくっつかないようにすることができます。 このコードの使い方に関する詳細の説明は 5.x モジュールの 6.x への変換6.x モジュールの 7.x への変換 のページにあります。

この使い方は実際 Drupal 6 の専売特許ではありません: Drupal 5 の jstools パッケージはまさしくこのパターンを使ってモジュール collapsiblock や tabs 、 jscalendar などのビヘイビアをコントロールしていました。

Drupal.behaviors 実践的な例

次の例はより実践的なものです。 Drupal Behaviors は attachBehaviors が呼び出されたときは常に発火します。 引数として渡される context 変数はどの DOM 要素が処理されているのかをよりよく把握するために役立つことがよくありますが、行っている処理が 2 回目かどうかを知るのには十分ではありません。 context 変数を jQuery セレクタの第 2 引数に渡すのはグッドプラクティスです。 なぜなら、こうしておくと与えられたコンテクストだけが探索対象となり、 document 全体は探索されないからです。 これは AJAX リクエストのあとにビヘイビアがくっつけられるときにいっそう重要となります。 次にあげるのは処理が各 DOM オブジェクトに対して 1 度だけ起こることを保証する Drupal.behavior の例です。

Drupal.behaviors.myModuleBehavior = function (context) {
  // この jQuery コードはこの要素が 1 度だけ処理されることを保証します。
  // 基本的にこういうことを意味しています:
  // 1) このクラスを持った要素のうち処理済み( processed )クラスを
  // 持っていないものすべてを見つけ出す
  // 2) それらに対してループを回す
  // 3) 処理済みクラスを追加する(繰り返し処理されないように)
  $('.module-class-object:not(.module-class-processed)', context).each(function () {
    $(this).addClass('module-class-processed');
      // 何かをします。
  });
};

リアルの例は OpenLayers モジュールの中にあります。 OpenLayers ではこの基本的な処理を使い、地図が 1 度かぎり作成されることを保証しています。 また、 Drupal behaviors を活用してパーツを追加したりイベントに反応したりしています。

Drupal 7.x の場合:

Drupal.behaviors.myModuleBehavior = {
  attach: function (context, settings) {
    // jQuery コードはこの要素が 1 度だけ処理されることを保証します。
    // 基本的にはこのようなことを意味しています:
    // 1) このクラスを持った要素のうち処理済み( processed )クラスを
    // 持っていないものすべてを見つけ出す
    // 2) それらに対してループを回す
    // 3) myCustomBehavior-processed クラスを追加する(繰り返し処理されないように)
    $('input.myCustomBehavior', context).once('myCustomBehavior', function () {
      // myCustomBehaviour 効果を要素に 1 度だけ適用します。
    });
  }
};

メモ:上記の Drupal 7 版サンプルは、ここで紹介した Drupal 6 版の単純な書き換えであり、 OpenLayers モジュールコードの現在のバージョンを表していないかもしれません。

Drupal.theme

Drupal.theme() はサーバサイドの theme() 関数のクライアントサイド版に相当するものです。 このような内容になっています:

Drupal.theme = function (func) {
  for (var i = 1, args = []; i < arguments.length; i++) {
    args.push(arguments[i]);
  }
  return (Drupal.theme[func] || Drupal.theme.prototype[func]).apply(this, args);
};

ですので、 Drupal.theme() の呼び出しを行うときは関数名を第 1 引数として渡します。 その後の引数の配列は第 1 引数の関数に渡されます。 引数として渡す関数は Drupal.theme() のプロトタイプオブジェクトである必要があります。 次のコードはその例です:

Drupal.theme.prototype.myThemeFunction = function (left, top, width) {
  var myDiv = '<div  id="myDiv" style="left:'+ left +'px; top:'+ top +'px; width:'+ width +'px;">';
  myDiv += '</div>';
  return myDiv;
};

この呼び出し方はこちら:

Drupal.theme('myThemeFunction', 50, 100, 500);

@see これに関する公式のドキュメントはこちら

Drupal.locale

Drupal.locale プロパティは Drupal.t() と連動して機能します。 Drupal.t() はサーバサイド関数 t() の JavaScript 版です。 ここには文字列の翻訳の集合があり、 Drupal.t() は受け取ったものを翻訳するために必要な文字列を Drupal.locale から取得することができます。

その他の情報:

以上です。いかがだったでしょうか?

Drupal での JavaScript もある程度スムーズに書けるようになると今回のような基礎の部分を改めて確認することはあまりありませんので、基礎を固めるという意味でも私自身勉強になりました。 基礎の部分への理解をもっと固めて、 Drupal が提供する仕組みをよりうまく活用したシンプル&エレガントなサイト開発をしていきたいものです。

Drupal の JavaScript 周りに関していえばコーディング・コメントスタンダードなども翻訳していますのでよろしければこちらもご参考にしてみてください。