JavaScript関数のカレントパスがそれを使うHTMLの場所になる仕様への対策

# 必要なファイル、相対パスで指定できない!

このTipsがどういう場面を想定したものなのかをわかり易く伝えるため、一つの例を考えてみます。

## (例)全HTMLページに拍手用JavaScript関数を設置したい

ある人がそんなことを思って、せっせとプログラムを作りました。

具体的には、各HTMLにAjaxによる拍手機能（拍手をサーバーに通知すると共に、パチパチしている絵をAjaxで表示する）を実装しようとしています。

ディレクトリー構造は次のとおりです。

```text:
/ --+- mydoc/ ---+- top.html
    |            +- chap1/ --+- section1.html
    |            +- chap2/ --+- section1.html
    |            |
    |            +- js/ -----+- ajax.js
    |            +- cgi/ ----+- ajax_clap.cgi
    |            +- img/ ----+- clapping.gif
    |
    | （↓他人のディレクトリー、同じajax.jsという名前のファイルがある）
    +- herdoc/ --+- js/ -----+- ajax.js
```

拍手Ajax用のJavaScript関数は`ajax.js`の中にあり、その関数がサーバー通知用CGIの`ajax_clap.cgi`を実行したり、拍手の絵`clapping.gif`を表示したりする、という仕様です。

### ここで悩み事が発生する

`ajax.js`の中に設置する拍手のための関数から、`ajax_clap.cgi`や`clapping.gif`を開くわけですが、 **JavaScriptでのカレントパスは「そのJavaScriptを利用しているHTMLファイルの場所」** になるという性質があります。だから、依存ファイル（`ajax_clap.cgi`や`clapping.gif`）を単純には相対パスで指定できないのです。

絶対パスで指定すれば確かに大丈夫です。しかし、文書の整理などでディレクトリーの移動を余儀なくされた場合、その都度JavaScript関数に書き込んでいるであろう絶対パスを全て書き直さなくてはなりません。

「これ、どーにかならないものか」というのがこのTipsです。

# そんなアナタにとっておきの関数！

とっても簡単！この関数を設置して、設置する時に最初の2行を決めて、あとは呼ぶだけです。すると **この関数自身が今置かれている絶対パス(URL)をその場で検出** して返してくれます。だから、どこのHTMLから呼ばれようが、自分がどこに移動されようが、一切影響なし！

