Displaying posts tagged with

“jquery”

Rails3でjQueryを使う場合の注意点

新規プロジェクトの作成時に -J オプションを付けてprototype.jsの読み込みをスキップした場合、config/application.rb の中で以下の設定が有効になる。

config.action_view.javascript_expansions[:defaults] = %w()

あとからjQueryを使うためにjquery-railsをインストールしたとき、この設定が有効なままだとビューの中で

javascript_include_tag :defaults

を書いてもjquery.js等が読み込まれない。

忘れずにコメントアウトしておく(コメントアウトすることで、jquery-railsが自動設定した値が有効になる)。

#config.action_view.javascript_expansions[:defaults] = %w()

あるいは、jqueryとapplication.js以外に常時読み込みたい.jsファイルがあるのなら、+= で配列に追加しておく。

config.action_view.javascript_expansions[:defaults]+= %w(mylib.js otherlib.js)

jQuery 1.5の新機能: jQueryのサブクラスが作れるようになる

jQuery 1.5 beta1 のリリースノートを見ていたら面白そうな機能があったので紹介。

1.5正式版では jQuery.sub という名称に変更になったので、本文およびサンプルコードを修正しました。

jQuery.sub() でjQueryのサブクラスを作ることができる

var MyjQuery = jQuery.sub();

作成したサブクラスには独自のメソッドを定義することができる。定義の仕方、使い方は通常のjQueryと全く同じ。

MyjQuery.fn.writeHello = function(){ this.text('Hello World'); };
 
MyjQuery('p').writeHello();

通常のjQueryメソッドはサブクラスでもそのまま使える。サブクラスを作った後に追加したメソッドも使えるので、プラグインによる拡張と組み合わせても大丈夫。

MyjQuery('div').css('border', '1px red solid').writeHello();
 
//元のjQueryを拡張
jQuery.fn.pluginMethod = function(){ alert('OK'); };
 
//OK
MyjQuery('.klass').pluginMethod();

逆にサブクラスで追加したメソッドは元のjQueryからは見えないので、プロジェクトやプラグインに固有なメソッドを定義したり、元のjQueryにあるメソッドを上書きしたりといったことが安全にできる。

// text	メソッドを上書きする
MyjQuery.fn.text = function(val) {
  var orig = jQuery.fn.text;
  if(typeof val === "string") {
    return orig.call(this, "!!"+ val +"!!");
  } else {
    return orig.apply(this, arguments);
  }
};
 
// 上書きされたtextを呼び出す
MyjQuery('#ex').text("foo");
 
// 引数を与えなければ元のメソッドが呼ばれる
MyjQuery('#ex').text();	//!!foo!!
 
// もちろん元のメソッドには影響ない
jQuery('#ex').text('foo'); //foo

さらに応用として、プラグインで拡張メソッドを提供する場合の名前空間としても使えるかもしれない。例えば私が前に作った動的にSELECTを構築するためのjQueryプラグインは、グローバルなメソッド空間に2つのメソッドを追加してしまうのであまりマナーが良くない。

$('select').addOption(...);
$('select').clearOptions(...);
 
//本当は次のようにしたいが、動作しない
$('select').optionBuilder.addOption(...);
$('select').optionBuilder.clearOptions(...);
 
//次のような実装が推奨されている
$('select').optionBuilder('addOption', ...);
$('select').optionBuilder('clearOptions', ...);

1.5のサブクラス機能を使えば、次のように隔離されたサブクラスでのみ使用可能なメソッドとして提供することができる。

//サブクラスを使えば直接呼び出せる
var optionBuilder = jQuery.optionBuilder();
optionBuilder('select').addOption(...)
optionBuilder('select').clearOptions(...);
 
//関数の中に隔離する方法も
jQuery.optionBuilder(function($){
    $('select').addOption(...);
    $('select').clearOptions(...);
});

実装は次のような感じになるだろう。

/*
 * jQuery.optionBuilder.fn[method](...);
 * を
 * jQuery.fn.optionBuilder(method, ...);
 * にマップする
 */
