From a Far East Island

2011-06-02

[][][] Firefox のみ Rails RJS の Sortableでエラー

sortable_element の引数に:scroll => true を渡すと、Firefoxで以下のエラーが出る。

[Exception... "Could not convert JavaScript argument arg 0 [nsIDOM3Node.compareDocumentPosition]" nsresult: "0x80570009 (NS_ERROR_XPC_BAD_CONVERT_JS)" location: "JS frame :: http://192.168.0.50:3000/javascripts/prototype.js?1300219131 :: <TOP_LEVEL> :: line 1882" data: no]

:scroll => true を削除することで解決した。

Firefox 3.6.7, Firefox 4.0.1 で確認。 Chrome, IE, Safari では問題なし。

2011-02-08

[]IE の Range オブジェクトについての質問です。

テキストボックスからフォーカスが外れた際のイベント(jQueryの'focusout'イベントを使ってます)で、選択されていた部分の文字列を取得するという処理をしたいのですが、どうしてもうまく行きません。

以下のJavaScriptソースコード中、try区の2行目でエラーが発生し、「引数が無効です」と言われてしまうのですが、どうしても原因が特定できず困っています。

どなたか、原因のわかる方のお知恵をお借りできたらと思います。

よろしくお願いします。

<html>
  <haed>
    <script src="/javascripts/jquery-1.4.2.min.js" type="text/javascript"></script>
    <script src="/javascripts/test.js" type="text/javascript"></script>
  </haed>
  <body>
    <input id="my-input" type="text" name="foo" value="test" />
  </body>
</html>
jQuery(document).ready(function($){
  var test1 = function(e){
    var range = document.selection.createRange();
    var range2 = document.body.createTextRange();
    var res = {};

    try{
      range2.moveToElementText(this);
      range2.setEndPoint('EndToEnd', range);
    } catch(e) {
      alert(e.message);
    }

    res.start = range2.text.length - range.text.length;
    res.end = res.start + range.text.length;
    res.text = range.text.substring(res.start, res.end);
    
    alert(range2.text);
  };
			 
  $("#my-input")
    .bind("focusout", test1);
});

[追記 : 2月8日] 解決しました。

[ JavaScript ] IE の Range オブジェクトについての質問です。 … - 人力検索はてな

返信してくれたCherenkov さん、windofjulyさんありがとうございました。

2011-01-26

[][]Rails開発のJavaScriptでのクロージャの使いどころ

今までサーバからブラウザサイドのJavaScriptに値を渡すときに、いちいちHTML要素に余分な属性を付加してそこに値を格納し、JavaScript側でそれを取り出すということをしてきた。例えばこんな感じ。

<div id="1" class="content" title="foo" owner="John" active="true">...</div>
<div id="2" class="content" title="bar" owner="Pat" active="false">...</div>
<div id="3" class="content" title="buzz" owner="Chris" active="true">...</div>

これらdiv要素の属性、title, owner, active が値を渡すために付加した属性だ。しかしページのあちこちに値を関連付けた要素が増えてくると、ページ中に値をそれらが分散して、どの要素にどの値が関連付けれれているかを知っている必要が出てくる。そもそも、いちいちjQueryなどを使って値を取り出すのも面倒だ。

具体例として、例えば、jQueryUIのダイアログを使って、これらdiv要素をクリックしたときに、ダイアログを表示して、これらの要素を更新したいなんて場合を考えてみよう。だいたい以下のような手順になるだろう。

  1. JavaScriptでこれらの値を属性から取り出す。
  2. ダイアログに値を渡してUI(title, owner, active)を初期化する。
  3. OKボタンを押したときの処理で、これら属性を更新する。

コードにするとこんな感じか。

var content = {
  initialize: function(id){
     // 1. ダイアログに渡すための値を要素から取り出す。
     var init_content = function(e){
	 $("#dialog").dialog({
	 // 各種処理
	 // 2. UIの初期化
	 // 3. OKボタンを押したときの要素の更新処理など
	 });
     };
     
    $(id).bind("mouseup", init_content);
  }
};

