あまり新しくないもの

新しさはそんなに求めず、自分のすきなことをやりたい人生だった

jQuery の.on()をもういっぺん理解してみる

jQueryのイベントハンドリングには、現在 .on(),.off()のみが推奨されて利用されています。

その基本的な使い方はこんなかんじでした。

$(element).on(event, function() {
    console.log('event fired!');
});

この場合、対象となる$(element)において指定したeventが発火した時に、コールバック関数が呼び出されます。非常に直感的ですね。

ところで、これは以前の.bind()と全く同じです。

.bind()の場合は、指定したずばりその要素に対してのみ、イベントハンドリングを行います。よって、.bind()が呼び出されたその時点で存在しない要素に対しては、たとえセレクタが一致していたとしてもハンドリングが行われません。

このことを利用すれば、イベントハンドリングをしたいまさにその要素にのみ、たとえその後同じセレクタが生成されてこちらにはイベントハンドリングを行いたくない場合でも、イベントハンドリングを行うことが出来ます。

<div class="parent">
<a id="target"></a>
</div>
var $parent = $('.parent'),
    $a = $parent.find('a');
$a.on('click', function() {
    console.log($(this).attr('id'));
});

var $dom = $('<a id="notTarget"></a>');
$parent.append($dom);
// #target をクリックするとイベント発火
// #notTarget をクリックしてもイベント発火しない

delegate使う

delegateってなにかっていうと、親要素に対してイベントハンドリングを一度だけ行って、その子要素を監視する感じのやつです。

言葉で説明してもわかりにくい系の概念なので、コードを書きます。

<div class="parent">
<a id="target"></a>
</div>
var $parent = $('.parent'),
    selector = 'a';

$parent.on('click', selector, function(e) {
    console.log($(e.currentTarget).attr('id'));
});

var $dom = $('<a id="targetToo"></a>');
$parent.append($dom);
// #target をクリックするとイベント発火する
// #targetToo をクリックしてもイベント発火する

はい、これでどっちのa要素をクリックしてもconsole.log流れます。

要は、いつ対象となるセレクタを持ってる要素が追加されようが、イベントハンドリングした親要素の中に入る限りは、いつだってイベント発火でコールバック関数を呼べるようになるんです。

これは特にajaxでバンバン要素を追加したり削除したりするようなwebアプリケーションで効果を発揮して、かなりメモリを節約出来る感じです。あといちいちイベント張らなくていいので、管理がものすごい楽です。コードも綺麗になる気がします。

delegateとlive

ところで.delegate()の他に.live()っていうメソッドも実はあります。ありますが、ほとんどやってることは同じで、親要素が特定のセレクタなのか、documentなのか、というくらいしか違いがない(と僕は認識してる)ので、.on()を使ってる限りは意識する必要ないです。

ただ、on()の場合は書式が統一されていますが、.bind(), .delegate(), .live()はそれぞれ書式が全然違うので、覚えることが増えて面倒なので.on()で統一するのが楽です。というか.on()が公式で推奨してるメソッドなので.on()にしましょう。

.off()

特にナニも言うことはありません。

普通に使って下さい。 うそです。.off()は実は.on()よりもわかりにくいメソッドな気がしているのでき ちんと整理してみます。

まず、すべてのイベントを削除する場合。

$(element).off();

これでこの要素に紐付いたイベントは全て削除されます。全て削除されるので、下手に使うとライブラリ側がハンドリングしたイベントまで削除されたりして、非常に危険なので非推奨です。

$(element).off(event);

これで特定のイベントだけ削除できます。これでも、だいぶ被害の範囲は少なくなりそうですが、まだまだ油断できません。

$(element).off(event + '.namespace');

はい、これで確実に安全です。jQueryのイベントハンドリングでは、各イベントに対してnamespaceを指定できます。こんなかんじに。

$('a').on('click.myEventSpace', function() {
    console.log('fire');
});
$('a').on('click.hisEventSpace', function() {
    console.log('he is Jhon Do');
});

このときa要素をクリックすれば当然二つのログが流れるわけですが、この後更に

$('a').off('click.myEventSpace');

としてやると、それ以後aをクリックしてもhe is Jhon Doとだけ流れて、1つ目のイベントのコールバック関数は実行されなくなります。

これがjQueryがもつイベントのnamespaceです。これを適切に利用できれば、余計なイベントハンドラーを削除しなくてすみそうです。

delegate

delegateでハンドリングしたイベントについて考えましょう。 考えたいですが、ここまでくればとても簡単なのでサクッと書きます。

$(parentElement).off(event, selector);

