Sep 7, 2004

Javascriptでの表示・非表示切り替え

Webは自分の書いたコードを広めるのにも適していますが、初期の掲示板CGIの例などを挙げるまでもなく、誰か一人が書いたに違いない必ずしも望ましくないコードが延々使い回されるという状況を助長する面もあります。

さてしばしばエントリの追記(extend)部分の表示・非表示を切り替えるのにJavascriptを使う例が散見されます。どうやらオリジナルはscriptygoddessやら「続きを読む」を隠したり出したり : cync.jp :: squint sightsあたりらしいのですが、これらのスクリプトではexpand/collapseする要素にいちいちid属性を振る必要があり、また「にもかかわらず」すべてのextend要素をまとめてexpand/collapseなどといったごく簡単な応用もできません。もちろんidを冗長に与えてロジックを単純にするという発想は正しいですが、id属性は一個の要素に一個しか与えられていない貴重な「資源」なのでwastefulな使い方は避けるべきです。Javascriptにはなぜだかこういう「仕事」が特に多い気がするのですが、解説本の影響でもあるのでしょうか。

このエントリで述べるのは、このような用途のスクリプトを私ならどのように実現するかということです。先例のスクリプトではexpand/collapseする要素にid属性を振り、getElementByIdで対象要素の操作をするわけですが、どうせDOMを使うのなら本質的にidを振る必要性はありません。

このエントリでは、類題として以下のようにexcerpt部分のBLOCKQUOTEの表示・非表示を切り替えるボタンを設ける場合を考えます。

テンプレートは以下のように記述してあるものとします。

List 1:
<MTEntries sort_order="ascend">
<$MTEntryTrackbackData$>

<MTDateHeader>
<a id="<$MTEntryDate format="d%d"$>"></a>
</MTDateHeader>

<h2 id="a<$MTEntryID pad="1"$>">
<a href="<$MTEntryPermalink valid_html="1"$>"><$MTEntryTitle$></a>
</h2>

<div class="meta">
Category:
<a href="<$MTEntryLink archive_type="Category"$>"><$MTEntryCategory$></a>
| <$MTEntryAuthor$> @ <$MTEntryDate format="%x %X"$>
<img onclick="toggleExcerpt(this)" alt="Expand excerpt"
 src="<$MTCGIPath$>images/expand.gif" />
</div>

<blockquote style="display:none"><$MTEntryExcerpt$></blockquote>
</MTEntries>

切り替えボタンを表示するIMG要素のonclickイベントとしてtoggleExcerptというユーザ定義メソッドを使います。このメソッドの引数はカレントノード(this)だけです。また、表示・非表示を切り替えるBLOCKQUOTE要素にid属性などは振りません。つまり、赤字で示したIMG要素だけを追加すれば切り替えボタン機能としてするようにします。

ではtoggleExcerptはどのように定義するかというと以下を参照してください。

List 2:
function toggleExcerpt(o) {
  var bqNode = o.parentNode.nextSibling;
  while (bqNode.nodeName.toUpperCase() != "BLOCKQUOTE") {
    bqNode = bqNode.nextSibling;
  }
  if (bqNode.style.display == "none") {
    bqNode.style.display = "block";
    o.alt = "Collapse excerpt";
    o.src = "<$MTCGIPath$>images/collapse.gif";
  } else {
    bqNode.style.display = "none";
    o.alt = "Expand excerpt"
    o.src = "<$MTCGIPath$>images/expand.gif";
  }
}

引数で渡されるカレントノード(IMG)のparentNode(DIV class="meta")の次の要素を順にスキャンして、nodeNameがBLOCKQUOTEである最初の要素を探し出し、style属性を書き換えるだけです(エラー処理は省略しています)。これを多少変更すればすべてのBLOCKQUOTE要素の属性をまとめて操作できることも分かるでしょう。

さてここで少しadvancedなことを考えてみます。<$MTEntryTrackbackData$>は以下のようにrdfに展開されるのですからExcerptを含んでいます。したがって、<blockquote style="display:none"><$MTEntryExcerpt$></blockquote>のようにしてExcerptをわざわざ二度も展開するのは無駄じゃないか、と。つまり、expandしたときにこのコメント内の文字列をBLOCKQUOTEに挿入することはできないかということです。

List 3:
<!--
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
         xmlns:dc="http://purl.org/dc/elements/1.1/">
<rdf:Description
    rdf:about="http://www.example.com/blog/archives/2004_08.html#000896"
    trackback:ping="http://www.example.com/mt/trackback.cgi/166"
    dc:title="mt-resave-entries.cgi: basenameがNULLのエントリを再保存するCGI"
    dc:identifier="http://www.example.com/blog/archives/2004_08.html#000896"
    dc:subject="Weblog"
    dc:description="Excerpt Text"
    dc:creator="ogawa"
    dc:date="2004-08-06T08:21:02+09:00" />
</rdf:RDF>
-->

これももちろんできます。List 1のBLOCKQUOTEは<blockquote style="display:none"></blockquote>のように空にしてあるとします。

toggleExcerptの定義はList 2とほぼ同じですが、expandするときにinnerHTMLが空だったらextractExcerptの返す文字列をinnerHTMLにセットする手続きを追加しておきます。

List 4:
function toggleExcerpt(o) {
  var bqNode = o.parentNode.nextSibling;
  while (bqNode.nodeName.toUpperCase() != "BLOCKQUOTE") {
    bqNode = bqNode.nextSibling;
  }
  if (bqNode.style.display == "none") {
    if (!bqNode.innerHTML) {
      bqNode.innerHTML = extractExcerpt(o);
    }
    bqNode.style.display = "block";
    o.alt = "Collapse excerpt";
    o.src = "<$MTCGIPath$>images/collapse.gif";
  } else {
    bqNode.style.display = "none";
    o.alt = "Expand excerpt"
    o.src = "<$MTCGIPath$>images/expand.gif";
  }
}

一方、extractExcerptは、引数で渡されるカレントノード(IMG)のparentNode(DIV class="meta")の前の要素を順にスキャンしてnodeNameが#commentのものを探し出し、コメント文字列から「dc:description="」と「"」で囲まれる文字列を取り出して返します。

var excerptStr = "dc:description=\"";
function extractExcerpt(o) {
  var cmntNode = o.parentNode.previousSibling;
  while (cmntNode.nodeName != "#comment") {
    cmntNode = c.previousSibling;
  }
  var s = cmntNode.nodeValue;
  var startpos = s.indexOf(excerptStr) + excerptStr.length;
  var endpos = s.indexOf("\"", startpos);
  return s.substring(startpos, endpos);
}

About Me

My Photo

つくばで働く研究者

Total Pageviews

Amazon

Copyright 2012 Ogawa::Buzz | Powered by Blogger
Design by Web2feel | Blogger Template by NewBloggerThemes.com