Hatena::Diary

id:HolyGrailとid:HoryGrailの区別がつかない日記 このページをアンテナに追加 RSSフィード

2008-05-15

本気でやるならonclick属性は避けてライブラリを活用すべき

[Think IT] 第1回:そろそろ本気で学びませんか? (1/3)

これ、今この記事書いてる時点で650以上ものブクマがされているんだけれども、あまり内容がよろしくない。

というのも、解説はとても丁寧ですごくよい内容なのだけど、サンプルコードの書き方がどうも古くさい。

onclick属性とか、今時のフロントエンドエンジニアはそんな書き方はしない(と思う)。

なぜonclickをあまり推奨しないのか

やっぱり、エンジニアとデザイナーorマークアッパーとの分業の点でHTMLの属性にスクリプトを書いちゃうのはあんまりよろしくない。

たとえばの話だけど関数の名前を変えたかったり、だとか、HTMLを変更したり、っていうときにミスが起こりやすくなってしまう。

これは分業していなくてもどちらにしろ発生してしまうことだと思う。

他にも前に書いたのだけど、aタグとかでonclickしちゃうとhrefにreturn falseとかjavascript:void(0)とか書かないといけなくなってしまう。

これは、数は少ないとはいえJavaScriptをオフにしているユーザにとってはすごくユーザビリティが悪い作りになってしまうのでできるだけ避けたいところ。

じゃあどうしたらいいの?

ってのは簡単な話で、HTMLとCSSが分けられるようにHTMLとスクリプトも分離してしまえばいいだけの話。

というわけで

ここではonclickというようにHTMLにイベントハンドラを書かなくてもこうすればいいよ、というお話をしたいと思います。

ライブラリは使おう!

さて、本気で始めるならブラウザ間の動作も完璧に理解しなくてはいけない。

が、もちろんそれはとても敷居が高いことだし、今はそのクロスブラウザにおける動作はさまざまなライブラリが吸収してくれている。

せっかくある資源を活用しないのはもったいないので、まずはライブラリを使用した実装を実践したあとに、興味のある人は中身を見ればいいと思う。

クロスブラウザの挙動を押さえるのはライブラリで実装を実践したあとでも決して遅くないよ!

サンプル

id="clickevent"という要素がクリックされたときに関数を実行するようなスクリプトを書いてみよう!

YAHOO UI Libraryの場合

ここからスクリプトをダウンロードしてもいいし、よく使われるyahoo-dom-eventというスクリプトをYahoo IncのAPIサーバからロードしてもOK。

<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/yahoo-dom-event/yahoo-dom-event.js"></script> 

YAHOO.util.Event.onという関数を使ってイベントハンドラを定義してみる。

YAHOO.util.Event.on("clickevent", "click", function(){ alert('clicked!') });

この関数は第1引数

  • id
  • HTML
  • それらの配列
  • ノードリスト

を渡して、その渡された要素に対して第2引数のイベントが発生したときに第3引数のコールバック関数を実行するための関数。

上のスクリプトを一つの文章にすると

id="clickevent"の要素がclickされたときにcallback関数(clicked!とアラート表示)を実行する

もちろん、次のようにあらかじめコールバック関数を定義しておいてもOK

var Func = function(){
  alert('clicked!');
}
YAHOO.util.Event.on("clickevent", "click", Func);

さて、実は上のスクリプト、何も気にせずに書いていると実は動かない。

おそらく、普通JavaScriptを書くとき、何も気にせず<head>に囲まれた部分に書いてしまいませんか?

JavaScriptというのはその行が読み込まれた時点で実行をするために、もし<head>部分に単純に上のスクリプトを書いているとDOMツリーの構築が完了していないため、id="clickevent"という要素が見つかりませんよ、というエラーが返ってくると思います。

そこで、次のようなコードに修正して、DOMツリーが構築された時点でイベントハンドラを設定するようにしてやりましょう。

function Func(){
  alert('clicked');
};
YAHOO.util.Event.onDOMReady(function() {
  YAHOO.util.Event.on("clickevent", "click", Func);
});

YAHOO.util.Event.onDOMReadyというのは、その名前の通り、DOMツリーが構築完了した時点で、渡された関数を実行するための関数です。