jQuery.optionBuilder = (function(){
  var sub = jQuery.sub(),
      base = jQuery.fn.optionBuilder,
      slice = Array.prototype.slice,
      methods = ['addOption', 'clearOptions'];
 
  $.each(methods, function(i, method){
    sub.fn[method] = function(){
      return base.apply(this, [method].concat(slice.call(arguments)));
    };
  });
  return function(fn){
    if(typeof fn === 'function') {
      return fn(sub);
    }
    return sub;
  };
}());

参考:

Google AJAX Feed APIを使って複数のフィードをまとめて表示するjQueryプラグイン

Google AJAX Feed APIを使うとJavaScriptだけでRSS/ATOMフィードを読み込むことができますが、標準の機能では「複数のフィードをまとめて」「日付順に(新しい順に)」表示することができないので、jQueryプラグインを作りました。

jquery.googleMultiFeed.js (github)

タイマーを使って逐次処理を行っているので、全フィードの読み込みを待つことなく、読み込まれたものから順に表示されていきます。Google AJAX Feed APIの(というよりJSONPの)制約でフィードを並列的に読み込むことはできないのですが、Google側でキャッシュ&タイムアウト処理が行われているため、多数のフィードを読み込んでもさほどストレスなく表示されるはずです。

jQuery 1.4以上が必要です。jqueryの.jsファイルはscriptタグを使って静的に読み込んでください。どうしても google.load を使う必要がある場合は setOnLoadCallback の中で initGoogleMultiFeed() を呼ぶ必要があります。

<script type="text/javascript" src="http://www.google.com/jsapi?key=API_KEY"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript" src="jquery.googleMultiFeed.js"></script>
<script type="text/javascript">
//google.load('jquery', '1.4');
 
google.setOnLoadCallback(function(){
  //jqueryをgoole.loadで読み込む場合のみ必要
  //initGoogleMultiFeed();
 
  $('#feed').googleMultiFeed({
    urls: ['http://example.com/rss', 'http://example.net/feed'],
    numEntries: 5
  });
});
</script>
...
<div id="feed"></div>

urls パラメータは必須で、読み込みたいフィードのURLを配列として指定します。 numEntries パラメータはオプションで、フィード一つあたりの件数です。

上の例を実行すると http://example.com/rss と http://example.net/feed から5件ずつフィードを読み込んで、新しい順に並び替えた上で、指定したタグの中に次のようなHTMLで表示されます。

<div>
<a title="エントリータイトル | フィードタイトル" href="リンク">エントリータイトル [フィードタイトル]</a>
<span class="entry-date">日付(YYYY-MM-DD)</span>
</div>
...(フィードの件数分繰り返し)...

表示されるHTMLをカスタマイズするには format パラメータでフォーマット用の関数を指定します。例えばエントリータイトルとリンクだけをリスト形式で表示したいなら、次のような関数を指定します。

//第一引数がエントリ、第2引数がフィード
//詳細は http://code.google.com/intl/ja/apis/feed/v1/reference.html#JSON
function titleOnly(entry, feed) {
  var link = $('<a />').attr({href: entry.link}).text(entry.title);
  return $('<li />').append(link);
}
 
//ULタグに対して追加する
$('ul#feed').googleMultiFeed({
  urls: ['http://example.com/rss', 'http://example.net/feed'],
  format: titleOnly
});

その他、loadCompleted パラメータにカスタム関数を指定すると、全てが読み込まれた後に”読み込み中インジケータ”を消す等の処理も行うことができます。有効なパラメータについてはGitHub上のWikiを参照してください。

以下のサイトなどを参考にしました。

Topsyのotter APIを使ってtwitter検索

Topsyが提供しているOtter APIを使ってTwitter検索。

特定のURLに対するツイート一覧を得る

特定のURLに対するツイートは trackbacks というリソースで取得できる。

例: http://wp.serpere.info/に対するツイートを10件までテキストフォーマットで得る

パラメータはtrackbackリソース固有のものと、共通のリスト用のものが使用できる。orderパラメータでレスポンスの順序を指定できるとあるのだが、現時点(2010-11-01)では正しく機能せず、何を指定しても常に新しい順で返ってくるようだ。

