uhyohyo.net

JavaScript初級者から中級者になろう

七章第六回 サンプルの改良

このページの最終更新日:

仕様を決める

今回は、前回作ったサンプルを改良しましょう。前回のサンプルはこれです。


var walker = document.createTreeWalker(document,NodeFilter.SHOW_ELEMENT,
  function(node){
    if(/^H[1-6]$/.test(node.tagName)){
      return NodeFilter.FILTER_ACCEPT;
    }else{
      return NodeFilter.FILTER_SKIP;
    }
  },
false);
var node;
var ol = document.createElement('ol');
var number = 1;
while(node=walker.nextNode()){
  var result = node.tagName.match(/^H([1-6])$/);
  var itsnumber = parseInt(result[1]);

  if(number < itsnumber){
    //数字が大きい
    var newol = document.createElement('ol');
    ol.lastChild.appendChild(newol);
    ol = newol;	//追加先を新しいolにする

  }else if(number > itsnumber){
    //数字が小さい
    for(var i=0;i<number-itsnumber;i++){
      ol = ol.parentNode.parentNode;
    }
  }
  number = itsnumber;	//「現在の番号」を更新

  var newli = document.createElement('li');
  newli.textContent=node.textContent;
  ol.appendChild(newli);
}
while(ol.parentNode){
  ol=ol.parentNode;
}
document.body.appendChild(ol);

ページにある見出しを一覧にするものでした。しかし、実はこれではあまり実用性がありませんね。今回、リストのそれぞれにリンクを付けて、クリックするとその見出しの場所が見られるというようにしましょう。

さて、その方法ですが、そもそもリンクをクリックするとその場所に移動するというのは、次のようにします。


<p>あああああああああ。<a href="#aaa">いいい</a></p>
<p>うううううう</p>
<p>ええええええええええええええ</p>
<p id="aaa">ここ</p>

a要素のhref属性には、#aaaと、先頭にシャープが付いたリンク先が指定されています。このような書き方をすると、同じページのaaaという名前のところに移動します。また、URLの後に#をつけると、そのページの該当部分に移動します。

では、どのようにしてページの一部に名前をつけるかというと、id属性を使用します。

これを今回のサンプルに使うと、たとえば


<h1>見出し</h1>
<p>あああ</p>

<h1>見出し</h1>
<p>あああ</p>

という見出しがあったとき、これを


<h1 id="h1_1">見出し</h1>
<p>あああ</p>

<h1 id="h1_2">見出し</h1>
<p>あああ</p>

のようにして、リストのほうは


<ul>
  <li><a href="#h1_1">見出し</a></li>
  <li><a href="#h1_2">見出し</a></li>
</ul>

のようにすればいいわけです。こうすれば、リストにある見出し一覧をクリックすると、その見出しに行くことができて便利です。

なお、この場合はh1要素等にもともとid属性がある場合を考えていませんが(要素に複数のidをつけることはできないため)、今回は考えないようにしましょう。実際は、h1要素等の子としてspan要素などを追加してやって、それにid属性をつけることで解決できます。h1_1などのIDがもともと存在するのもまずいですが、それも今回は考えないことにします。(このid属性が既に存在するか調べて既に存在するIDはとばすことで対応できます。)

前回同様、できるなら自分でこれを実装してみてください。前回のコードを改造すればいいので幾分楽だと思います。

作る

さて、まずそれぞれの見出し要素にid属性を追加する処理を作ります。サンプルの


while(node=walker.nextNode()){
  var result = node.tagName.match(/^H([1-6])$/);
  var itsnumber = parseInt(result[1]);

  if(number < itsnumber){
    //数字が大きい
    var newol = document.createElement('ol');
    ol.lastChild.appendChild(newol);
    ol = newol;	//追加先を新しいolにする

  }else if(number > itsnumber){
    //数字が小さい
    for(var i=0;i<number-itsnumber;i++){
      ol = ol.parentNode.parentNode;
    }
  }
  number = itsnumber;	//「現在の番号」を更新

  var newli = document.createElement('li');
  newli.textContent=node.textContent;
  ol.appendChild(newli);
}

