IE 6/7 で文書間通信を実現するための一案2011年12月08日 23時32分

HTML5-WEST.jp 飲み会 UST というのがあるそうで、「参戦希望者募集」とお誘いを受けたのですが、「参戦」というからには何かしら戦の準備を整えねばなるまいと、以前から考えていたことを夜なべして実装しました。

いわゆる HTML5 の文書間通信、window.postMessage() を IE 6/7 でどう再現するかという話で、about:blank を指す隠しフレームを二つ用い、window.name を介することで双方向の通信を実現しています。

どうも IE での about:blank は、その空白ページを読み込ませた文書の生成元を継承するらしく、たとえば http://example.org/ から location.href = 'about:blank'; を実行すれば http://example.org を生成元とする about:blank になります (少なくともそのように見えます)。これを利用し、一つのフレームに二つの文書から交互に about:blank を読み込ませあうことで、そのフレームの window.name を両文書で共有できるという仕組みです。

about:blank を読み込ませるのに location.reload() を使えば履歴に余分な項目を残すこともありませんし、フラグメント識別子を使った場合と違ってデータ量にもだいぶ余裕が出ます。

Firefox Developers Conference 20102010年12月28日 00時18分

Firefox Developers Conference 2010 に行ってきた。全体のまとめとしては以下が詳しい。

はてなブックマークで fxdevcon タグがつけられたエントリーを見てまわるのもいい。Firefox 4 ベータ版機能概要では新しいタブインターフェース "Panorama" の紹介動画を見られる。

内容に関しては上述のページを参考にしてもらうとして、個人的に感じたことををいくつか挙げる。

Beyond Firefox 4 (Jay Sullivan)

Mozilla はユーザーの選択肢を広めることを重視する、逆に言えば単一のプラットフォームを目指すわけではないということのようだ。

John Resig feat. Shibuya.js

10+1 Things you should know about JavaScript testing (t_wada)

私の場合、Web ブラウザ向け JavaScript のテストを書いていないのはさくっとテストできる環境を知らないからというのが大きい。Firefox 拡張に関しては UxU という素晴しい環境があったから部分的とはいえテストを書けた。

カスタムイベントで処理をつなぐというのは結構やる。イベントモデルの実装も何回かやったけど、jQuery が使えるなら jQuery の実装 (bindtrigger) を使うのが楽だ。カスタムイベント名には Progress Events 仕様で提案されているものを流用したほうがいいかと思ったが、trigger 時に独自引数を渡すならかえって紛らわしいかもしれない。

// 独自のオブジェクトに jQuery のイベントモデル実装を組み込む例

function Loader() {
    this.$ = $({});
}
$.extend(Loader.prototype, {
    method: function () {
        this.$.trigger('load', args);
    }
});

var loader = new Loader();
loader.$.bind('load', function (args) {});

Node.js にまつわる 7 つの誤解 (meso)

現在、Node.js の開発は個人の手を離れ Joyent 社主導で行われているそうだ。会社によってはライブラリ/フレームワークの採用基準に、それが一定規模の団体によって保守されているかどうかが含まれるだろうから、Node.js の普及を促進する上ではよいことだと思う。

トークセッション: HTML5 時代の技術で Web プラットフォームはどう変わるのか (矢倉眞隆、村岡正和、浅井智也)

W3C CSS working group ではレイアウト関係が要注目とのこと。Flexible Box Layout Module は Mozilla の XUL ボックスモデルに由来するもので、(Mozilla から Apple へ移籍した Dave Hyatt の手により) 類似のモデルが WebKit でも実装されている。ただし、現在の草案は display プロパティの値に "box" ではなく "flex" を採用するなど、両実装と異なる点が多い。Grid Alignment Module は Microsoft が中心となって策定を進めている (編者の一人 Alex MogilevskyFlexible Box Layout Module の編者も兼任)。両モジュールともアプリケーション UI の整形を念頭に置いているが、手軽さでは Flexible Box、柔軟性では Grid Alignment が勝っているように感じる。