```js:この関数を書いて使えば解決!（コメントがウザかったらごっそり消しましょう）
// ===== 本システムのホームディレクトリの絶対URLを得る ===============
// [入力]
// ・なし
// [出力]
// ・本システムのホームディレクトリーの絶対URL文字列
//   - 文字列の最後には "/" が付いている。従ってこのURLを元に、別のファ
//     イルやディレクトリーの絶対URLを作る時、付け足す文字列の先頭に "/"
//     は不要である。
//   - 失敗時はnull
// [備考]
// ・JavaScriptにとって、自分のプログラムファイルが置かれている絶対URL
//   を自力で知るのは難しい。理由は、呼び出し側HTMLのカレントURL(絶対
//   URL)が与えられるからだ。
// ・そこで呼び出し側HTMLの中にある、呼び出し元の<script src="...">を探
//   し出し、そのURL情報を使ってカレントURL(絶対URL)に位置補正を行い、
//   自身の絶対URLを検出する。
function get_homedir() {

  // --- 自分自身に関する情報の設定(自分の位置を検出するために必要) --
  var sThis_filename    = 'relocatable.js'; // 自分が置かれているファイルの名前(*1)
  var sPath_to_the_home = '..';             // ↑自身からホームdirへの相対パス
     // (*1) 同名ファイルが他に存在するなどして、ファイル名だけでは一意
     //      に絞り込めない場合、「<script src="~">で必ず含めると保証さ
     //      れている」のであれば、親ディレクトリーなどを含めてもよい。
     //      例えば、1つのサイトの中の
     //        あるHTMLでは<script src="myjs/script.js">
     //        またあるHTMLでは<script src="../myjs/script.js">
     //      というように、全てのHTMLで "myjs/script.js" 部分を必ず指定
     //      しているなら、他の "otherjs/script.js" と間違わないように、
     //        sThis_filename = 'myjs/script.js' としてもよい。
     //      ただしこの時 sLocation_from_home は、"myjs" の場所から見た
     //      ホームディレクトリーへの相対パスを意味するので注意。

  // --- その他変数定義 ----------------------------------------------
  var i, s, le; // 汎用変数
  var sUrl = null; // 戻り文字列格納用

  // --- 自JavaScriptを読んでいるタグを探し、homedir(相対の場合あり)を生成
  le = document.getElementsByTagName('script');
  for (i=0; i<le.length; i++) {
    s = le[i].getAttribute('src');
    if (s.length < sThis_filename.length) {continue;}
    if (s.substr(s.length-sThis_filename.length) !== sThis_filename) {continue;}
    s = s.substr(0,s.length-sThis_filename.length);
    if ((s.length>0) && s.match(/[^\/]$/)) {continue;}
    sUrl = s + sPath_to_the_home;
    sUrl = (sUrl.match(/\/$/)) ? sUrl : sUrl+'/';
    break;
  }
  if (i >= le.length) {
    return null;             // タグが見つからなかったらnullを返して終了
  }

  // --- 絶対パス化(.や..は含む) -------------------------------------
  if (     sUrl.match(/^http/i)) {
    // httpから始まるURLになっていたらそのままでよい
  }
  else if (sUrl.match(/^\//)   ) {
    // httpから始まらないが絶対パスになっている場合はhttp～ドメイン名までを先頭に付ける
    if (! location.href.match(/^(https?:\/\/[a-z0-9.-]+)/i)) {return null;}
    sUrl = RegExp.$1 + sUrl;
  }
  else                           {
    // 相対パスになっている場合は呼び出し元URLのディレクトリまでの部分を先頭に付ける
    sUrl = location.href.replace(/\?.*$/,'').replace(/\/[^\/]*$/, '/') + sUrl;
  }

  // --- カレントディレクトリ表記(.)を除去 ---------------------------
  while (sUrl.match(/\/\.\//)) {
    sUrl = sUrl.replace(/\/\.\//g, '/');
  }

  // --- 親ディレクトリ表記(..)を除去 --------------------------------
  sUrl.match(/^(https?:\/\/[A-Za-z0-9.-]+)(\/.*)$/);
  s    = RegExp.$1;
  sUrl = RegExp.$2;
  while (sUrl.match(/\/\.\.\//)) {
    while (sUrl.match(/^\/\.\.\//)) {
      sUrl = sUrl.replace(/^\/\.\.\//, '/');
    }
    sUrl = sUrl.replace(/^\/\.\.$/, '/');
    while (sUrl.match(/\/[^\/]+\/\.\.\//)) {
      sUrl = sUrl.replace(/\/[^\/]+\/\.\.\//, '/');
    }
  }
  sUrl = s + sUrl;

  // --- 正常終了 ----------------------------------------------------
  return sUrl;
}
```

## デモもあるよ

デモも用意したので試してみてください。→[デモ](http://lab-sakura.richlab.org/CLAPPING_COVER/top.html)

ディレクトリー構成は、冒頭に記したmydoc/の中に似せています。top.htmlとchap1の中のHTMLとで全く同じJavaScript関数`clap()`を呼んでいます（JavaScrpit関数にとってみるとカレントディレクトリーが異なる）が、画像ファイル`clapping.gif`もCGIファイル`ajax_clap.cgi`もちゃんと開けていますよね。

## どうやって、自分の場所を調べているの？

こたえを言ってしまうと、 **各HTMLで指定している`<script src="～">`を見てます。** （複数の`<script>`タグがあってもいいように、自ファイル名が指定されているものだけに注目している）

`<script>`タグのsrcプロパティーには当然、こちらのJavaScript関数が書かれたファイルへのパスが記されていますよね。それをたどりさえすればHTMLファイルから自分への位置を知ることができるというわけです。「じゃあ`<script>`タグをの書いてあるHTMLファイルの場所は？」と言われれば、それは`location.href`を見ればわかります。

この2つを結合して絶対パスを求めるのがこの関数`get_homedir()`というわけなのです。理屈としては簡単ですよね。（関数はごちゃごちゃしてますが）

# ちなみに、jQueryとか一切使ってない

このデモの拍手JavaScript。jQueryなどの外部ライブラリーを一切使っていません。つまり、外部ライブラリーが存在する環境であろうが無かろうが関係無いわけで、そういう意味でも「どこに置いても平気」だったりします。蛇足ですが。