さとやn Blog

試合はRuby,勝負はC#!

jQuery

jQuery: live関数でまだ存在しない要素に対してイベントを設定する

Ajaxを使用していると、動的に追加されたコンテンツにボタンとかリンクとかがあって、そいつらにイベントハンドラを設定したい場合が多々あると思います。
私はこれまでは、Ajaxで取得したコンテンツをページに追加した後に、イベントハンドラを設定するコードを書いていたのですが、これって結構コードが美しくなかったりします。
でも、まあ、仕方ないよなー、とか思っていたのですが,jQueryのliveメソッドを使用すると、簡単に解決できてしまうことを発見。
ドキュメントを読むと、
	Attach a handler to the event for all elements which match the current selector, now and in the future.
現在及び将来において存在する要素に対してイベントハンドラを設定することができる、とのこと。
これは素晴らしい、と言うことで早速やってみる。
「追加」をクリックすると、Ajaxで部分コンテンツを取得して、ページに追加して、「削除」をクリックするとその要素が消える、といった感じのページを作ってみる。
31)

live関数を使用しない場合は、こんな感じで、部分コンテンツ取得後に「削除」リンクのイベントを設定するコードを書いてました。
	$(function(){
		
		//「追加」リンクをクリックで 簡単なDIV要素をAjaxで取得してドキュメントに追加するだけ
		$("#addLink").click(function(){
			$.get("./get_partial_content", function(html){
				$(document.body).append($(html));
				
				//削除リンクのイベント設定
				$(".deleteLink").click(function(){
					$(this).parent().remove();
				})
]			});
		});
		
	})
でも、この書き方だと、要素を追加するたびに、既存の削除リンクに対してもイベントハンドラを追加し続けてしまいます。この例では要素が削除されてしまうので、眼に見える害はありませんが、もしアラートを表示するような内容にした場合、何度も同じアラートが表示されるような削除リンクができあがってしまいます。
できればページ初期化時にイベント関連はまとめて設定しておきたいところです。
で、それを可能にしてくれるのがlive関数、というわけで、コードを次のように書き換えてみる。

	$(function(){
		
		//「追加」リンクをクリックで 簡単なDIV要素をAjaxで取得してドキュメントに追加するだけ
		$("#addLink").click(function(){
			$.get("./get_partial_content", function(html){
				$(document.body).append($(html));
			});
		});
		
		//上記のイベントで動的に追加されたDIV要素の「削除」リンクをクリックすると、自分自身(と親DIV)をドキュメントから削除する
		$(".deleteLink").live("click", function(){
			$(this).parent().remove();
		});
	})
	
こうすると、動的に追加されたコンテンツ内の class="deleteLink" の要素に対してもイベントハンドラが設定されます。
素晴らしい!

jQueryUIで超簡単にAutoCompleteを試してみる

久しぶりにjQueryUIのサイトを見てみたら、新機能としてAutoCompleteなるものが追加されていました。丁度仕事でも使いそうなので、やってみることに。
新規にRails3でプロジェクトを作成したばかりなのでとりあえず、mainという名前でデフォルトのコントローラを作る。
  #rails g controller main
  create  app/controllers/main_controller.rb
  invoke  erb
  create    app/views/main
  invoke  test_unit
  create    test/functional/main_controller_test.rb
  invoke  helper
  create    app/helpers/main_helper.rb
  invoke    test_unit
  create      test/unit/helpers/main_helper_test.rb
とりあえずサーバーを起動して main コントローラにアクセスしてみると、そんなページないよーってな感じでRouting Errorになる。 今までは何の問題もなくいけてたんだけどなー、、、Rals3からはいちいちルーティングファイルに書き込む必要があるのだろうか? 調べないとよくわからないので、とりあえず routes.rbをデフォルトのコントローラとアクションが main/index になるように変更。
  # You can have the root of your site routed with "root"
  # just remember to delete public/index.html.
  # root :to => "welcome#index"
  root :to => "main#index"
でapp/views/main/index.html.erb には、jqueryUIのautocompleteのドキュメントに従ってこんな感じでサンプルを書いてみる。 とりあえずデータはJavaScript直書きで持たせてみる。
	<input type="text" id="textbox1"></input>
	<script type="text/javascript" charset="utf-8">
		$(function(){
			var dataItems = ["C#", "C++", "C", "Java", "JavaScript", "Ruby", "IronRuby", "JRuby"];

			$("#textbox1").autocomplete({
				source : dataItems
			});
		})
	</script>
