カテゴリー別アーカイブ: jQuery

jQuery.validationプラグインでとても複雑なバリデーションを行う

jQuery.validationプラグインのハックテクニックをご紹介します。

http://jqueryvalidation.org/

jQuery.validationプラグインはフォームのバリデーションを簡単に行う事ができ、プロジェクトでとても重宝しているプラグインの1つです。

ただし、複雑なバリデーションを行おうとするとなかなか難しいです。

1つのインプットデータ、あるいは複数のチェックボックスなどをひとまとめにしたものに対してバリデーションを行う場合はプラグインの守備範囲なのですが、以下のような場合に急に難しくなります。

1:1つのフォームが他のフォームの内容に複雑に関係するバリデーション

例えば「3つあるラジオボックスの1つ目をチェックした時はアルファベットのみ、二つ目をチェックした時でかつ別のチェックボックスをチェックした時はひらがなのみ」など

2:フォームの内容ではなく画面の状態などをバリデーションしたい場合や
3:BackboneJSなどでデータを保持している場合

データ入力にリッチなUIを実装しようとするとフォームだけでは難しくなります。そこでデータをjsonなどで保持することになりますが、フォームの内容とjsonの内容の両方に対してバリデーションし、エラーの表示をjQuery.validationで統一しようとする場合、どうしてもbeforeValidationイベントが欲しくなりますが存在しません。http://jqueryvalidation.org/validate/

下記CoffeeScriptですが、まずaddMethodでメソッドを追加します。

beforeValidation ->
 #something code
$.validator.addMethod 'beforeValidation' (value, element, param) ->
  beforeValidation() #スコープに注意
  return true #常にtrueを返す

そしてダミーのinputタグを用意する

 <input name="beforeValidation" type="hidden" />

#readyのあとで
 options = {
   rules: {
     'beforeValidation': {#このルールを必ず初めに記述する
       beforeValidation: true
     }
   }
 }
 $form.validate(options)

jQuery.validationプラグインがフォームをvalidateする際に必ずbeforeValidationを初めに行ってくれるので、実質beforeValidationイベントがトリガーできます。

ここでjsonのデータをチェックして、ゴニョゴニョしてします。

例えばエラーを表示する為だけにinput[type=”hidden”]を用意し、エラーがみつかったら値を空に、エラーが無ければ”ok”とか適当な値を入れて、requiredをあらかじめrulesに入れておけばOKです。

jQuery 1.9.1:checkboxのcheck/uncheckはpropで

これまでjQueryを用いたcheckboxのcheck/uncheckは下記のようにしていました。

checkbox.attr(‘checked’, ‘checked’)

checkbox.removeAttr(‘checked’)

しかし、どういうわけかコレが効かなくなる場合がありました。

同様にハマッているひとがいまして、こちらの記事で無事に解決させていただきました。
http://stackoverflow.com/questions/14769408/jquery-checkbox-check-uncheck

checkbox.prop(‘checked’, true)

checkbox.prop(‘checked’, false)

確かにこちらの方がしっくり来ます。ちなみにcheckboxがcheckであるかどうかは

checkbox.attr(‘checked’) == ‘checked’ でなく

checkbox.is(‘:checked’) とするのが良いようです。

jQuery1.6あたりでそれまでattrがattrとpropに分かれたようです。
その辺の情報は下記のリンクまで。

http://myjquery.blog.fc2.com/blog-entry-14.html#modified-10

jquery UI jQuery UI – v1.8.23 Dialogの画面スクロールに固定させる

jquery UI の Dialogは基本的にDialogの箱を position: absolute にしている。

スクロールするとダイアログはそのまま置いていかれ、画面の外にいってしまう。

それが困るので、画面に固定させる方法を色々試してみました。

最も簡単な方法が position: fixedにする方法です。

[javascript]

box = dialog.parent() //dialogはdialog()メソッドを呼んだ後のjqueryオブジェクト
box.css({position: ‘fixed’})
[/javascript]

しかし、これだとリサイズした際にjquery UIがpositionをabsoluteに戻してしまうらしく、バグが起きました。
というわけでリサイズした後に再度position: fixedに戻してやることで対応しました。