まとめとして、矢倉氏は描画には canvas だけでなく SVG、通信には WebSocket だけでなく Server-Sent Events など、あることを実現するのにさまざまな手段があるのを知ってほしいと、村岡氏は積極的に HTML5 を書き、関連 API を使って開発し、そして仕様策定者側にフィードバック (提案) しようと述べていた。

大ライトニングトーク

HTML 2.0 (TAKESAKO)

"2.0" というのはバズワード。HTML パーサのエラー処理方法の違いを利用してブラウザを判別する試みである。HTML5 では HTML 構文の解析方法がエラー処理も含めて規定されるので、今後ブラウザが HTML5 HTML 構文に対応していく中でこのような試みは困難になっていくと思われる。

placeholder 実装マニアックス

テキスト入力欄に何らかのメッセージを表示し、入力欄にフォーカスするとそれが消えるという UI は以前から見られた。HTML5 では placeholder 属性でこのメッセージを指定できるが、未対応ブラウザでこの挙動を再現しようとすればスクリプトを使う必要がある。

まず考えられるのは入力欄の value プロパティにメッセージの文面を設定し、フォーカス移動に際してこれを消去することである。しかしこの場合、フォーム送信時のメッセージ消去や、ブラウザがフォームコントロールの入力値を記憶することへの対処が必要となる。

別の方法として、CSS を使いメッセージと入力欄を重ね合わせることも可能だ。メッセージ部分を絶対配置の要素とし、文書木上では入力欄の直前に挿入する。left 及び top プロパティを指定しなければ絶対配置の要素は「その要素が絶対配置でなかったときの位置」に置かれる。この絶対配置要素の初期位置は意外と便利で、top、left、right、bottom プロパティのいずれかひとつのみ指定すれば、水平方向または垂直方向にだけずらすこともできる。

なお、placeholder に指定するのは入力欄に関する補助的な情報 (なくても問題ないもの) であり、入力欄に対するラベルではない。ラベルは label 要素で指定する。中には入力中に参照したい情報もあるだろうし、状況によっては placeholder の内容が提示されないこともあるようなので、そもそも placeholder を使うべき場面なのか検討したほうがいい。

懇親会

食事があっという間になくなってしまったのが残念だった。

ブラウザの進化速度は速い。できることはどんどん多くなるが、それらをどう組み立てていくかというパターンはまだ不安定に思える。その部分を探っていきたい。

リンクのようなボタンを作る2010年12月15日 23時52分

こんばんは、JavaScript Advent Calendar 2010、15 日目担当の nanto_vi (なんと) です。12 月 15 日が何の日か調べてみると東北本線が宮城県に到達した日とのこと。当時は上野から仙台まで 12 時間 20 分かかったそうです。それから 123 年を経た現在では同じ時間で鹿児島中央から新青森まで行けるようになり、鉄道の速度にも JavaScript の実行速度にも日進月歩を感じる今日この頃です。

さて、アプリケーションを作っていると、見た目はリンクのようだがリンクでない UI 部品を使いたくなるときがあります。ここで「リンクでない」とは、クリックしてもページ遷移が発生しないということです。このような UI 部品は、ページ遷移の代わりにメニューの表示といった何らかのアクションを引き起こす、すなわちボタンとして振舞います。

ユーザーインターフェース記述言語として HTML を使っているとき、この「リンクのようなボタン」をどのように実現すればいいのでしょうか。

input、button 要素

ボタンとして振舞うものはボタンとして記述すべきです。HTML では汎用的なボタンとして <input type="button"> 及び <button type="button"> が用意されています。スタイルシートを使えば見た目をリンクのようにもできるでしょうし、画像を使いたければ <input type="image"> もあります。

<input type="button" value="リンクのようなボタン" onclick="...">
<button type="button" onclick="...">リンクのようなボタン</button>