ページを表示してみる。

45)

かなり簡単にできてしまいます。 でも、実際にはサーバーと通信させる場合のほうが多いと思います。 その場合は source オプションに JSONデータを返すURLを指定してあげて、サーバー側では、 文字列配列か、もしくは { label : '表示項目', value : 'Value値'}の配列を返すように実装してあげれば良いようです。
ユーザーが入力した文字は term というパラメータ名でサーバーに渡るので、そいつをキーワードとしてフィルタリングするような感じになるかと思われます。
DBをいちいち用意するのは面倒だったので、あらかじめ配列で持たせたプログラミング言語名称一覧からユーザーが入力した文字に該当するものをJSONで返すような処理を書いてみました。
	class MainController < ApplicationController
	  def index
			#なんかまたルーティングの設定なのか、上手く行かのいので indexアクションの中でajaxリクエストかどかで処理を分けてしまった、、、
	    if request.xhr?
	      data_items = %w(C# C++ C Java JavaScript Ruby IronRuby JRuby)
	      return render :json => data_items.find_all{|lang| lang.include?(params[:term]) }
	    end
	  end
	end
JavaScriptの方は sourceオプションをURLに変更するだけ。
	<input type="text" id="textbox1"></input>
	<script type="text/javascript" charset="utf-8">
		$(function(){
			//var dataItems = ["C#", "C++", "C", "Java", "JavaScript", "Ruby", "IronRuby", "JRuby"];
			$("#textbox1").autocomplete({
				//source : dataItems
				source : "/"
			});
		})
	</script>
う〜ん、なんて簡単なんだ、、、、。

31)

Rails3でjQueryを規定のクライアントサイドのスクリプトとして使う

仕事ではまだRails3はやったことないのですが、そんな事言っているといつまでたっても触る機会もなさそうなので、ちょっとしたJavaScriptなんかを試したい時用のプロジェクトをRails3で一個作っておこうと思いやってみました。
とりあえず、Rails3をインストール。
gem install rails
ちょこっとしたJavaScriptとかを試したい時用のプロジェクトなので、 js_spikeって名前でプロジェクトを作ってみる。
rails new js_spike
public/javascripts下を覗いてみるとデフォルトでは例によってPrototypeになっているようです。
-rw-rw-r-- 1 satoyan satoyan    148 Feb 28 15:52 application.js
-rw-rw-r-- 1 satoyan satoyan  34787 Feb 28 15:52 controls.js
-rw-rw-r-- 1 satoyan satoyan  31056 Feb 28 15:52 dragdrop.js
-rw-rw-r-- 1 satoyan satoyan  38467 Feb 28 15:52 effects.js
-rw-rw-r-- 1 satoyan satoyan 162353 Feb 28 15:52 prototype.js
-rw-rw-r-- 1 satoyan satoyan   6278 Feb 28 15:52 rails.js
jquery-railsというgemを入れるとクライアントサイドのスクリプトをすべてjQueryのものに置き換えてくれるようなのでそれをインストールしてみる。 Gemfileに 下の一行を追加。
	gem 'jquery-rails'
bunldle install を実行
	#bundle install を実行
	
	Fetching source index for http://rubygems.org/
	Using rake (0.8.7) 
	Using abstract (1.0.0) 
	Using activesupport (3.0.5) 
	Using builder (2.1.2) 
	Using i18n (0.5.0) 
	Using activemodel (3.0.5) 
	Using erubis (2.6.6) 
	Using rack (1.2.1) 
	Using rack-mount (0.6.13) 
	Using rack-test (0.5.7) 
	Using tzinfo (0.3.24) 
	Using actionpack (3.0.5) 
	Using mime-types (1.16) 
	Using polyglot (0.3.1) 
	Using treetop (1.4.9) 
	Using mail (2.2.15) 
	Using actionmailer (3.0.5) 
	Using arel (2.0.9) 
	Using activerecord (3.0.5) 
	Using activeresource (3.0.5) 
	Using bundler (1.0.10) 
	Using thor (0.14.6) 
	Using railties (3.0.5) 
	Using rails (3.0.5) 
	Installing jquery-rails (0.2.7)  #インストールされました...
	Using sqlite3 (1.3.3) 