[javascript]
function fix(){
box = dialog.parent()
if(box.css(‘position’) == ‘absolute’){
top = parseInt(box.css(‘top’)) – $(window).scrollTop()
left = parseInt(box.css(‘left’)) – $(window).scrollLeft()
box.css({position: ‘fixed’, top: top + ‘px’, left: left + ‘px’})
}
}
fix()
dialog.on(‘dialogresizestop’) = function(e){
fix()
}
[/javascript]

ポイントはposition: absoluteと position: fixedではleftおよびtopが
相対値と絶対値という違いがあるのでwindowのスクロールポジションから補正してあげることです。

この記事は jquery UI -v 1.8.23にて検証したものです。
それ以外のバージョンでは状況が違う可能性があります。

visualize.jqeury.jsはjquery ver.1.8で動かない(2012.9現在)

グラフを表示するjavascriptライブラリのvisualize.jqeuryを試してみました。
が、動かない。

エラーも無く、静かに枠だけが表示されます。

サンプルは表示されるのになぜ?と試行錯誤していると、
jqeuryのバージョンが1.8だと動かないようです。

どうやらjqueryのfilterメソッドに仕様変更があったようで、
visualize.jquery.js内でfilter(‘ ‘)とデフォルトで呼び出している箇所があります。
これがjquery 1.8だとnullを返します。

visualize.jquery.jsないの20行目あたりにそのデフォルト値を設定してい入る箇所がありますので、
下記のように変更します。

[javascript]var o = $.extend({

rowFilter: ‘*’, //’ ‘
colFilter: ‘*’, //’ ‘

},options);[/javascript]

ちなみこちらのサイトでfolkプロジェクトが進行しているようです。
オリジナルのほうはシンプルでとてもよいですが、
folkのほうはグラフごとにプラグインになっています。

プラグイン形式にしたほうが開発に広がりが生み出せますよね。

tableからグラフを作るというコンセプトは使用する側としてはわかりやすいです。
一方当然生データよりは余計なプログラムが動くことになります。

Ruby on Rails 3.2 Livestamp.js を利用してある日時からある日時までがどれだけあるのかを表示する

Rails でActiveRecord::Baseを継承したモデルのなかで普通に日付の属性にアクセスるるとその型はActiveSupport::TimeWithZoneになる。

今日から数えてその日時が何日後なのかあるいは何日前なのかといったことを表示するためにはActionViewのdistance_of_time_in_words_to_nowを利用すれば実現できます。

ローカルの時間を許容するのであれば、サーバーに負担をかけないためにローカルで動作するtimeago.jslivestamp.jsを利用するのもオススメです。

今回問題になったのはある日付からある日付までの時間を計算する必要があったことです。
まず、サーバーになるべく負担をかけないようにローカルで動作するlivestamp.js ( v1.1.1 ) を選択しました。
※livestamp.jsをどのように使うかの説明は割愛します。

このライブラリ(他のもそうですが)ある日付と現在の時間を計算して表示することを目的としていますので、上記の問題にはそぐわなかったわけです。

ちょっとトリッキーな方法で解決しました。

helpers/application_helper.rbなどに

[ror]def between_date_livestamp(from_date, to_date)
(Time.zone.now – (to_date – from_date)).to_time.iso8601
end [/ror]

などとしておいてviewで

[ror]
<span data-livestamp="<%= between_date_livestamp(@a_model.start_date, @a_model.end_date)  %>"></span>[/ror]

のようにします。

between_date_livestampメソッドで行っていることは
比較したい二つの日時の「差」を出しておいて、
現在日時からその分離れている架空の日時を返すというものです。

逆にいうとその架空の日時は現在日時から比較したい二つの日時と同じ分だけ離れているので、
本日からどれくらい離れているのかを計算するlivestamp.jsの値として利用できるわけです。

デメリット
・サーバー側で計算したときの「今日」とローカルでlivestamp.jsが計算した「今日」では少なくとも通信時間分のズレがあります。厳密さが必要なときは利用できません。
・ せっかくサーバーに負担をかけぬようlivestamp.jsを使うのに、結局サーバーでの計算が必要。
・htmlの内容に架空の(何の意味もない)日付が記述されてしまう。

 

