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

「Effective JavaScript」かいつまみ -基礎編-

翔泳社の「Effective JavaScript」(David Herman著 / 吉川邦夫訳)の一章から、
重要そうな部分をピックアップして箇条書きで要約しました。
太字の部分は要約者の勝手な感想(?)です。
普段開発で使用しているPHPに関して主に追記しています。

第一章 JavaScriptに慣れ親しむ

項目2 JavaScriptの浮動小数点数を理解しよう

・JavaScriptでは、すべての数が倍精度浮動小数点数(いわゆるdouble)である。
・-9,007,199,254,740,992~9,007,199,254,740,992までの整数は全てdoubleで表現する事が可能。
・当然のように、浮動小数点数演算特有の問題が存在。

console.log(0.1 + 0.2); //0.30000000000000004

・少数を扱う際は一度整数にしてから演算して、結果を調整するほうがよさそう。

console.log((0.1 * 10 + 0.2 * 10)/10); //0.3

項目3 暗黙の型変換に注意しよう

・JavaScriptはほとんどの型エラーをスルーして実行してしまう。
・数値演算地の-、*、/、%は、計算を行う前に引数を数値に変換しようとする。
・ただ+だけは例外的な動きをする。
・以下例

console.log(3 + true); //4
console.log("2" + 3); //"23"

・真偽値の型強制。
・ifや||や&&のような演算子は、受け取った値を真偽値に型変換して解釈する。
・JavaScriptでは偽となる値は、false 0 -0 “” NaN null undefined の7つだけ。
・PHPの場合は、”0″ や [] もfalseになるので要注意。

項目5 型が異なるときに==を使わない

・==で値を比較する場合、項目3で既出の暗黙の強制によって両者が数値に変換されて比較される。
・===は型を考慮して比較。
・==のほうが便利な場合もあるのでついつい使ってしまう事もありますが、基本的には===で統一したほうがよさそう。
特にPHPの場合 var_dump(“hoge” == 0)  //結果:true などと直感に反するケースがある(最初が数字でない文字列は0に変換されるため)ので要注意。

項目7 文字列は16ビットの符号単位を並べたシーケンスとして考えよう

・Unicodeが16ビット内でおさまると考えられていた時代にJavaScriptは生まれたため、JavaScriptの文字列の要素は16ビットの符号単位である。(UTF-16形式)
・実際にはUnicodeには16ビットを超える文字も存在し(例えば辰吉じょう一郎のじょう・丈の右上に点)、それらは16ビットの要素2つで表現される。
・そのためUnicodeで16ビットを超える文字の場合は、length や charAt などで文字数と結果が一致しないことになる。

Posted on

jQueryプラグインSweetAlert2を使っていいカンジのconfirmをサクッと呼べるラッパー作りました

案件でよくユーザーに処理の実行を問いかけたい時にJavaScriptの標準機能のconfirmを使ったり、情報を表示する際にAlertを使いますが、どこか味気ないし、ブラウザによっても表示がまちまちなので、もうちょっとイイ感じにしたい…そんな時はSweetAlert2を使ってみましょう。

こんなconfirmがサクッと作れます!

SweetAlert2 – a beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript’s popup boxes

CSSとJSの読み込みです。sweetalert2はES6のPromiseを使って書かれているようなので、IE11以下や古いAndroidに対応するためにpolyfillを先に読んでおきましょう。

今回はCDNを使用しました。







SweetAlert2は便利なのですが、呼び出す構文にオブジェクトを作ったりする必要がありますが、標準のconfirmのように引数で呼び出せたらラクなんだけどなぁと思ったので、ラッパーを作ってみました。

See the Pen
SweetAlert2 wrapper
by koka (@kokaben)
on CodePen.

ボタン色はBootstrap4のクラスを指定しています。

以下コードをコピーしてページに貼るか、alertmessage.js等のファイルで保存してscriptタグで呼び出してください。