この中にYAHOO.util.Event.onを書いてやることで、正常にイベントハンドラが設定されることになります。

一番最初にあげた記事のサンプルには「onload」イベントハンドラでinit関数を実行していますが、これもあまりおすすめしません。

というのも、onloadイベントハンドラはブラウザによって「DOMツリー構築完了」の場合もあれば「画像も含めてHTMLの読み込みがすべて完了後」の場合もあるからです。

現時点では後者の実装の方が多いために、大きい画像が用意されているページでは、画像が読み込み終わるまでに初期化関数が実行されなくなってしまいます。

そのため、上のようにライブラリを利用し、DOMツリーが構築された段階でイベントハンドラを設定してやるようにしましょう。

cf. YUI Library Examples: Event Utility: Using onAvailable, onContentReady, and onDOMReady

APIドキュメント

ここを参考にいろいろやってみよう!

http://developer.yahoo.com/yui/docs/

jQueryの場合

jQueryは今もっとも熱いライブラリ!

今使うならこれをオススメする。

さて、上と同じことをjQueryでやってみましょう。

$('#clickevent').click(function(){
  alert('clicked');
});

jQueryの特徴はチェーンメソッドといって、$()関数で要素を決定したあとに、ひたすら「.(ドット)」でメソッドをつなげていくこと。

$()関数に続いて「.(ドット)」でつながれた関数が$()関数で指定された要素に適用されていく。

つまり、上の場合は#clickeventというCSSセレクタで指定された要素に対して「click」というメソッドがつながれているので、#clickeventがクリックされたときに、関数が実行される、という形になる。

さて、これも上と同じようにこのまま書いてしまっても動かない。

jQueryの場合は次のように書いてあげよう。

$(document).ready(function(){
  $('#clickevent').click(function(){
    alert('clicked');
  });
})

また、上のような書き方は次のように省略して書くこともできます。

$(function(){
  $('#clickevent').click(function(){
    alert('clicked');
  });
});

YAHOO UI Libraryに比べてかなりスマートに書けましたね。

cf. .ready() – jQuery API

APIドキュメント

ここを参考にいろいろやってみよう!

http://docs.jquery.com/Main_Page

まとめ

  • onclick属性は使うと後々で面倒!
    • 運用負荷はやっぱり下げたいよね!
  • ライブラリを恐れるな!
    • ライブラリを一度使っちゃうと依存しちゃうかも、でもやっぱりまず動くものを作ることが重要だよね!

といった感じでしょうか。

他にもいろいろと説明したいことはあるのですが*1思ったよりも長くなってしまったので今日はこの辺でおしまいです。

今回の内容に興味を持った人は是非

  • addEventListener
  • attachEvent

について調べてみてください!

追記

これよりも本当に前の段階でJavaScriptを本気で勉強したいのであれば

のような内容が適切だと考えています。

JavaScript 第5版

JavaScript 第5版

*1:キャメルケースの話とか、YUIの時はaタグのclickにイベントハンドラやるときはYAHOO.util.Event.stopEvent(e)とかで遷移を制限しないといけない、とか

twktwk 2008/05/16 11:46 こうやってるんですけど、ページの読み込みが遅い時イベントハンドラが設定される前に押してhrefの方に遷移されてしまったりしません? 本当は関数もheadに書いてonxxx属性通した方がユーザーには優しいんじゃないかと思わないでもない今日この頃。

HolyGrailHolyGrail 2008/05/16 12:01 >twkさん
たしかにそういう状況が発生しうる危険性は十分にあると思います。
でもそれはHTMLの組み方に問題がある場合がほとんどで、たとえばHTMLの途中に処理の重いJavaScriptが組み込まれていたりと、何かしらの理由が存在しています。
こういった部分を解消したり、他には、どうしても重要な部分については
YAHOO.util.Event.onContentReadyという関数を利用したり、jQueryならready()で、documentではなく、要素を指定することで、特定の要素が現れた段階でイベントハンドラを設定できるので、そのような実装をしてみるのもいいかもしれません。

ちなみに記事の例はscriptを全てhead内で記述した場合の例です。
ライブラリを用いないで上の関数のような動作を実装したい場合はhead内に関数をかき、その要素直下にscriptを書いてやるといいかと思います。