追記:(2012/09/17)

上記にバグ(というか見落とし)ありました。

livestampは時間がたつと更新されます。「今の時間」は刻々と代わっていくけれども、終了日時は変わらないのでおかしいことになります。

livestamp.jsの中身を見てみるとpauseというメソッドがあるのですが、グローバルらしいので使えません。
あんまりライブラリはいじりたくないので、かわりにdestroyメソッドを使ってなんとかしました。

[javascript]

$(‘.live-stamp-freeze’).each(function() {
var text;
$(this).livestamp($(this).text());
text = $(this).text();
$(this).livestamp(‘destroy’);
$(this).text(text);
)};

[/javascript]

[ror]
<span class=".live-stamp-freeze"><%= between_date_livestamp(@a_model.start_date, @a_model.end_date)  %></span>[/ror]

一度livestampで値を出してからその値を変数に入れておいてlivestampをdestroyします。それから再度textメソッドを使って再設定しています。 かなり強引ですが。。

 

jQuery UI dialogでcloseしてもdestroyしてもコンテンツが残る

jQuery UI dialogはclose destroyというメソッドが用意されています。
dialog.closeは文字通りdialogを非表示にするだけ。
dialog.destroyはdialogを消去します。

しかしこのdialog.destroyが曲者で、dialog内のコンテンツは消去しないようです。

動的に作ったjQueryオブジェクトに対し、dialogを作成すると、bodyタグの中に次々とごみが残っていきます。。

それを回避するためにはonCloseイベントでdialog.destroyメソッドを呼んだあと、
dialog.widgetメソッドを呼び、戻り値にremoveメソッドを呼んでやればすべて消えます。
[javascript]
scope = this
this.dialog = $(‘<div class="テスト">これはテストです</div>’);
this.dialog.dialog({
close: function(){scope.onClose()}
});
this.onClose= function(){
this.dialog.dialog("destroy");
this.dialog.dialog("widget").remove();
}
[/javascript]

jqGridのsetFrozenColumnsのイベント処理とその後のDOM構造について

javascriptをベースとしてエクセルのような機能を表現できるjqGridで最近では列固定、行固定することができるようです。

そのこと(setFrozenColumns)についてはこちらを参考にさせていただきました。

さて、setFrozenColumnsメソッドを呼ぶとのDOM構造が変わってしまいます。

それによって独自に拡張した部分の挙動がおかしくなりました。
正しく動作させるためにはsetFrozenColumns後に作られたDOMにあわせて拡張する必要があります。

そのためには以下の2つのことが必要ですが、ドキュメントにはなかったのでメモします。
※注意:この記事を書いた時点のバージョンjqGrid4.3.2におけるものであり、今後仕様が変更される可能性が高いです。

1:setFrozenColumnsが完了したというイベントを取る必要がある。

ソースを読むとどうやら、 “jqGridAfterGridComplete.setFrozenColumns”というイベントを発行しているようです。ですので

[javascript]
$grid.jqGrid(‘setFrozenColumns’);
$grid.on(‘jqGridAfterGridComplete.setFrozenColumns’, function(e){//処理})[/javascript]

とすればsetFrozenColumnsの完了イベントを受け取れます。

2:DOM変更後のテーブルを読み解く

DOM変更後は4つのテーブルに分かれます。

まず上下にテーブルが2つずつ。そしてそれぞれの左側に重なるようにテーブルが1つずつできます。このうちスクロールされるのが緑の部分というわけです。
上の二つのテーブルはクラスが”ui-jqgrid-htable’です。下の二つのテーブルには”ui-jqgrid-btable”という名前のクラスがつきます。ですので、

[javascript]hTables = $gird.find(‘table.ui-jqgrid-htable’);
bTables = $grid.find("table.ui-jqgrid-btable’);
$red_table =  $(hTables[0])
$blue_table = $(hTables[1])
$green_table = $(bTables[0])
$yellow_table = $(bTables[1])[/javascript]

とすればそれぞれのテーブルを取得できます。