しかし、フォームコントロールに対してはスタイルシートが期待通り適用されないこともあり、ボタンをどうしてもインライン要素にしたいときにこの方法は取れません。

a 要素

リンクのように見えるならリンクにすればいいということで a 要素が使われることもあります。click イベントを処理するときにデフォルトアクションをキャンセルすれば、もともと指定してあったリンク先に飛ぶことはありません。あるいはリンク先に javascript:void(0); と指定することでページ遷移が発生しないようにします。

<a href="#" onclick="...; return false;">リンクのようなボタン</a>

しかし、a 要素はあくまでハイパーリンク、すなわち他のリソースへ移動するためのものです。外見がどうあろうとリンクでないものの記述に使うの好ましくありません。

span 要素

click イベントに対する処理はどんな要素にも付加できるので、a 要素にこだわる必要はありません。「リンクのような」の部分はスタイルシートで実現できます。

<style>
.trigger {
  color: #00f;
  text-decoration: underline;
  cursor: pointer;
}
</style>

<span class="trigger" onclick="...">リンクのようなボタン</span>

しかし、これではキーボードを用いてボタンにアクセスすることができません。多くのブラウザでは Tab キーでリンクやフォームコントロールへ移動できますが、span 要素はその対象から外れています。

span 要素 + tabindex 属性

tabindex 属性はフォーカス順を変更するものだと思っていたあなた、それはこの属性が持つパワーのほんの一部でしかありません。HTML5 において tabindex 属性は要素をフォーカス可能にする属性として生まれ変わったのです。この属性はどんな要素にもつけられ、値に 0 を指定すればその要素がキーボードアクセス可能になります。

<span class="trigger" tabindex="0" onclick="...">
  リンクのようなボタン
</span>

しかし、Tab キーでこのボタンにフォーカスし、Enter キーを押しても何もおきません。a要素によるリンクなら Enter キーを押すと click イベントが発生するのにも関わらずです。なお、Opera なら span 要素でも Enter キーにより click イベントが発生し、指定したアクションが実行されます。

span 要素 + tabindex 属性 + onkeypress 属性

click イベントが発生しないなら自分で click イベントに対する処理を呼び出せばいいのです。onclick 属性に指定したコードは onclick プロパティから関数として取得できます。Enter キーを表すキーコードは 13 なので、そのときのみ処理を実行します。

<span class="trigger" tabindex="0" onclick="..."
      onkeypress="if (event.keyCode === 13) this.onclick(event);">
  リンクのようなボタン
</span>

しかし、click イベントに対する処理が onclick 属性に書かれているとは限りません。addEventListener や attachEvent メソッドでイベントリスナが追加されていることもあれば、祖先要素、文書ノードで click イベントが処理されていることもあります。また、Opera ではこの場合 Enter キーを押すと onclick 属性の内容が2回実行されてしまいます。

span 要素 + tabindex 属性 + キーイベント処理

click イベントに対する処理を直接実行できなくとも、click イベントを発生させれば自然とそれらが実行されます。イベントを発生させるのに、DOM イベントモデルでは dispatchEvent、IE のイベントモデルでは fireEvent メソッドを用います。Opera 対策に keypress イベントのデフォルトアクションをキャンセルしておきましょう。

<script>
function activate(event) {
    event = event || window.event;
    if (event.keyCode !== 13) return;
    if (document.createEvent) {
        var e = document.createEvent('MouseEvent');
        e.initMouseEvent(
            'click', true, true, event.view, 1,
            event.screenX, event.screenY, event.clientX, event.clientY,
            event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, 0, null
        );
        event.target.dispatchEvent(e);
        event.preventDefault();
    } else if (document.createEventObject) {
        var e = document.createEventObject(event);
        event.srcElement.fireEvent('onclick', e);
        event.returnValue = false;
    }
}
</script>