(function (ns) {

// Sweet Alert 2が読み込まれてなければ抜ける
if (typeof Swal === "undefined") {
console.error("プラグイン「SweetAlert2」が読み込まれていません。先にscriptタグで読み込んでください");
return false;
}

var AlertMessage = function () {

// インスタンスがあるかどうかチェック
if (typeof AlertMessage.instance === "object") {
return AlertMessage.instance;
}

// 無ければキャッシュする
AlertMessage.instance = this;
return this;
};

/**
* confirmラッパー
* @param _text String 表示したい内容テキスト
* @param _title String 表示したいタイトルテキスト
* @param _mode string アラートの種類 bootstrapと同じ or 'question' ( ?マーク )
* @param _callback function アラートの結果を受けて関数を呼び出す。引数に成否を渡す
* @param _array [] コールバックに渡したいもの配列
* @constructor
*/
AlertMessage.prototype.confirm = function (_text, _title, _mode, _callback,_array) {

var dispTitle = _title;
if (typeof dispTitle === "undefined") {
dispTitle = "確認";
}

var o = {
allowOutSideClick: false,
showCancelButton: true,
confirmButtonText: 'OK',
cancelButtonText: "キャンセル",
customClass: {
cancelButton: 'btn btn-gray', //bootstrap4のクラス
},
title: dispTitle,
html: _text
};

if (_mode === "success" ||
_mode === "error" ||
_mode === "warning" ||
_mode === "info" ||
_mode === "question") {
o["type"] = _mode;
};

Swal.fire(o).then(function (result) {
var retBool = false;
if (typeof result.value !== "undefined" && result.value === true) {
retBool = true;
}
if(typeof _callback === "function"){
_callback.call(this, retBool,_array);
}
});

};

/**
* Alertラッパー
* @param _text String 表示したい内容テキスト
* @param _title String 表示したいタイトルテキスト
* @param _mode string アラートの種類 bootstrapと同じ or 'question' ( ?マーク )
* @constructor
*/
AlertMessage.prototype.alert = function (_text, _title, _mode) {

var o = {
allowOutSideClick: false,
type: _mode,
title: _title,
html: _text
}
Swal.fire(o);
};

ns.AlertMessage = new AlertMessage();

})(window);

使い方

htmlでボタンを作成します。

削除ボタン 

基本の呼び出し方は以下となります。文言が長いので引数1つずつに改行を入れていますが、横に並べて書いても大丈夫です。

// ボタン押下のイベント
$(document).on("click", "#deleteUserButton", function (e) {
      e.preventDefault(); // 本来のボタン押下の処理のキャンセル
      
      AlertMessage.confirm(
          "ユーザーのデータを削除します。よろしいですか?", // confirm本文
          "ユーザーの削除の確認", // confirmタイトル
          "info", // アラートの種類
          function (_enter) { // コールバック
            if (_enter) {
              // OKが押された時の処理
            }
          });
    });

ボタンを押下するとこんな感じで表示されます。

SweetAlert2のconfirmをいつでもどこでもAlertMessage.confirm()引数で呼べるようになりました。

アラートの種類というのは、表示するアイコンの種類になります。
bootstrapの”success”, “error”, “warning”, “info”で指定できるので慣れている人は直感的に書けます。それに加えて”question”も指定できるのでユーザーに問いかけたい時は非常に便利です。

さらにconfirmのコールバックにさらに配列を渡せるようにしました。使いたい値がある場合は、便利かもしれません。

 var infoArray = ["タンタン", "パンダ", "神戸"];
    
      AlertMessage.confirm(
          "情報を追加してもよろしいですか?", // confirm本文
          "確認", // confirmタイトル
          "question", // アラートの種類
          function (_enter, _array) { // コールバック
            if (_enter) {
              // OKが押された時の処理
              var _text = "";
              $(_array).each(function(i){
                _text += this;
                if(i < _array.length -1){
                _text += ",";
                }
              });
              $("#output").html(_text); //タンタン,パンダ,神戸
            }
          },
          infoArray // コールバックに渡したい配列
          );
    });