の部分を改造しましょう。まず最初に、id属性を決定する必要があります。一番単純なのは、通し番号を付けることですよね。例えば、最初の見出し要素は"h_1"、次の見出し要素は"h_2"、以下h_3,h_4,h_5,h_6,h_7,h_8,h_9……と順番に続いていく方式です。


var bangou = 1;
while(node=walker.nextNode()){
  var result = node.tagName.match(/^H([1-6])$/);
  var itsnumber = parseInt(result[1]);

  var id = "h_"+bangou;
  bangou++;
  node.id=id;

  if(number < itsnumber){
    //数字が大きい
    var newol = document.createElement('ol');
    ol.lastChild.appendChild(newol);
    ol = newol;	//追加先を新しいolにする

  }else if(number > itsnumber){
    //数字が小さい
    for(var i=0;i<number-itsnumber;i++){
      ol = ol.parentNode.parentNode;
    }
  }
  number = itsnumber;	//「現在の番号」を更新

  var newli = document.createElement('li');
  newli.textContent=node.textContent;
  ol.appendChild(newli);
}

まず最初に通し番号の変数bangouを1にしておきます。そこで、"h"+bangouとすると、変数nameは"h1"となりますね。次にbangouを1足しています。こうすることで、重複のない番号を順番につけることができますね。

あとは簡単です。


var bangou = 1;
while(node=walker.nextNode()){
var result = node.tagName.match(/^H([1-6])$/);
var itsnumber = parseInt(result[1]);

var id = "h_"+bangou;
bangou++;
node.id=id;

if(number < itsnumber){
  //数字が大きい
  var newol = document.createElement('ol');
  ol.lastChild.appendChild(newol);
  ol = newol;	//追加先を新しいolにする

}else if(number > itsnumber){
  //数字が小さい
  for(var i=0;i<number-itsnumber;i++){
    ol = ol.parentNode.parentNode;
  }
}
number = itsnumber;	//「現在の番号」を更新

var newli = document.createElement('li');
var newa = document.createElement('a');		//リストのa要素
newa.href = "#"+id;			//href属性を設定
newa.textContent=node.textContent;
newli.appendChild(newa);
ol.appendChild(newli);
}

これでいいのですが、実はまだ完璧とはいえません。このスクリプトを実行する前に、もし既に文書中にh1というidを持つ要素があったらどうでしょう。h1という名前が重複してしまいます。HTMLでは同じidをもつ要素が複数存在してはいけませんから、これでは困ります。そこで、決めた名前が重複しているかどうかチェックして、重複していたら名前を変えないといけません。簡単なのは、重複したら、さらに通し番号を進めるという方法です。

id属性が重複しているかどうかを確認するのは難しくありません。document.getElementByIdでそのidを持つ要素が既に存在するかどうか調べればいいのです。

そこで、これを使ってidが重複しないか調べてみましょう。通し番号を進めてもまだ重複しているという可能性もあるので、while文を使って、重複しなくなるまで進めましょう。


var bangou = 1;
while(node=walker.nextNode()){
  var result = node.tagName.match(/^H([1-6])$/);
  var itsnumber = parseInt(result[1]);

  var id;
  while(true){
    id = "h_"+bangou;
    bangou++;

    var t = document.getElementById(id);
    if(t == null){
      break;	//重複していなければそれでOK
    }
  }


  node.id=id;

  if(number < itsnumber){
    //数字が大きい
    var newol = document.createElement('ol');
    ol.lastChild.appendChild(newol);
    ol = newol;	//追加先を新しいolにする

  }else if(number > itsnumber){
    //数字が小さい
    for(var i=0;i<number-itsnumber;i++){
      ol = ol.parentNode.parentNode;
    }
  }
  number = itsnumber;	//「現在の番号」を更新

  var newli = document.createElement('li');
  var newa = document.createElement('a');		//リストのa要素
  newa.href = "#"+id;			//href属性を設定
  newa.textContent=node.textContent;
  newli.appendChild(newa);
  ol.appendChild(newli);
}

これで完成です。

サンプル

今回もこのページで試してみました。