レスポンスの形式は拡張子で指定できる。デバッグ時はテキスト形式(.txt)が便利。実際にプログラムから使用する場合はJSON形式(.json)またはJSONP(.js)を使う。JSONの構造については今のところ実際に動かしてみて確認するしかない。

例: http://wp.serpere.info/に対する最新のツイート10件をjQueryによるJSONPで取得・表示する

otter API trackback resource – jsdo.it – share JavaScript, HTML5 and CSS

サイト全体に対するツイート一覧を得る

サイト全体(特定のドメイン以下のページ)に対するツイートは search リソースで site: クエリを使うことで得られる。

例: このサイト全体に対する言及ツイートを、過去1ヶ月分10件まで取得

使用できるパラメータはsearchリソースに対するものと共通のリスト用オプション。windowオプションでは対象となるツイートの期間を指定する。d10で10日、w2で2週間、aで全期間など。

このsearchリソースの仕様には色々と謎が多い。

  • 新しい順にソートして取得するためのsearchdateリソースを使った場合と、取得できる内容が大きく異なる。searchdateの方が反映が遅い?
  • 効かないはずのorderオプションが効いている? order=date に設定すると searchdate を使った場合と結果が同じになる?

例: このサイトに対する過去1か月の言及ツイートを20件、jQueryによるJSONPで取得し、自分(tkykmw)以外のものを表示

otter API search/searchdate resource – jsdo.it – share JavaScript, HTML5 and CSS

Kanasan.JS jQuery コードリーディング#2に参加しました

9月26日にKanasan.js主催で開催された「jQuery コードリーディング#2」に参加してきました。

複数人でペースを合わせてコードを読んでいくのは初めてだったので、最初は勝手がつかめなくて戸惑いましたが、慣れるとかなり面白かったです。一人で読んでいたら何気なく読み飛ばしてしまうような箇所でも、他の人の質問を受けて改めて読み返すと意外な発見があったりして、なかなか新鮮な体験でした。自分も途中からはほとんど独り言をつぶやくようなつもりで発言しまくっていました。

ただコードを読む前に、まずjquery.comのAPI Referenceを読むべきだったかなと思いました。たとえよく使うメソッドであっても、意外と知らない機能が隠れていたりするもので、そうなるとコードの意図を推測する時間が余計にかかります。例として jQuery.map が挙げられます。このメソッドは、JavaScriptの Array.prototype.map と違ってLispの mapcan みたいな機能を兼ねています。すなわち null, undefined は無視され、配列は concat されます。

$.map([1,2,3,4], function(x){ if(x % 2 == 0) return [x, x]; });
// => [2,2,4,4]

この動作を頭に入れておけば、最後の concat.apply は配列を一次元flattenするためのイディオムであることもすぐに見当がつきます。

ほとんど使ったことがないメソッドについては、この”意図を知る”作業は決定的に重要になります。今回の範囲で言うと私は queue, dequeue を全く使ったことがなかったので、かなり苦労しました。「これらはアニメーション(エフェクト)の基礎になっているメソッドだ」と別の方が指摘してくれたお陰でだいぶ助かりましたが、最初からAPIリファレンスを読んでいれば、もっと手っ取り早く正体がつかめたはずです。

実際にはこのqueque/dequeueは、非同期で実行される操作(関数)をあらかじめキューに溜めておいて、実行順序を保証するための仕組みです。アニメーション以外の例では、例えば jQuery.getScript によるスクリプトを読み込みを非同期かつ安全に実行するために使用できます。

/**
 * - LibraryA,
 * - LibraryA に依存する LibraryB
 * - LibraryA と LibraryB を使用する処理
 * を順番に実行する.
 * 読み込みが失敗したら後続の処理は実行しない
 */
 
// script-loader という名前の queue を使用する
jQuery.fn.loaderQueue = function(func) {
    return this.queue('script-loader', func);
}
jQuery.fn.loaderDequeue = function() {
    return this.dequeue('script-loader');
}
 