そして、サーバ側では、各div.content要素を生成する度にcontent.initializeを呼ぶ。

page << "content.initialize(#{content.id})"

しかし、この1と3の手順が結構面倒なのである。何とか成らないかと考えていたところ良い方法を思いついた。

以下のようにする。

JavaScript側:

var content = {
  initialize: function(id, title, owner, active){
     var init_content = function(e){
	 $("#dialog").dialog({
	 // 各種処理
	 // UIの初期化
	 });
     };
     
    $(id).bind("mouseup", init_content);
  }
};

サーバ側:

page << "content.initialize(#{content.id}, #{content.title}, #{content.owner}, #{content.active})"

content.initialize の引数に title, owner, active を渡すのだ。そうすることで、UIを初期化する際にこれらの値が使えるようになる。

ただし、これだけでは上手くいかない。

content.initialize が呼ばれるのは、要素の初期化時のみ。title, owner, active は、要素を初期化した時点の値なので、ダイアログを表示して値を更新し、もう一度ダイアログを表示したときに前の値が表示されてしまう。

値を何処かに保存しておかなければならないのだ。

そうすると、やっぱり要素の属性として値を保存しておき、必要になったときに取り出すという操作が必要になるのか?

と思ったのだが、よくよくコードを見てみると、content.initialize の引数、id, title, owner, active は、関数 init_content() のクロージャ変数になっているのだ。

だから、OKボタンを押したときの処理で、これらの変数に値を書き戻してやると、次回ダイアログを表示した際に新しい値でUIが初期化されるようになる。

var content = {
  initialize: function(id, title, owner, active){
     var init_content = function(e){
	 $("#dialog").dialog({
	 // 各種処理
	 // UIの初期化
	 // OKボタンを押したときの処理
	 title = new_title
	 owner = new_owner
	 active = new_active
	 });
     };
     
    $(id).bind("mouseup", init_content);
  }
};

これで、要素の属性に値を取り出したり格納したりする作業から開放される。サーバ側でも、要素を生成するときに余分な属性を付加しなくて良くなる。

大分コードがシンプルになるのではないだろうか。

とりあえず、今日思いついた自分なりのベストプラクティスなのだが、もっと良い方法や一般的に知られている簡単な方法などがあったら教えていただきたい。

2010-10-08

[]jQuery.data について

以下の記事で勉強。

jQuery.dataの使い方 - KAYAC engineers' blog

以下のようにjQueryメソッドと追加しておくと、

$.fn.setData = function(key, value){
  this.each(function(){
    var obj = $.data(this, "name_space") || {};
    obj[key] = value;
    $.data(this, "name_space", obj);
  });
  
  return this;
};

$.fn.getData = function(key){
  var result = [];
  
  this.each(function(){
    result.push($.data(this, "name_space")[key]);
  });
  
  return result;
};
$(element).setData(key, value)
$(element).getData(key)

とアクセス出来るようになるので、なお便利かも。

2010-03-23

[][]Cookieのhttponly属性を外す方法

Railsでセッションを作成するとブラウザにCookieが送信される。このCookieが普通にcookiesハッシュで作成したCookieと違うのは、Cookieのhttponly属性がonになって送信されることだ。

これがonのCookieは、ブラウザ上のjavascriptで、

document.cookie

としても値を取得できない。

クライアント側でセッションのCookieを意図的にハックしたい場合、以下の方法でhttponly属性をoffにすることができる。

ActionController::Base.session_options[:httponly] = false

RailsによるアジャイルWebアプリケーション開発 第2版」によると、

セッションの場合、オプションはグローバルなので、環境ファイル(config/environment.rbまたはconfig/environments内のファイルのひとつ)で設定できます。

とあるので、環境設定ファイルconfig/initializers/session_store.rbで設定を行うことで、これを実現することができた。