ukstudioukstudio 2008/05/16 12:11 JS勉強会、またやりたいねぇ

HolyGrailHolyGrail 2008/05/16 13:44 >ukstudio
オフライン版Roppongi.JSフラグktkr!

sharpoonsharpoon 2008/05/16 15:52 分業の問題なのですが、onclick=”関数名()”がid=”ID名”となっているだけで、コードのインターフェース部分とHTML部分がなんらかの規約に基づいて結合していることには変わらないと思うのですがどうでしょうか?
私はむしろ、onclickというイベントのトリガー自体がビューであるHTMLに記述されているほうが、より明示的であると思うのですが。

sharpoonsharpoon 2008/05/16 15:56 ごめんなさい。ちょっと説明不足なので追記です。
イベントトリガが「明示的」であることの利点は、例えばUIデザイナはHTMLを書いている状況の場合、HTML自体にスクリプトのどのロジックがどのようなイベント(onclick)によって呼び出されるのかをUIデザイナが明示的に指示できる型式のほうが、場合によってはいい事もあるんじゃないかなあと思ったりしたわけです。
とはいえ、HTMLコードは醜くなるし、onclickもアレなんですけどね。

yukichiyukichi 2008/05/16 20:15 そう思っていた時期がわたしにもありました。
だけどむしろ生産性は低下。
考えとしては良い方向だと思うんですけどね。
テンプレートのループの中にアンカーがあったとして、それらに全部idを振った上さらにイベントをセットしなきゃいけないなんてことになるとそれはもう面倒極まりない。
妥協してここだけは仕方ないか・・・なんてことになるとテンプレートの規則が崩壊。

HolyGrailHolyGrail 2008/05/16 22:34 >sharpoonさん
私が問題だと考えているのはそこがスクリプトになっている、という点だと考えます。
たとえばマークアッパーがエンジニアに言われてスクリプトを書いたり、もしくはエンジニアがHTMLに手を加える、またはマークアッパーがエンジニアに言われた通りにHTMLに手を加えるといった事態が発生するかと思います。
お互いが、正しく把握していないものに手を加えるのはやはり危険ですし、そのやりとりの回数はできるだけ減らすにこしたことはありません。。
イベントハンドラ属性を使用しないことにより、要素のやりとりだけで、そこにお互いの手が入る必要はなくなります。
今回はあくまで挙動を押さえるためにシンプルなサンプルのためにそこまで考えが回らなくなりがちですが、これが大きくなればなるほど、また動作が複雑になればなるほど、上記のような弊害は大きくなっていきます。
>>場合によってはいい事もあるんじゃないかなあと思ったりしたわけです。
はい、場合によってはもちろんいいと思います。
たとえば一人で何か小さなウェブサイトを作ってちょっとしたJavaScriptによる動作を仕込みたい、とかの時は確かにその方がわかりやすいし楽な方法の時もあるでしょう。
ただ、やはりこれから始めよう、という人に勧めるやり方ではないのかなと、個人的には思います。
本当に小規模なページで作る人がわかった上で利用するonClick属性であれば、それは悪ではないと思いますので。

>yukichiさん
うーん、申し訳ないのですがそれはHTMLの組み方が悪かったり、スクリプトの書き方が悪いとしか言えないと思います。
ループ中のアンカーに・・・、となればそれこそもっともこの記事の方法が最良の選択であり、たとえば簡単なサンプルを示すと、次のようなコードを書けば一発だと思います。

// ループ中のアンカーを取得
var a = document.getElementById(’loop’).getElementsByTagName(’a’);
var aL = a.length;
for(var i=0;i<aL;i++){
// アンカーのイベントハンドラをセット
YAHOO.util.Event.on(a[i], ’click’, function(e){ alert(’clicked!’); });
}

としてやるだけで必要なid属性は1つで済みます。
もちろんHTMLやスクリプトの書き方次第でid属性なしでもどのようにでも操作できると思います。

これは私の経験則で申し訳ないのですが、綺麗なHTMLは綺麗なスクリプトで処理できるため、まずはそこから見直しが必要なパターンなのではないか、と思いました。

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。