ついでにAlertMessage.alert()も作りました。シンプルにアラートが出せます。

AlertMessage.alert(
          "処理を中断します。", // alert本文
          "エラー!", // alertタイトル
          "warning", // アラートの種類
         );

jQueryのプラグインはとても便利ですので、そのプラグインをもっと簡潔に使えるような工夫をすればもっともっと手っ取り早くコーディングができるようになりますよ。

Posted on

JavaScriptトラップ集

業務上JavaScriptを使用していて、ハマったorハマりかけたポイントをまとめてみました。

1、indexOf(危険度:★★★☆☆)

文字列内に特定の文字列が存在するかを判定したい時に、
indexOf()を使用すると便利ですが、

 
 let tmpArr = '小谷翔平';
 let searchArr = '谷';
 console.log(tmpArr.indexOf(searchArr)); //結果:1 ⇒ 文字列の2文字目に存在

気をつけなければならないのは、
特定の文字列が存在しない時に返って来るのが、falseでは無く-1だという事です。

 
 let tmpArr = '小谷翔平';
 let searchArr = '鈴';
 console.log(tmpArr.indexOf(searchArr)); //結果:-1

PHPの同じような関数のstrposではfalseで返ってくるので、
業務で両方使っていると紛らわしいですね。

また、indexOfで特定の文字列が一番最初に存在する場合は、
0が返ってくるので、以下のような使い方はバグのもとになります。

 
 let tmpArr = '小谷翔平';
 let searchArr = '小';
 if(!tmpArr.indexOf(searchArr)) { //0だと通ってしまう
	//文字列が含まれない場合はなんらかの処理
 }

2、parseIntとNumberの違い(危険度:★★☆☆☆)

JavaScriptで文字列を数字に変換したい場合、
主にparseInt()、Number()の2種類の方法があります。

 
 let tmpArr = '1234';
 console.log(Number(tmpArr)); //結果:1234
 console.log(parseInt(tmpArr)); //結果:1234

両者の違いですが、parseIntの場合は数値以外の文字列が含まれる場合でも、
数字を抽出して返却してくれるケースがある事です。

 
 let tmpArr = '1234abc';
 console.log(Number(tmpArr)); //結果:NaN
 console.log(parseInt(tmpArr)); //結果:1234

