Posted on

jQuery UI Datepickerにクリアボタンを追加してスマホでも使いやすくする

jQueryUIのDatepickerを使っているのですが、スマホなどで使用する場合は入力させる時にスマホのIMEがせり出てきてスマホの表示領域を圧迫して入力しづらい場面がやや出てきます。

そのinput要素をスマホの場合はreadonlyにする事で、IMEが出てくる事を回避する事はできるのですが、今度は入力した値を消したい場合はどうすればいいかという問題が出てきました。

Datepickerにクリアボタンを追加したらいいんじゃないかと思いその対応について記述したいと思います。


HTMLではdatepickerにする要素にreadonly属性を付与します。

   var setCalsClearButton = function(year,month,elem){

        var afterShow = function(){
            var d = new $.Deferred();
            var cnt = 0;
            setTimeout(function(){
                if(elem.dpDiv[0].style.display === "block"){
                    d.resolve();
                }
                if(cnt >= 500){
                    d.reject("datepicker show timeout");
                }
                cnt++;
            },10);
            return d.promise();
        }();

        afterShow.done(function(){

            // datepickerのz-indexを指定
            $('.ui-datepicker').css('z-index', 2000);

            var buttonPane = $( elem ).datepicker( "widget" ).find( ".ui-datepicker-buttonpane" );

            var btn = $('');
            btn.off("click").on("click", function () {
                    $.datepicker._clearDate( elem.input[0] );
                });
            btn.appendTo( buttonPane );
        });
   }

datepickerには開いた後のイベントがないため、開く前のイベントにウインドウとなる要素がdisplay:block;になったかどうかをpromiseで判定します。

要素が作られるまで待つのですが、inputを選択して5秒待ってまだ開かなかったらpromiseを辞めるというコードも書いています。その場合はおそらく内部エラーくらいだと思いますが、待ち続けているのも良くないので終了させるよう書いています。

要素が作られたら、ボタンを追加するコードを入れています。


  $(".datepicker").datepicker({
      showButtonPanel: true,
      changeMonth:true,
      changeYear:true,
      beforeShow : function(inst,elem){
              setCalsClearButton(null,null,elem);
      },
      onChangeMonthYear:setCalsClearButton
  })

datepickerを呼び出す所optionに、beforeshowに上記関数を指定します。

onChangeMonthYearにもオブジェクト関数を指定しています。これは月を変更した場合、再描画が走ってクリアボタンも消えてしまうので、月の変更時にイベントにボタンを追加するコードを走らせて都度生成するようにしています。

これで、入力しやすくなりましたね。

Posted on

deferredに感動したが、その感動を伝えきれない

「ボタンを押したらXMLから値をajaxで取ってきてフォームに入れて、その値を即座に10倍した値を別のフォームに入れたい」
という設定でコードを書く。


この画像の条件で「読め」を押すと、まず50の入っているフォームが2000に上書きされて、その下の10倍に20000が入るはず。

…。これで数日かかりました。なぜ読み込む前の値を読んでしまうのか。
「successコールバックに書く」という方法もなくはない。なくはないが
実際は「読み込んだ値をすぐ別のajaxで送信して」的なことが発生していた。

訳が分からなくなる…。

調べてみると、jQuery.Deffred と .then() という書き方があることに気づく。
ざっくりいうと、
「processedとかrejectedが返されるまで待機」
「$.ajaxはこっそりprocessedとかrejectedを返している」
「$ajax().then()のthen節にふたつfunctionを入れることができて、前半はprocessedのあと、後半はrejectedのあと実行される」
「.then()を直結連鎖させると上からprocessed側が実行されていく」…

とりあえず、.thenだ。と。


ajaxで値が変わるのを待ってから値をフォームから読んで書き直している。
これがやりたかったことだった。

まだまだ、使い方には研究が要りそうだ。

var deferred = new $.Deferred()

からの

return deferred.resolve().promise()


processedの結果を返したりとかもあった。

簡単なコードで連鎖に成功すると気持ちが良い。今はその感動だけ伝えたい…。

Posted on

アウトオブ眼中

ボタンをクリックしたら隠しdivの中のフォームをコピーして、
通し番号を発行して表に追加する、みたいな処理をよく書きます。
通し番号の入る場所は仮にダミー文字列(今回はQQQQ)を置いておいて、ボタンが押されたときの処理でまとめて書き換える感じです

何度か書いているうちにdata-属性に行番号を持たせることで、:lastから現在の最大行番号を把握し、
次の行番号を探る、なんてことをやるようになりました。
隠しフィールドよりずいぶんすっきり見えます。attrで持たせると文字列で保存されるので、取得時に0を引いて数値に解釈しなおすことをお忘れなく。 …【1】
追加するときだけじゃなくて削除にも役立ちます。$(“tr[data-no=’行番号’]”,”#fueruTable”).remove() で行ごと削れます。

ところで、これはこれで面倒なことが一つあって、これを実際submitしたとき。

QQQQはダミーフィールドなので使わないはずだし、何よりdataに関してforeachとか回すなんてことになると邪魔。
サーバー側にunset($_POST[‘data’][‘QQQQ’])を噛ませて長いことやってました。

ある時、formの閉じタグ位置を間違えて、隠しフィールドをformの外に追いやってしまったんです。
それでsubmitしたらどうなったかというと

QQQQがいない。
これはこれで何か問題がある気もするけれど、ただテンプレートとして置いておきたいだけで、そもそもsubmitする気のないフィールドは
そもそもform内に置かなければよかった!

考えてみれば当たり前のことなのに、長いこと気づかなかったのでついブログに書き出してしまいました。
submitしないフォームはformタグの外、アウトオブ眼中!他の事例でも使えたら使うつもりでいます。