あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

別のページにすでにある用語集を使って、用語にマウスポインタ合わせると用語集にある用語の説明をツールチップで表示するJSサンプル作った

タイトルなげー。

APIもDBもない時にこういうことしようと思ったらどうやって実現しようかなーと思ってサンプルを作ってみました。IE、バージョンの古いブラウザは未対応。

前提

こういう用語集のページがすでにあると過程。

grossary.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>用語集</title>
</head>
<body>
<dl>
<dt class="term">インターネット</dt>
<dd class="description">すばらしい</dd>
<dt class="term">JavaScript</dt>
<dd class="description">たのしい</dd>
</dl>
</body>
</html>

classは後から付ける感じになると思いますが、必ず対になっていて、用語が先、説明文が次に来る構成を想定してます。このルールを守って、用語に「term」、説明に「description」というclassが付いていれば、dt、ddでなくてもOK。

で、この用語集を使って用語の説明をツールチップで表示したいページがこんな感じであります。

tooltips.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>ツールチップ</title>
<script src="forceToolTips.js"></script>
</head>
<body>
<div id="use_term">
<a href="grossary.html">用語集</a>
<p>あいうえおかきくけこインターネットさしすせそJavaScriptたちつてとなにぬねのインターネット</p>
</div>
</body>
</html>

適当でごめんなさい。

で、JSファイル。

forceToolTips.js
document.addEventListener('DOMContentLoaded', function() {
  function Grossary(req, area) {
    function setHTML(d) {
      area.innerHTML =
        area.innerHTML.replace(
          new RegExp(d, 'g'),
          '<a class="tooltip" href="javascript:void(0)">' + d + '</a>'
        );
    }
    return {
      List : req.responseText.split(
        req.responseText.match(/\r\n/) ? '\r\n' :
        req.responseText.match(/\n/)   ? '\n'   : 
        '\r'
      ).filter(function(d) {
        return d.match(/class="term"|class="description"/);
      }).map(function(d) {
        return d.replace(/.*?<.*?>(.*?)<\/.*?>.*?/, '$1');
      }).map(function(d, n, lis){
        if (n%2==0||n==0) {
          setHTML(d);
          return [d, lis[n+1]];
        }
      }).filter(function(d) {
        return d;
      }),
      setToolTips : function() {
        var tooltips = document.getElementsByClassName('tooltip');
        var list = this.List;
        var key = 0;
        var value = 1;
        Array.prototype.forEach.call(tooltips, function(anchor) {
          list.map(function(wordset) {
            if (anchor.textContent == wordset[key]) {
              var span = document.createElement('span');
              span.textContent = wordset[value];
              span.style.display = 'none';
              anchor.parentNode.insertBefore(span, anchor);
              anchor.addEventListener('mousemove', function(e) {
                var span = this.previousSibling;
                span.style.display  = 'block';
                span.style.color = '#fff';
                span.style.backgroundColor = '#999';
                span.style.padding = '3px';
                span.style.position = 'absolute';
                span.style.zIndex = '999';
                span.style.top  = e.pageY + 20 + 'px';
                span.style.left = e.pageX + 'px';
              }, false);
              anchor.addEventListener('mouseout', function() {
                this.previousSibling.style.display = 'none';
              }, false);
            }
          });
        });
      }
    }
  }

  var grossary_file = 'grossary.html';
  var request = new XMLHttpRequest();
  request.open('get', grossary_file);
  request.send();
  request.onreadystatechange = function() {
    if (request.readyState == 4) {
      var list = Grossary(request, document.getElementById('use_term'));
      list.setToolTips();
    }
  };

}, false);

要検討

  • XHRから取ってきたHTMLファイルを正規表現ですべてパースしてるけど、もっとシンプルにパースしてinnerHTMLに突っ込んでDOMで操作した方が良いかもしれない。
  • たぶん用語多いと超重い。
  • dtとddの中に別のインラインのタグが入ってたら動かないし、外側に別のタグが入ってても動かないし、ddが複数定義されてても動かない。
  • 正規表現の箇所が貧弱なため、例えば「ServerSideJavaScript」って用語が定義されてたりしてもうまく動かない。
  • ToolTipsのデザインが適当すぎる。
  • 実践で使うにはお粗末。
  • IE対応が嫌い。

問題は山積み。ライブラリ作る人は偉大だと思う。

でもJavaScriptしか使えないという制約の中でいろいろ考えると勉強になります。

サンプル

サンプルはこちら