昨日載せた文字パレットのソースコードと、ローカルに保存して単独で使う例の紹介です。

HTML
charPalette.htmlSelectRawtextBitbucket
<!DOCTYPE html><html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="charPalette.css">
</head>
<body>
  <input id="charInput" type="text">
  <input id="charButton" onclick="charPaletteExec()" type="submit" value="Query">
  <span id="charNavi"></span>
  <table id="charPalette"></table>
  <div id="charNotes">
    (説明文)Unicodeコードポイントor文字から検索して、……
  </div>
  <script src="charPalette.js"></script>
</body></html>

昨日は記事中に直接HTMLを入れました。それを単独で使うテンプレートにすると ↑ こんな感じ。スクリプトは最後に実行します。説明文のところは空でも可。というか本当は説明文のDIV要素自体を不要にすべきでしたが、最初、拡大文字を説明文の直前に(ID:charNotesを探して)挿入していた名残りです。


JavaScript
/* coding: utf-8 */

// when Query button clicked
function charPaletteExec() {
    var que = document.getElementById('charInput').value.split(/ *, */),
        btn = document.getElementById('charButton'),
        pre = btn.getAttribute('lastExec'),
        now = (new Date).getTime();

    // prevent unintended pressing twice
    if (pre && (now - pre) < 500) return;
    btn.setAttribute('lastExec', (new Date).getTime());
    
    getChar(que[0], que[1]);
    var h = document.getElementById('charNotes');
    while (h.hasChildNodes()) h.removeChild(h.firstChild);
}

// main process
function getChar(input, nrow, move) {
    // for surrogate pairs
    // c.f. http://webdev.seesaa.net/article/33141943.html
    //      http://teppeis.hatenablog.com/entry/2014/01/surrogate-pair-in-javascript

    if (typeof input === 'string') {
        if (input.match(/^x[0-9a-f]+$/i)) {
            input = '0' + input;
        } else if (input.match(/^u\+[0-9a-f]+$/i)) {
            input = '0x' + input.slice(2);
        }
    }
    var num = parseInt(input), cp10, sgp;

    var cp10, sgp; // decimal code point, flag of surrogate pairs
    if (!input) {
        cp10 = Math.round(Math.random() * 0xD7FF);
    } else if (!isNaN(num)) {
        cp10 = num;
        sgp = Math.pow(2, 16) < cp10 ? true : false;
    } else {
        var x = input.charCodeAt(0),
            y = 1 < input.length ? input.charCodeAt(1) : 0,
            sgp = 0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF;
        cp10 = sgp ? (x - 0xD800) * 0x400 + y - 0xDC00 + 0x10000 : x;
    }
    if (!nrow) {
        nrow = 10;
    } else if (nrow > 50) {
        nrow = 50;
    }

    // create table, magnified character and controls
    setTable(cp10, nrow, sgp, move);
    addControls(cp10, nrow);
}

// create palette
function setTable(cp10, nrow, sgp, move) {
    var tbl = document.getElementById('charPalette'),
        tr = document.createElement('tr'),
        td = document.createElement('td');
    clearPalette(tbl);

    var r = tr.cloneNode();
    for (var j = -1; j < 16; j++) {
        var c = td.cloneNode(),
            n = j.toString(16).toUpperCase();
        if (j >= 0) c.appendChild(document.createTextNode(n));
        r.appendChild(c);
    }
    tbl.appendChild(r);

    var cp10start = Math.floor(cp10 / 16) * 16;
    tbl.setAttribute('start', cp10start);
    
    if (!move) {
        tbl.setAttribute('searched', cp10);
        charEnlarge(cp10);
    }
    var searched = parseInt(tbl.getAttribute('searched'));

    for (var i = 0; i < nrow; i++) {
        var r = tr.cloneNode(),
            c = td.cloneNode(),
            n = cp10start + i * 16,
            n16 = 'U+' + n.toString(16).slice(0, -1).toUpperCase();
        c.appendChild(document.createTextNode(n16));
        r.appendChild(c);

        var tds = [];
        for (var j = 0; j < 16; j++) {
            var n10 = n + j,
                param = [];
            if (n10 === searched) param.push('class="clicked"');
            param.push('nrow="' + i + '"');
            param.push('ncol="' + j + '"');
            param.push('onclick="clickCell(this)"');


            var h = '<td ' + param.join(' ') + '>';
            if (sgp) {
                h += '&#x' + n10.toString(16) + ';';
            } else {
                h += String.fromCharCode(n10);
            }
            h += '</td>';
            tds.push(h);
        }
        r.innerHTML += tds.join('');
        // use innerHTML to set "onclick" when loaded with location.search
        tbl.appendChild(r);
    }
}

// when a character clicked
function clickCell(cell) {
    if (!cell) return; // just in case
    cell.className = 'clicked';
    var start = parseInt(cell.parentNode.parentNode.getAttribute('start')),
        nrow = cell.getAttribute('nrow'),
        ncol = cell.getAttribute('ncol'),
        cp10 = start + parseInt(nrow * 16) + parseInt(ncol);
    charEnlarge(cp10);
}