"rails g" でgenerator一覧を見てみると新たにjQueryの欄ができています。
	#rails g でgeneratorのヘルプを見てみる
	
	Usage: rails generate GENERATOR [args] [options]

	General options:
	  -h, [--help]     # Print generator's options and usage
	  -p, [--pretend]  # Run but do not make any changes
	  -f, [--force]    # Overwrite files that already exist
	  -s, [--skip]     # Skip files that already exist
	  -q, [--quiet]    # Suppress status output

	Please choose a generator below.

	Rails:
	  controller
	  generator
	  helper
	  integration_test
	  mailer
	  migration
	  model
	  observer
	  performance_test
	  plugin
	  resource
	  scaffold
	  scaffold_controller
	  session_migration
	  stylesheets

	Jquery:
	  jquery:install
	
使い方を jquery:install で表示してみる。
	#rails g jquery:install --help
	
	Usage:
	  rails generate jquery:install [options]

	Options:
	  [--version=VERSION]  # Which version of jQuery to fetch
	                       # Default: 1.5
	  [--ui]               # Include jQueryUI

	Runtime options:
	  -s, [--skip]     # Skip files that already exist
	  -p, [--pretend]  # Run but do not make any changes
	  -q, [--quiet]    # Supress status output
	  -f, [--force]    # Overwrite files that already exist

	This generator downloads and installs jQuery, jQuery-ujs HEAD, and (optionally) the newest jQuery UI
	
バージョンの指定とjQueruyUIを使うかどうかの指定ができるらしいので、さっそく実行してみる。
	#rails g jquery:install --ui --force
	
    remove  public/javascripts/controls.js
    remove  public/javascripts/dragdrop.js
    remove  public/javascripts/effects.js
    remove  public/javascripts/prototype.js
  fetching  jQuery (1.5)
    create  public/javascripts/jquery.js
    create  public/javascripts/jquery.min.js
  fetching  jQuery UI (latest 1.x release)
    create  public/javascripts/jquery-ui.js
    create  public/javascripts/jquery-ui.min.js
  fetching  jQuery UJS adapter (github HEAD)
     force  public/javascripts/rails.js
	
public/javascripts下を覗いてみると、jquery関連のファイルが生成されていました。
  # ll public/javascripts
	-rw-rw-r-- 1 satoyan satoyan    148 Feb 28 16:25 application.js
	-rw-rw-r-- 1 satoyan satoyan 216840 Feb 28 16:40 jquery.js
	-rw-rw-r-- 1 satoyan satoyan  85260 Feb 28 16:40 jquery.min.js
	-rw-rw-r-- 1 satoyan satoyan 367486 Feb 28 16:40 jquery-ui.js
	-rw-rw-r-- 1 satoyan satoyan 198792 Feb 28 16:40 jquery-ui.min.js
	-rw-rw-r-- 1 satoyan satoyan   5033 Feb 28 16:40 rails.js
でもpublic/stylesheets下にはjQueryUI用のCSSはできてないようです。 あれもなきゃだめじゃん、と言うことでCSSはGoogleCDNを利用することにします。 とりえあず redmondというテーマのものを使うようにapplicataion.html.erbに書きこんでおく。
	  <%= stylesheet_link_tag :all %>
	  <%= javascript_include_tag :defaults %>
	  <%= csrf_meta_tag %>
		<%= stylesheet_link_tag "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/redmond/jquery-ui.css" %>
とりあえず、こんな感じでjQueryの設定はできました。CSSもやってくれないと不便なような気がするんだけどなー、、、、。
使い方知らないだけ?

Jrailsと同時にActiveScaffoldも使用する場合

RailsでPrototype.jsの代わりにjqueryを使うためのプラグインとしてJrailsというものがあり、非常に便利なのですが、ActiveScaffoldというこれまた非常に便利なプラグインを同時に使用しようとすると、ActiveScaffoldを適用したページ表示の際に、Prototype.jsが読み込まれてないですよー、といった感じで怒られてしまいます。
※ActiveScaffoldはScaffoldのAjax版みたいなものです。詳しくは
http://activescaffold.com/

実際Jrailsを使ってるので
<%= javascript_include_tag :defaults %>


ってやってもPrototype.jsではなくjQueryインクルードのタグが生成されます。

で、どうやるのかな? と調べたところ、
ちゃんとActiveScaffoldのWikiにありました。

Jrailsを適用したくない、というか、Prototype優先にしたいコントローラで、
  ActionView::Helpers::PrototypeHelper::JQUERY_VAR = 'jQuery'

のようにして、JavaScriptの$をjQueryで上書きさせない様にしてやります。


実際にJrailsのソースを見ると、

