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

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です。

fontawesomeとCSSのみでstar ratingを実現する

fontawesomeとCSSのみでstar ratingを実現することができました。

IE9以上が必要です。
※まだIE10以上ではテストしていません。
なにか見つけたら教えていただけるとありがたいです。

http://jsfiddle.net/dyosuke/67XXF/

下記は画像を使用していて大変参考にさせていただきました。

http://jsfiddle.net/LTQrZ/20/

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]

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

jQueryの$.css(‘width’)や$.css(‘height’)取得はcssを設定していなくても実際の値が返ってくる

jQueryので幅や高さを取得する場合はwidth()メソッドやheight()メソッドが便利だが、
css()メソッドを使用して$.css(‘width’)や$.css(‘height’)でも取得できる。
でも単純にcssで設定されている値を取得したい場合には邪魔になる機能だったりする。
css()メソッドはCSSとして設定していないのに数値pxが返ってくるらしい。(jquery1.7.1で検証)

この場合はget()メソッドを使用して一度DOMを取得してからDOM.style.widthやDOM.style.heightを参照するとよい。

[HTML]
<div id=”test”>TEST</div>
<script>
console.log($(‘#test’).css(‘width’));//出力:’1345px’ (実際の大きさ)
console.log($(‘#test’).get(0).style.width);//出力:” (空)
</script>

[/HTML]

Flasherの衰退と今後の人材と教育について

以前勤めていた会社ではFlasher(Flashコンテンツを作成する人)として働いていました。
当時はFlashがリッチコンテンツを作るうえでとても手軽なことがあり、
ブラウザ上だけでなく、SONYのVAIOに標準搭載されるアプリケーションのUI部分の作成など、
Flashを作成する技術が世の中で重要なスキルだったとおもいます。

「Flashが今後なくなることはないけれども、モバイル機器用のは開発しない」というアドビの発表。

さて、それに代わるのがHTML5だというのですが、これは話を少々単純にしすぎです。
「一部ではそう」ですが、ほんの一部です。もっというとHTML5で新しくできることって厳密に言うとそんなにないのではないかと思います。

ビデオが見れる。絵が描ける。などの実現はFlashではなくてもQuickTimeやJavaアプレットなどでできました。HTML5の厳密な見方はこのような「プラグインに任せていた部分をブラウザがやりますということになった」ということだと思います。
実際HTML5として紹介されている多くの部分をAjaxが担っています。
Ajaxが登場したときも同じでした。Ajaxは通信する方法の名前でしかなく、開発はほぼjavascriptです。

カートにドラッグできるショッピングサイトなんかをFlashで作るようなことは
ずいぶん前からなくなっていて、Ajaxで作ります。
その時点でFlashの役割のかなりの部分はAjaxに取って代わっていました。

CSS3もそうですね。

このように、Flashは急に役割を取られたのではなく、徐々にそうなっていったのです。

それなのに突然な印象を与えたのは、Apple社のモバイル製品がフラッシュを非対応にしたときです。
正直「えぇ?」と思いました。それは二つの理由からです。
ひとつはまだまだFlashで作られたサイトやサービスがたくさんある状況でお客さんがどのように反応するだろうという不安。
二つ目はFlashが(Flasherという職が)なくなってしまうのではないかという危惧。

一つ目の理由については、本当に驚きました。一流の先導者の凄み、出てました。
二つ目の理由についてはそれほど意外なことではなかったです。

さて、HTML5の時代にこれから入ります。
今後Flasherは普通にやっていたら食べれないと思います。

進路のパターンとしては、
1:他の技術も習得してポリバレントな技術者になる。
2:さらに進化していくだろうFlashの最先端をひたすら追い続け、職人として生きる。

どちらかというと私は1のパターンです。デザインやアニメーションが強い人は2のパターンになるのかな。

媒体で言うと、1のほうが圧倒的に舞台が多い。

そういうわけで就職も1の人材のほうが就職しやすいでしょうね。
2は会社を選びます。故に狭き門。職人の淘汰がされるでしょうね。

HTMLコーダーと呼ばれている職業の人がHTML5という名前だけを理由に、それをやらされているとも聞きます。
FlashでActionScript書いてた人のほうが簡単に習得できる技術だと思うので、
HTML5の表面の部分(マークアップとCSS)と動作部分(Javascript)で職種名を分けて、
Javascript側をやる人として再就職とかお勧めです。Javascripterとか、Ajaxianとか。

現在の教育の現場でもいまだにHTMLとかFlashとか分けて教えられていますが、
それらの境目を作るのは早くやめてほしいと思います。
まずウェブサービスが動いている全体を感じられる指導をしてほしい。

1:ネットワークを組む。(プラモデル方式に)
2:サーバーを立ち上げる。(ざっと)
3:データベースを作成(いわれたとおりに)
4:サーバー側の簡単なプログラム(一応内容を把握)
5:クライアント側の簡単なプログラム(アレンジしてみる)
6:それらを動かす。

これを一度体験しておくことで、その後どんな専門を持ったとしても、
技術革新が早いこの業界においても自分を変えながら生きていける絶滅しない人材になれると思います。

DeAGOSTINIさんあたりで出ないかな。ニッチすぎるか。。