// add magnified character
function charEnlarge(cp10) {
    // check existence of the same character
    var ext = document.getElementsByClassName('enlargeLabel'),
        cp10ext;
    if (typeof ext === 'object' && ext.length > 0) {
        for (var i = 0, len = ext.length; i < len; i++) {
            cp10ext = parseInt(ext[i].childNodes[2].nodeValue.slice(2, -1));
            if (cp10 === cp10ext) {
                // remove the same character
                var obj = ext[i].parentNode;
                obj.parentNode.removeChild(obj);
                break;
            }
        }
    }

    var nts = document.getElementById('charNotes'),
        enl = document.createElement('div'),
        cls = document.createElement('div'),
        hex = cp10.toString(16).toUpperCase();
    enl.className = 'enlarge';
    enl.innerHTML = '<div class="enlargeChar">&#x' + hex + ';</div>' +
        '<div class="enlargeLabel">&amp;#x' + hex + ';<br>&amp;#' + cp10 + ';' +
        '<div class="enlargeClear"' +
        'onclick="enlargeClose(this)">&#xD7;</div>';
    // use innerHTML to set "onclick" when loaded with location.search


    var clrFloat = document.getElementById('clearEnlarge'),
        p = nts.parentNode;
    if (!clrFloat) {
        clrFloat = document.createElement('div');
        clrFloat.id = 'clearEnlarge';
        p.insertBefore(clrFloat, nts);
    }
    
    // insert next to palette
    p.insertBefore(enl, document.getElementById('charPalette').nextSibling);
}

// remove magnified character
function enlargeClose(obj) {
    var p = obj.parentNode.parentNode;
    if (!p || p.className !== 'enlarge') return; // just in case
    p.parentNode.removeChild(p);
}

function clearPalette(div) {
    while (div.hasChildNodes()) div.removeChild(div.firstChild);
    var mv = document.getElementById('charNavi');
    if (mv) while (mv.hasChildNodes()) mv.removeChild(mv.firstChild);
}

function addControls(cp10, nrow) {
    var nav = document.getElementById('charNavi'),
        lnk = document.createElement('button'),
        obj = { '▲▲': -nrow, '▲': -1, '▼': 1, '▼▼': nrow * 1 },
        min = Math.pow(2, 5),
        max = Math.pow(2, 16) + Math.pow(2, 20) - Math.pow(2, 11);

    for (var k in obj) {
        var target = cp10 + obj[k] * 16;
        if (target < min || max < target) continue;
        var h = '<button onclick="getChar(' +
            target + ', ' + nrow + ', true)">' + k + '</button>';
        nav.innerHTML += h;
    }
}

// added process
(function () {
    // when query string added
    if (location.search) {
        try {
            var que = decodeURIComponent(location.search.slice(1));
        } catch (e) {
            console.log('unexpected error in parsing location.search');
        }
        document.getElementById('charInput').value = que;
        charPaletteExec();
        return;
    }
})();

長いのでフレーム内スクロールで表示。大半はインターフェイス回りで、基本は
  1. 指定されたコードポイントor文字から、テーブル左上の起点コードポイントを決める
  2. 起点から横に16字、縦に10行(または指定された行数)ずつ、文字を並べる

だけです。1.で文字が来た場合のサロゲートペア処理が少し面倒。またURLにクエリが付いていたら自動的に検索&文字パレット表示しますが
(例:~.html?0x2710)、生成されるボタンやセルにonclickイベントを付ける際、JavaScriptで「オブジェクト.onclick = function(e) {} 」とはできなかったので、代わりにHTMLタグに「onclick="関数名(this)"」と書いてinnerHTMLで投入しています。もしかしたら、昨日の記事中に入れたせいかも。


CSS
charPalette.cssSelectRawtextBitbucket
#charInput {
    padding: 5px;
}

#charButton {
    margin-left: 0.25em;
    margin-right: 1em;
}

#charPalette {
    border-collapse: collapse;
}

#charPalette td {
    border: solid 1px lightblue;
    text-align: center;
    padding: 5px 5px 4px;
}

#charPalette tr:first-child td:first-child {
    border-style: none;
}

#charPalette tr:first-child, tr td:first-child {
    color: gray;
    font-family: monospace;
}

#charPalette tr:nth-child(n+2) td:nth-child(n+2) {
    cursor: pointer;
    font-size: 1.25em;
}

#charPalette .clicked {
    background: lightblue;
}

#charNavi {
    white-space: nowrap;
}

#charNavi button {
    font-size: 0.7em;
    margin-right: 0.5em;
}

.enlarge {
    border: solid lightblue 1px;
    float: left;
    margin-bottom: 10px;
    margin-right: 10px;
    padding: 0 10px 3px;
    position: relative;
    white-space: nowrap;
}

.enlargeClear {
    background: lightblue;
    cursor: pointer;
    color: white;
    font-size: 1.5em;
    position: absolute;
    right: 0;
    top: 0;
}

.enlargeChar {
    font-size: 4em;
    margin-right: 10px;
}

.enlargeLabel {
    font-family: monospace;
}

#clearEnlarge {
    clear: both;
}        

#charNotes {
    text-align: left;
}

こちらも長いのでフレーム内スクロールで。内容はパレット(テーブル)や拡大文字などのスタイル設定。一部でCSS
3の疑似クラスを使い、未対応ブラウザでは表示が若干変わります。


使用例
以上の3ファイルをcharPalette.zipにまとめました。適当な場所に解凍しChromium43で開くと ↓ こんな感じ。なおローカルファイルからJavaScriptを開くので、環境によっては起動時のオプションとか必要かも。自分の場合(Windows7)特に不要でした。


↓ 昨日と同様、サロゲートペアの絵文字の一つ0x1F427をクエリした結果。Segoe UI Symbolというフォントがシステムに入っていれば(Windows
7では最初からあり)こんな風になるはず。


↓ 実際どのフォントが使われたかChromiumで調べたところ。Inspect Elementツールを起動し、文字を選択してElementsComputedを選ぶと、下の方にRendered Fontsが出ます。


ローカルでHTMLJavaScriptを開くにあたり、PCのセキュリティ設定等で動かない場合があるかも。そんな時は
昨日の記事をオンラインで使って下さい。