unless const_defined? :JQUERY_VAR
  JQUERY_VAR = '$'
end

といった記述があります。

ですので、別にjQueryという文字列でなくても良いわけで、jQueryのサイトにもあるように
var $j = jQuery.noConflict();
$j とかでも問題なし。


私の場合、ActiveScaffoldはAdmin系のページでしか使わないので、Admin系専用の基底コントローラクラスを作って、他のAdmin系コントローラはそいつを継承させるようにしてるため、こんな感じで対応してあげればOKでした。
class Admin::AdminControllerBase < ApplicationController
  ActionView::Helpers::PrototypeHelper::JQUERY_VAR = 'jQuery'
  layout 'admin'
  before_filter :block_none_admin
  ActiveScaffold.set_defaults do |config|
    config.ignore_columns.add [:created_at, :updated_at]
  end
end

adminというlayoutを使っているので、そいつの中もこんな感じに jQuery.noConflict してやってPrototypeを読み込んであげればOKです。
effects スクリプトも指定するのも忘れないようにましょう。

<%= javascript_include_tag :defaults %>
		
<%= javascript_include_tag "prototype", "effects"  %> 
<%= active_scaffold_includes %>

あ、でもページングでエラーになる、、。 もうちょっと調べないと駄目だ、、、。

jQueryにいつの間にproxyなんて、、、でも不可解なオーバーロード定義が、、

ちょっと暇だったので、jQueryのドキュメントを眺めていたら、
$.proxy 
ってなんだ?  と思い見てみたら、イベントハンドラのコンテクストを指定できるようになったんだ。
以前からなんでないのかー?と思っていたのですが。
最近はRailsでも、ほとんどJavaScriptはjQueryのお世話になっているので、試しにPrototypeとどっちが短くなるか書いてみました。

    1 //prototype
    2 Event.observe("button1", "click", this.onButton1Clicked.bindAsEventListener(this));
    3 $("button1").observe("click", this.onButton1Clicked.bindAsEventListener(this));
    4 
    5 //jQuery
    6 $("#button1").click($.proxy(this.onButton1Clicked, this));
    7 $("#button1").click($.proxy(this, "onButton1Clicked"));
    8 


やっぱりjQueryの方が短くて済みますね。
パラメター他の渡し方は二通りあるのですが、コンテクスト指定の順番が逆になっている、、、。ドキュメントのユーザーコメントにもありましたが、これはやめてほしいです。なんか理由が有るのかな。ていうか、後者の方って不要な気がする、、、。 

ドキュメントはこちら

RailsでjQueryをGoogleCDNでインクルード

また新規にRailsプロジェクトを作成しているのですが、最初の段階で毎回jQueryをダウンロードして解凍して配置、するのは今後も面倒だな、ということでGoogleが何かそういうの提供していたのを思い出して、使ってみることにしました。
実は最初にMicrosoftの同様のサービスを調べてみたのですが、やる気なさそうなので止めました。(^_^;)

アドレスを静的に指定する方法と、GoogleAjaxAPIローダーを使って動的に読み込む方法があるようです。
直接指定した方が簡単ですが、APIを使った方が柔軟な指定が可能、ということでそっちでやってみました。
※APIを使用するにはキーが必要とのことでした。


<script type="text/javascript"
src="http://www.google.com/jsapi?key=APIのキー">
</script>


取り敢えずGoogleAjaxAPIローダーをインクルードして、

google.load("jquery", "1.3");

で、jQeuryの1.3系の最新版をインクルードするようにしてみました。
1.3.xのように詳細なバージョンを指定することも可能で、省略すると勝手に最新のものを持ってきてくれるとのことです。

で、RailsではPrototypeも使うので、jQueryの競合を避けるために、すぐ下に
jQuery.noConflict();
とやったみたら、jQueryなんてないよ、と怒られる。
静的にリンクした訳じゃないので、下に書いたからって駄目なようです。

google.setOnLoadCallback(function(){
jQuery.noConflict();
});


としてやって、ページロード後にやってあげることにしました。
何かドキュメント読むと、loadメソッドのオプションに callbackというのがあって、スクリプトロード後に呼び出される、みたいなことが書いてあったんだけど、何か上手くいきませんでした。

あとは同じように
google.load("jqueryui", "1.7");
で、UIも使えるようしておきました。

jQuery