// スクリプト読み込みが成功したら dequeue する $.getScript
function dequeuingGetScript(url) {
    return function(next) {
        jQuery.getScript(url, function(data, status) {
                next();
            });
    };
}
 
jQuery(document)
    .loaderQueue(dequeuingGetScript('libraryA.js'))
    .loaderQueue(dequeuingGetScript('libraryB.js'))
    .loaderQueue(function(){ alert(LibraryA.func() + LibraryB.func()); })
    .loaderDequeue();

ただ実装の中ではエフェクト用の処理(fx queue)を決め打ちで特別扱いしているので、メソッドを分割した方が良さそうだよね、という意見が会場で出ました。

……

その他諸々、会場での話題や気づいた点など。

  • ローカル関数 access は非常に複雑かつ名前も分かりにくすぎる。特に第6引数 pass が何のためにあるのかわからない。一応
    $('#sss').attr({ key:function(){  }}, true)
    という形で使用すれば機能することはわかったが、何に使うか不明。
  • jQuery.support.hrefNormalized の名前が意味と逆転している。
  • expando って何?=>実行時に動的に追加されるプロパティのこと。
  • ローカル関数 eventSupported は特定のイベントがbubbleするかどうかのチェックに使われている。IEではsubmit/changeはbubbleしない。jQuery 1.4からはjQueryがエミュレートしてくれるようになった。
  • jQuery.data まわりのコードで jQuery.expando とローカル変数 expando の参照が入り交じっている。ケアレスミス?
  • 変数名 jQuery.cache は変。何かをキャッシュしているわけではなく、単なるデータストア。

あと懇親会ではバイクで行ったせいでお酒が飲めず、大変残念な思いをしたので、次に参加するときには絶対にバスか電車で行こうと思いました。

jQuery History is now on Github

I created a fork of jQuery History plugin on Github.

Though this is just a fork for now, Mikage Sawatari, the original author of this plugin, and I agreed that it could be the mainstream in the near future.