詳しい事はこちらで。
このあたりはトラップが多いので、replace()等であらかじめ数字を抽出したうえでNumber()をかけるのが良さそうです。

 
 let tmpArr = 'abc1234';
 console.log(Number(tmpArr.replace('abc', '')); //結果:1234

3、JavaScriptとphpでの空配列の扱い(危険度:★★★☆☆)

JavaScriptとphpでは条件式での真偽値がちょくちょく違うので要注意。

//JavaScriptの場合
 console.log(Boolean([])); //結果:true
//PHPの場合
 var_dump((Boolean)[]); //結果:false

空の配列では真偽値が逆になります。
ここら辺も闇が深そうなので業務で両方使う場合は注意が必要そうですね。

4、JavaScriptのDate関連(危険度:★★★★☆)

JavaScriptではDateオブジェクトで日時を扱いますが、
こちらのページに詳しいように、
いろいろとトラップが多いので注意が必要です。

特に個人的にハマったのが、getMonth()の挙動です。

 
 let date = new Date('2019-02-04T03:24:00');
 console.log(date.getMonth()); //結果は2では無く、1

他にも、日付を得るのはgetDay()ではなくgetDate()とかいろいろと紛らわしいです。

Posted on

LodashのTemplateとsortByを使って並び替えと再利用に強いページを作ろう

フロントエンドの業務でAJAXでバックエンドと接続して内容を取得してデータを表示する、なんていう場面は多々あると思います。

ただ、AJAXで取得したデータを一つ一つ要素を作成して流し込んでいく事が面倒だったり、表示の並びなどはViewだけでちょっと触るだけで変更できればバックエンドの処理が減って良かったりします。

今回は、jQueryとJavaScriptの便利ライブラリ集Lodashのテンプレート機能とソート機能を使ってみたいと思います。

続きを読む LodashのTemplateとsortByを使って並び替えと再利用に強いページを作ろう

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タグの外、アウトオブ眼中!他の事例でも使えたら使うつもりでいます。

Posted on

jQueryカラーピッカーspectrum.jsの使用方法

案件にて、色を扱いたいといった事があって、サクッと色を選べるプラグインが無いかなと探してみた所、spectrum.jsというイイ感じのプラグインが見つかりましたので、ご紹介したいと思います。

spectrum.js

DEMO

使用方法

このプラグインはjQueryに依存しますので、jQueryを先に読み込ませます。
そして、使用したいhtmlにspectrum.jsとspectrum.cssを読み込みます。
jquery.spectrum-ja.jsは日本語対応です。必要である場合は使用します。





inputにクラス名を付けておきます。


bodyタグの最後にscriptにてspectrumを呼び出します。
パレットに予め色を用意しておきたい場合は、paletteプロパティに配列で色を#RRGGBB形式で入力しておきます。

	jQuery(function($){
		$(".picker").spectrum({
			color: "#f00", //初期色
			showPalette: true, // パレット表示あり
			palette: [ // パレットで使う色を指定
				["#f00", "#0f0", "#00f", "#ff0", "#f0f", "#0ff"]
			]
		});
	});

IE11でsubmitしても値が反映されなかった時の対応

IE11を使用してシステムにsubmitした際、データが飛ばないので探ってみると、カラーピッカーを使用した後にinputのvalueに#RRGGBBの形で値が設定されていませんでした。

spectrumに用意されている`change`イベントでinputを#RRGGBB値に書き換えるようにしてやれば解決しました。

    jQuery(function($){
        $(".picker").spectrum({
			color: "#CF85A1", //初期色
			showPalette: true, // パレット表示あり
			palette: [ // パレットで使う色を指定
				["#CF85A1", "#CCD58D", "#94DAC0", "#A49CDF", "#ACA4E4"]
			],
            change: function(color) {
                $(this).val(color.toHexString());
            }
        });
    });

簡単な設定で細かい色指定ができるので、これは重宝しそうです。

Posted on

全画面ページを簡単に作成!fullpage.jsの使い方とTIPS集

案件で全画面ページを簡単に作るプラグイン「fullpage.js」を使用する事がありました。

今回、レスポンシブ対応にあたって、文章等が1ページの範囲を超える場合があり、その際はスクロールバーを表示してスクロールできるようにしたいため、scrolloverflow.jsも使用しました。

そちらの簡単な使い方とハマった所をtipsとして取り上げます。

初期設定

まずは、必要な読み込みファイルから。

fullpage.js 公式サイト

まず、表示させたいページに必要なCSSとjsを読み込みます。








全画面ページにしたい所は起点の要素(今回はIDがfullpageの要素)と、その子クラスにsectionの名前を付けます。

横スライドも欲しい場合は、section要素にその子のクラスにslideという名前を付けます。

Some section
Some section
Some section
Slide 1
Slide 2
Slide 3
Slide 4
Some section

jQueryを使って呼び出します。

$('#fullpage').fullpage({
   anchors: ['page1', 'page2', 'page3', 'page4'], // ページのアンカー名を指定
   menu: '#global-menu', // グローバルメニューのID名
   scrollOverflow: true, //全画面よりコンテンツが多い場合スクロールバーを出すかどうか
   loopHorizontal:false, //横スライダーをループさせない
   touchSensitivity: 16, //タッチデバイスでのセクション切替の強さの閾値
   afterLoad: function (anchorLink, index) {
    //セクションが読み込み終わりのイベント
    if (index === 1) {
     //TOPロード時
	
    }else if(index === $('#fullpage .section').length){
     //最下ページ時
    }else{
     //その他のページの時
    }
   },
   afterSlideLoad: function (anchorLink, index, slideAnchor, slideIndex) {
     // スライドが読み込み終わった後のイベント
   },
   onLeave: function (index, nextIndex, direction) {
     // セクションを離れた時のイベント
   },
   onSlideLeave: function (anchorLink, index, slideIndex, direction, nextSlideIndex) {
     // スライドを離れた時のイベント
   },
   afterResize: function () {
    // リサイズ後のイベント
  }
});

グローバルメニューが要る場合には、上記menu:"#global-menu"のように指定して、li要素のdata-menuanchorに上記anchors配列で指定した名前と対応させます。

そうすると、グルーバルメニューのクラスに現在居るページはactiveが付きます。ページが遷移される度に現在居るクラスにactiveが付き、移動元ページのactiveクラスは外れますので、メニューのON/OFFのCSSの装飾などが簡単に作れます。


これで、縦4ページ、3ページ目に横に4スライドある全画面ページが出来上がったと思います。

TIPS

作成に当たって色々とありましたので、そこで得たTipsをドドーンと紹介しちゃいます。

可変サイズ対応

全画面のページのサイズが確定した後にAJAXやdisplay:noneしていた要素を表示させて、表示させている要素が増えた場合、スクロールする領域が足りない事が起きたりします。その際は、rebuild()関数を呼び出して、再度サイズ計算し新たなページのサイズを確定させます。

// フェードイン・フェードアウトの後fullpage.js再計算
$("#recruit-space").fadeToggle(500,function(){
    $.fn.fullpage.reBuild();
})

fullpage.jsの読み込み後に何かをさせたい場合

jQueryのロード時とfullpage.jsのロード時はタイミングとして別になるので、 fullpage.jsが読み込まれた後に処理を走らせたい場合は、afterRender以下に処理を書きます。

afterRender: function () {
    //(例)クリッカブルマップレスポンシブ
     $('img[usemap]').rwdImageMaps();
}

ページを遷移させる度にGoogle Analyticsのコードを送る

fullpage.jsは1ページを複数ページとして見せているだけなので、普通にGoogle Analyticsコードを貼っても該当ページの読み込み時に1回しか送られません。

貼るコードを分けて、縦ページの遷移、横ページをスライドさせた時に、Analyticsのコードを送るように書けば思うようなデータが取れます。

通常のアナリティクスコード読み込みの時に、下の方にあるga('send', 'pageview');を外します。


そして、fullpage.jsの横スライドが読み込まれた際に起きるイベントafterSlideLoad内にga('send'...のコードを書きます。

afterSlideLoad: function(anchorLink, index, slideAnchor, slideIndex) {
   ga('send', 'pageview', { 'page': anchorLink, 'title': slideAnchor });
},

縦ページ遷移に対応するにはadterLoadイベント内に記述します。

  afterLoad: function (anchorLink, index) {
     ga('send', 'pageview', { 'page': anchorLink, 'title':anchorLink });               
  }

まとめ

SPA風にページが手軽に作れたり、アイデア次第で面白いページが作れると思います。その分独自のライフサイクルやイベントがあるのでハマり所があったり、SEO的にはどうなるんだろうと思う所がありましたが、ここまで対応できれば普通に使えそうですね。

また何かあったら追記しようと思います。

fullpage.js 公式サイト

Posted on

ドラッグアンドドロップでファイルをアップロードする

今回ファイルを選択してあげている所をドラッグアンドドロップでファイルを指定して上げたいという要望を受けて対応しました。
jqueryさえあれば簡単に出来ちゃうので一番シンプルなサンプルを作ってみました。
続きを読む ドラッグアンドドロップでファイルをアップロードする