こうです。要するに.on()の書式はおなじです。

また、delegateしたイベントハンドラーのすべてを削除したい場合は、

$(parentElement).off(event, '**');

とすることもできます。この場合、delegateじゃないイベントについては削除されません。

さらに、.on()のコールバックを設定するときに特定の有名関数を使っていた場合は、その関数名を指定することでその関数だけを削除することが出来ます。

$(parentElement).off(event, selector, functionName);

簡単ですね。


まとめ

.on().off()は古いバージョンのjQueryでいうところの、 .bind().live().delegate() の三つのメソッドを共通の書式で書けるように統合した便利な感じのメソッド。 それぞれの性質を理解した上で使えばより一層便利できれいな感じにコードを書ける。

あとnamespaceは大事。

Grunt 0.4.2 がリリースされたようです

Gruntの0.4.2がリリースされたようです。 いくつかのバグフィックスのほか、一部のモジュールが外部ライブラリ化されたようなので、該当するモジュールを使っている場合は注意が必要そう。

該当する新しい外部ライブラリ名と、それに対応する以前まで実装されていたモジュール名は下記の通り。

  • glob
    • grunt.file.glob
  • minimatch
    • grunt.file.minimatch
  • findup
    • grunt.file.findup
  • lodash
    • grunt.util._
  • underscore.string
    • grunt.util._.str
  • hooker
    • grunt.util.hooker
  • async
    • grunt.util.async
  • getobject
    • grunt.util.namespace

今後これらの機能を利用する場合は、

npm install <module> --save-dev

した後、下記のようにGruntfile.jsrequireしてから使うことになります。

var _ = require('loadsh');
var newArr = _.map(arr, fn);

名前が変わっているのもあるので特にそれらは注意かも。


なお、以前のままのコードでも一応使えるようですが、非推奨とのことなので早めに対応したほうがよさそうです。

以前のコードのままでもどのみち外部ライブラリのnpm installは必要みたい?(未確認)

Tweet後のコールバックをとる

サイト内でツイートしてもらって、その完了を確認して何かしら関数を実行したい時があると思います。(僕は有りました)

そういう時に使うやつです。

なお、IE8以下ではイベントが上手く発火しないので動かないのと、スマートフォンでは別タブでのjs実行がそもそも難しいのでうまくいかないです。PCのモダンブラウザとIE9以上のみです。

var Twitter = {

    init: function(callback) {
        var self = this;
        window.twttr = _chktwttr(document, 'script', 'twitter-wjs');
        window.twttr.ready(function (twttr) {
            self._setCallback(callback);
        });

    },

    _setCallback: function(callback) {
        twttr.events.bind('tweet', function(e) {
            if( typeof(callback) == 'function') {
                callback();
            }
        });
    },

    _chktwttr: function(d,s,id) {
        var t, js, fjs = d.getElementsByTagName(s)[0];
        js = d.createElement(s);
        js.id = id;
        js.src = "//platform.twitter.com/widgets.js";
        fjs.parentNode.insertBefore(js, fjs);

        return window.twttr || (t = { _e: [], ready: function(f){ t._e.push(f); } });
    }

};

こういうオブジェクトを用意して、

<a href="https://twitter.com/intent/tweet?text=test" class="tweet">ツイートする</a>

<script>
Twitter.init(function() {
    // ツイート完了後に実行される
    console.log('complete tweet');
});
</script>

呼び出します。ツイートボタンの呼び出し先はintentになってます。そこだけ注意すれば後は普通に使えばおっけーです。

jsのゼロパディング

よく使うやつ。

function zeroPadding(num, order) {
    var r = new Array(order);
    return (r.join(0)+num).slice(-order);
}

console.log(zeroPadding(1,2); // -> 01
console.log(zeroPadding(120,5); // -> 00120

一行で書くなら

function zeroPadding(num, order) {
    return ((new Array(order)).join(0)+num).slice(-order);
}

やってることは下記の通り。

  • 長さだけある空の配列を用意する
  • 0を連結文字として配列をjoinで結合して必要な長さの0が連続した文字列を確保
  • 対象の数値をそこに結合して00000123みたいな文字列を生成
  • 最後にsliceで、文字列の後ろから必要な桁数だけ取得する

ChromeのdevToolsで擬似要素が見えるようになってた

今までもElementのstyleをみれば、擬似要素がどう設定されているかとかみれてましたが、 いつのまにか擬似要素自体がElementとして表示されるようになってました。

こんな感じ。

f:id:rtshaaaa:20131115100515p:plain

これでだいぶデバッグが楽になりそうです。