I’ve already done some major changes to the script structure:

  • It no longer uses the Webkit-specific hack for updating the location object, because recent versions of Webkit can correctly update the object just like Firefix and IE8.
  • Hashes are percent-encoded with encodeURIComponent before being put to location.hash. So you can use any characters such as `?’ in your hash.
  • history.load does not execute the callback unless hash is changed.
  • Updated API/code format. Thanks to Kevin Dalman.

It works on IE6, IE7, IE8, Firefox3, Safari4, Chrome4, and Chrome5.

If you have any problems or feature requests, please create a new issue in the ITS. And of course, you can create your own fork.


jQuery Historyプラグインは今後Githubで管理されることになりました。

Mikage Sawatariさんによる元々のバージョンに対し、次のような大きな変更が加えられています。

  • Webkitで動かすためのハックは不要になったので取り除かれました。
  • location.hash に代入する前に encodeURIComponent によるパーセントエンコーディングを行うようにしました。これによって ? を含むあらゆる文字を hash として保存できるようになりました。
  • ハッシュが変更されていない場合はコールバックが実行されないようになりました。
  • API/コーディングスタイルを変更しました。この変更はフォーラムのKevin Dalman氏の投稿に依ります。

IE6, IE7, IE8, Firefox3, Safari4, Chrome4, Chrome5で動作します。

Githubのバグトラッキングシステムに登録してください。もちろん自分でforkを作って修正していただいても構いません。

Cicindelaを使ったAjaxアプリケーションサンプル 2

ライブドア製レコメンデーション・エンジン Cicindela の動作を確かめるために、簡単なAjaxアプリケーションを作ってみました。

前回の続きです。

JavaScript: Logger

Cicindela との連携部分を作る前に、簡単な Logger を作っておきます。ログの出力先には TEXTAREA 要素を想定しています。

var Logger = {
    clear: function() {
        $('#log').val("");
    },
    append: function(text) {
        var dom = $('#log').get(0);
        dom.value+= text + "\n";
        dom.scrollTop = dom.scrollHeight;
    }
};

Cicindela への入力

まずは入力部分から作っていきます。このアプリケーションにおいて必要な操作は「あるユーザがアイテムを選択した」、「あるアイテムをあるカテゴリに所属させる」の2つです。これらの操作を抽象化するために、Cicindela Web APIに対する Proxy となるオブジェクトを作ります。

var Cicindela = {
    base: '/cicindela/',
    set:  'sample',
    _insert_op_url: function(op, item) {
        return this.base +"record?set="+ this.set +"&item_id="+ item + "&op="+ op;
    },
    _send_req: function(url, cb) {
        Logger.append("send Request to " + url);
        $.ajax({ type: "GET",
                 url: url,
                 success: cb,
                 complete: function(xhr, status) {
                     Logger.append("receive Response["+ xhr.status +"] from "+ url);
                 }
               });
    },
    set_category: function(item, category) {
        this._send_req(this._insert_op_url("set_category", item) + "&category_id="+ category);
    },
    insert: function(user, item) {
        this._send_req(this._insert_op_url("insert_pick", item) + "&user_id="+ user);
    }
};

insert がアイテム選択、set_category がカテゴリ登録のためのメソッドです。実際にリクエスト送信を行うのは _send_req メソッドで、送信先URLと成功時に呼び出されるコールバック関数とを引数にとります。set プロパティには集計セットの名前を書いておいてください。

あとは「insert」ボタンが押されたときに、これらの API を呼び出します。

  // $(document).ready において
  $('button#insert').click(
      function(e) {
        var user_id = $('#username').val();
        if(user_id == "") {
          return false;
        }
        $.each(data, function(category_name, category_data) {
                   var category_id = category_data.id;
                   var container = $("#"+ category_name);
                   var values = container.val();
                   $.each(values,
                          function(i, v) {
                              Cicindela.set_category(v, category_id);
                              Cicindela.insert(user_id, v);
                              $('#username').val("");
                          });
               });
        return false;
    });

このアプリケーションの場合、カテゴリ登録はデータの初期化時にまとめて行った方が効率的なのですが、「同じアイテムを、何度同じカテゴリに登録しても問題ない」ことを確かめるために、あえてアイテム選択時に毎回登録を実行しています。

これで Cicindela への入力準備が整いました。ユーザ名を入力して、適当にアイテムを選択して「insert」ボタンを押してみます。成功すれば次のようなログが出力されるはずです。

send Request to /cicindela/record?set=sample&item_id=Ruby&op=set_category&category_id=1
send Request to /cicindela/record?set=sample&item_id=Ruby&op=insert_pick&user_id=cicindela
receive Response[204] from /cicindela/record?set=sample&item_id=Ruby&op=insert_pick&user_id=cicindela
receive Response[204] from /cicindela/record?set=sample&item_id=Ruby&op=set_category&category_id=1

またMySQLサーバにログインして、categories テーブルや picks テーブルにデータが入っていることを確認します。

Cicindela からの出力

続いて Cicindela からレコメンデーション・データを取り出してみます。必要なのは「特定アイテムに対するレコメンデーション(関連アイテム)取得 (=item to item)」なので、次のような get_for_item メソッドを Cicindela オブジェクトに追加します。

var Cicindela = {
    /* ...略... */
 
    get_for_item: function(item, category, cb) {
        var url = this.base +'recommend?set='+ this.set
            +'&op=for_item&item_id='+ item +"&category_id="+ category;
        this._send_req(url, cb);
    }
};

そして「get」ボタンが押されたときにこのメソッドを呼び出します。第3引数として渡すコールバック関数は、正常にレスポンスが受信できたときに受信データを引数として呼び出されます。Cicindela からのレスポンスは改行区切りの文字列データです。ここでは単に出力用の要素に html メソッドで書き込んでおきます。

  // $(document).ready において
  $('#get').click(
      function(){
          var item_id = $('#items').val();
          $.each(data, function(category_name, category_data) {
                     Cicindela.get_for_item(item_id,
                                            category_data.id,
                                            function(data) {
                                                $("#output-"+ category_name).html(data)
                                            });
                 });
      });

以上で一通りの機能が完成しました。ユーザ名と選択アイテムを変えて何度か入力を行い、レコメンデーションを取得してみましょう。その際には自分なりに”シナリオ”を決めて、そのシナリオに沿った結果になるか試してみると面白いです。例えば:「Perl好きはEmacsやVimを使う」、「WindowsユーザにもMacユーザにもLinuxユーザにもJava好きはいる」、「EmacsとVimを両方ともよく使うユーザは少ない」などなど。

完全なソースを gist に置いておきます。

Cicindelaを使ったAjaxアプリケーションサンプル

ライブドア製レコメンデーション・エンジン Cicindela の動作を確かめるために、簡単なAjaxアプリケーションを作ってみました。スクリーンショット:

Cicindela Ajaxアプリ

「好きなプログラミング言語」と「よく使うソフトウェア」についての情報を収集し、その結果に基づいておすすめの言語・ソフトを算出する、一種のアンケートです。

現実にはAjaxアプリケーションから直接 Cicindela のWeb APIを叩くのは難しいと思いますが(いたずらされ放題になってしまうので)、機能をテストするのには手軽です。以下に作り方を紹介しておきます。

  • Cicindela はセットアップ済みとします(CentOS 5にセットアップする手順はこちら
  • カテゴリー機能を使います
  • アイテムid・ユーザidは文字列です
  • jQuery 1.3.2 を使います
  • Firefox 3で動作確認します(ほかのブラウザでも動くと思いますが)

データベースの初期化

create_init_sql.pl を実行して使用するデータベースを作成します。DB名は sample としました。

perl create_init_sql.pl --db_name=sample | mysql -uroot

cicindelaの設定

lib/Cicindela/Config/_common.pm に集計セットを定義します。

    'sample' => {
        datasource =>  [ 'dbi:mysql:sample;host=localhost', 'cicindela', 'cicindela' ],
        filters => [
            'PicksExtractor',
            'InverseUserFrequency',
            'ItemSimilarities',
        ],
        recommender => 'ItemSimilarities::LimitCategory',
        refresh_interval => 1,
 
        use_user_char_id => 1,
        use_item_char_id => 1,
        discard_user_id_char2int => '6 month',
        },

カテゴリ機能を使いたいので、フィルタとして ItemSimilarities を、レコメンダとして ItemSimilarities::LimitCategory を指定します。またユーザidとアイテムidに文字列を使用したいので、 use_*_char_id を設定します。結果を確認しやすくするために refresh_interval を 1 にしていますが、実運用時にはもっと大きな値を指定してください。

HTML

HTMLのbody内を抜粋すると次のようになります。SELECT 要素は後からJavaScriptで初期化するので、この段階では空っぽです。

<h2>追加</h2>
<div class="insert">
<p>username: <input type="text" id="username" /></p>
 
<fieldset><legend>好きな言語</legend>
<select multiple="multiple" id="langs"></select>
</fieldset>
 
<fieldset><legend>よく使うソフト</legend>
<select multiple="multiple" id="softs"></select>
</fieldset>
<p class="submit-space"><button id="insert">insert</button></p>
</div>
 
<h2>取得</h2>
<div class="recommend">
<p><select id="items"></select></p>
 
<fieldset><legend>おすすめ言語</legend>
<div id="output-langs"></div>
</fieldset>
 
<fieldset><legend>おすすめソフト</legend>
<div id="output-softs"></div>
</fieldset>
 
<p class="submit-space"><button id="get">get</button></p>
</div>
 
<h2>ログ</h2>
<textarea id="log"></textarea>

JavaScript: データの定義

使用するデータは JavaScript のオブジェクトとして、次のような構造で定義します。langs と softs という2つのカテゴリがあり、それぞれに数値のid(1,2)が割り当てられています。

var data = {
    langs: { id: 1,
             list: ['Perl', 'PHP', 'Ruby', 'Python', 'Java',
                    'JavaScript', 'CSharp', 'Erlang', 'CommonLisp'] },
    softs: { id: 2,
             list: ['Windows', 'Linux', 'Mac', 'Emacs', 'Vim',
                    'Eclipse', 'NetBeans', 'VisualStudio', 'Dreamweaver'] }
};

JavaScript: SELECTの初期化

document の ready イベントにてSELECT要素を初期化します。Cicindela に渡す item_id としてアイテムの名前をそのまま使うので、OPTION要素の value 値にはアイテム名を設定しておきます。

$(function() {
      $.each(data, function(category_name, category_data) {
                 var all_items = $('#items');
                 var category_items = $("#"+ category_name);
                 $.each(category_data.list,
                        function(i,l) {
                            $('<option />').attr('value', l).text(l)
                                .clone().appendTo(category_items)
                                .clone().appendTo(all_items);
                        });
             });
  });

続く。

動的にSELECTを構築するためのjQueryプラグイン

全てのブラウザで安定してSELECT要素を操作するためのプラグイン……というか、実質IE対策プラグインです。

gits: 256077動作テスト

ごく小さなプラグインで、余計な機能は一切ついていません。使用できるメソッドは clearOptions と addOption の2つです。当たり前ですが、SELECT要素以外に使っても意味はありません。

clearOptions()
全てのOPTION要素を削除します
addOption(text, value, selected)
新しいOPTION要素を追加します。引数はそれぞれ表示されるテキスト、値、選択状態です

こんな感じで使います。

//start月からend月までの選択肢を作る
function updateMonth(start, end) {
  var select  = $('#month');
  var current = select.val();
 
  select.clearOptions();
  for(var i=start; i<=end; ++i) {
      select.addOption(i + "月", i, i == current)
  }
}

全てのブラウザで(というかIEで)安定した結果を得るために、次の点に注意すると良いでしょう:

  • 構築直後に val メソッドによる選択は行わない。代わりに addOption メソッドの第3引数でデフォルトの選択状態を設定する
  • 構築処理全体を setTimeout で遅延実行する

検索で来る方のために、このプラグインを作るまでにIEで遭遇した問題も書いておきます:

  • 構築後、SELECTの横幅がおかしくなる。構築を繰り返すたびにだんだんと短くなり、最後には消滅する
  • 構築に時間がかかる。そのために処理の途中でSELECTの表示がチラつく
  • 構築直後に val メソッドで選択状態を変更すると「selected プロパティを設定できませんでした。未定義のエラーです。」となる(val メソッドを使わず、addOption メソッドの第3引数を使うことで回避可能)
  • ページ読み込み直後にSELECTを構築すると、選択がレンダリング結果に反映されない(構築処理全体を setTimeout で遅延実行することで回避可能)

コード全体:

jQueryでPrototype.js風のクラスを使う

Prototype.js 風の Class を書きたいが、ライブラリ全体は読み込みたくない、というときのために、必要最低限のコードを抜き出してみた。要jQuery。

var Class = (function() {
        function subclass() {}
        return {
        create: function(parent) {
                function klass() {
                    this.initialize.apply(this, arguments);
                }
 
                var index = 0;
                if(jQuery.isFunction(parent)) {
                    index = 1;
                    subclass.prototype = parent.prototype;
                    klass.prototype = new subclass;
                }
                for(; index < arguments.length; ++index) {
                    jQuery.extend(klass.prototype, arguments[index]);
                }
                return klass;
            }
        }
    })();

最大の違いは $super による親クラスのメソッド呼び出しができないこと。Prototype.jsのチュートリアルの例は次のようになる。

var Person = Class.create({
  initialize: function(name) {
    this.name = name;
  },
  say: function(message) {
    return this.name + ': ' + message;
  }
});
 
var Pirate = Class.create(Person, {
  say: function(message) {
    //親クラスのprototypeに直接アクセス
    return Person.prototype.say.call(this, message) + ', yarr!';
  }
});
 
var john = new Pirate('Long John');
john.say('ahoy matey');
// -> "Long John: ahoy matey, yarr!"

mix-in の例などはそのまま動く。