仕事でいろいろJavaScriptでのUI効果を取り入るべく、jQueryを導入したのですが、どうもjQueryに詳しいと思われているようです。
自分から使うのは実は初めてで、それまではWebデザインチームの若手デザイナが使っていて、うまくいかない時などに質問に答えるためにちょっと調べたりするくらいでした。
しかし今やjQueryはあの何でも自社製にしたがるMicrosoftが標準で採用するくらい勢いのあるフレームワークに成長しています。
2年以上前でしょうか、jQueryなるものを教えてくれた某有名サイトの若手デザイナさんには感謝しています。あのときに一緒にやってなければまだ「jQueryか〜、名前は知っているけど、、、」てな感じだったでしょう。
やっぱ時代はjQueryでしょうか、、、、。やっと私も好きになりました。


激論: Prototype vs. jQuery。。。。

激論: Prototype vs. jQueryという記事があったので読んでみました。

どちらかといえば、Prototypeの方がいい、みたいな感じになっているのですが、わたしも同感だったりします。

jQueryの良さは理屈ではわかるのですが、実際のコード書いていると、なんかやっぱりPrototypeの方がしっくりくるのです。
jQueryの第一印象は、「これはなんかプログラマ向けではなくデザイナ向けって感じだなぁ」てな感じでしたので、その辺が理由かもしれませんが、、、、。
そういえばDojoって全く触れたことないんだった、、。
ExtJSは結構さわったけど、なんか、面倒くさくて、またあれでやろうって気にはなりませんでした。あれだったらGWTのほうが全然いいです。ていうかGWTは好きです。

jQuery

先日、デザインチームのメンバーに質問されたのですが、jQueryを使っていて、イベントハンドラ(ボタンがクリックされた時とかに呼ばれる関数のこと)の中で、thisを参照すると、なんかおかしい、ということでした。
結構これではまる人がいるのではないかと思い、ちょっと書いてみようと思いました。
JavaScriptではprototypeでクラスのようなものを定義できますが(別にprototypeを使わなくてもできますが、、)、他のオブジェクト指向言語(C#、Javat等)になれている人は、ついその中で定義されている関数内ではthisの参照は常に自分自身のクラスのインスタンスだと思ってしまいます。
が、JavaScriptではそうはいかず、以下のようなクラス(と呼ばせてください)を定義した場合、

function MyClass(){
? //button1のクリックイベントハンドラを設定
? $('#button1').bind('click', this.onButtonClicked);
}

MyClass.prototype = {
? //button1をクリックするとこの関数が呼ばれます
? onButtonClicked : function(e){
??? this.sayHello();
? },

? sayHello : function(){
??? alert('hello world!!');
? }
}

一見、button1をクリックすれば 'hello world!!' 当メッセージが表示されるかと思いますが、実際にはundefined エラーになってしまいます。
これは、onButtonClicked関数内で this が参照するのは、 MyClassのインスタンスではなく、button1そのものだからです。これはJavaScriptの仕組み上変えられません。
では、onButton1Clickedの中でMyClassのインスタンスを参照するにはどうすればいいのかというと、イベントを設定する時に、自分自身(this)への参照をイベントハンドラに渡してやる必要があります。
jQueryではイベントを設定する関数 bind の第2パラメータで任意の情報を設定することができるので、そこで設定してやります。

? $('#button1').bind('click', {"self" : this},? this.onButtonClicked);

のように、第2パラメータでJSON形式で自由にデータを設定できます。ここでは self という名前で thisへの参照を設定しています。
で、これをイベントハンドラの中で参照する場合は、

? onButtonClicked : function(e){
??? e.data.self.sayHello();
? }

って感じで、イベント引数の data プロパティ経由で参照できます。
ここで、e.data.self というのは、先ほど指定した、 "self" : this のことなので、つまりMyClassのインスタンスを指しています。
多分jQueryではこんな感じで参照を保持するしか方法がないかと思われます。※間違っていたらおしえてください。
これは、対抗馬のPrototypeフレームワークでは、イベントを設定する際に、
bindAsEventListener(this)
とすることで、素直にイベントハンドラ内で this を参照してもちゃんと期待通りに MyClassのインスタンスにアクセスできます。
何故この辺をjQueryがサポートしないのか、ちょっと不思議なのですが、、、、。
私も本当はjQueryを使いたいのですが、なんか、この辺が引っかかって、まだPrototypeの方を使っています。
これは、単に私のjQueryの理解が足りないせいかもしれませんので、誰か、詳しい方がいれば、是非教えてください。




livedoor プロフィール
QRコード
QRコード
  • ライブドアブログ