<span class="trigger" tabindex="0"
      onclick="..." onkeypress="activate(event);">
  リンクのようなボタン
</span>

しかし、視覚的なブラウザであれば見た目からこの要素が何らかのアクションを引き起こすことがわかりますが、環境によってもそもそもこれが「押せる」ということすら伝わらないかもしれません。

span 要素 + tabindex 属性 + キーイベント処理 + role 属性

視覚によらず、機械的に UI 部品を認識するための仕様として WAI-ARIA があります。要素がボタンの「役割」を果たすことを表すには、role 属性の値に "button" を指定します。これで人の目からだけでなく、機械から見たときもボタンとして認識・操作できるようになりました。

<span class="trigger" role="button" tabindex="0"
      onclick="..." onkeypress="activate(event);">
  リンクのようなボタン
</span>

サンプル

リンクのようなボタンのサンプルで実際の挙動を確認できます。

終わりに

以上はあくまで JavaScript の使える環境が前提です。スクリプトが動かなくとも最低限の機能は利用できるよう気をつけましょう。スクリプトが動く場合でも、本当にリンクのようなボタンでなければいけないのか、通常のボタン、またはハイパーリンクでは実現できないのかよく検討した上で使うようにしてください。

参考資料

Kanasan.JS JavaScript 第 5 版読書会 #10 XPath 発表資料2010年09月27日 08時58分

6 月のことになりますが、Kanasan.JS JavaScript 第 5 版読書会 #10 にて XPath に関する発表を行いました。内容は基本的な XPath の解説です。

本番では図の準備が間に合わずホワイトボードに描きながら発表したのですが、逐次挙がる質問に対してもすぐ描き直して対応できたので、かえって理解を深めるのに役立ったのではないかと思います。資料中の注釈は口頭で説明したことなどを書き起こしたものです。

タイトルに excellent と入っていますが何か特に素晴しいということはなく、単に頭韻を踏みたかっただけです。(しかし excellentexpression では ex の発音が違うので踏めてないという……)

勝手に添削: Selection内のHTML Textをいい感じに取得する2010年02月05日 09時15分

というわけでやってまいりましたこのコーナー! 本日のお題は「Selection内のHTML Textをいい感じに取得する - 枕を欹てて聴く」でございます。選択範囲のHTMLソースを抜き出すというやつですね。では早速いってみましょう!

  if(src.focusNode){
    // selection

まずは HTML5 テキスト選択 APISelection オブジェクトが登場! 以後これに対する操作が続きます。しかしこの Slection オブジェクト、getRangeAt メソッドを使うとなんと選択範囲に対応する DOM 2 Traversal and RangeRange オブジェクトが取れちゃうんです!

    // common parent node search
    (以下 21 行省略)

それ Range#commonAncestorContainer で取れるよ!

      // common配下のindexを見て, focus と anchorがどちらが前方かを調べる
      (以下 9 行省略)

それ Range#startContainerRange#endContainer でわかるよ!

      // focusに沿って後方をremove
      (以下15行省略)
      // anchorに沿って前方をremove
      (以下11行省略)

それ Range#extractContents()Range#cloneContents() でできるよ!

というわけで不要な要素を除去する部分は置いといて今までの経過をまとめてみると、

function convertToHTMLString(source, safe) {
  if (!source || (source.getRangeAt && source.isCollapsed)) return '';
  var range = source.getRangeAt ? source.getRangeAt(0) : null;
  var node = range ? range.cloneContents() : source.cloneNode(true);
  if (safe) { ... }
  return new XMLSerializer().serializeToString(node);
}

なんということでしょう! コメント空行抜きでも 86 行あった部分が実質たったの 2 行に!

これに残った部分を付け足していくわけですが、出来上がった品はこちらになります。(お試しはこちら!)

まとめ

以上、もろもろの要因で無駄にハイテンションの nanto_vi (なんと) がお届けしました!