投稿日:

「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 などで文字数と結果が一致しないことになる。

投稿日:

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

投稿日:

EAVをなんとかし隊

これは平成最後の月。
CakePHP2.x系。入力チェックが通らないたび、フォームの内容が編集前に戻ってしまう不具合を直したときの話。

単純にまず、DBがイケてなかったんです。


INT id
INT type
VARCHAR(255) value

どこからどう見てもEAV。Entity Attribute Value。
何故よくないかのDB設計理論的な難しい説明は置いとく。
DB構造はその時動かす権限がなかった。(もし権限があったらその根本原因から直してしまいたかった)

で、その不具合が発生している画面を見て。

・DBから持ってきたデータ構造
・フォームへ渡すときのデータ構造
・フォームの初期値を表示するとき

これらが全部バラバラで往生した…。

DBからとってきたときの配列:

[
    0=> ['Model'=> [id => 1, type => 1, value => 5]],
    1=> ['Model'=> [id => 2, type => 2, value => "someaddress@example.com"]],
    2=> ['Model'=> [id => 3, type => 2, value => "anotheraddress@example.com"]]
]

これを加工して,typeをキーにしてビューに渡すけれど:

[
    1=> [
        0=> ['id'=>1, 'value'=>5]
    ],
    2=> [
        0=> ['id'=>2, 'value"=>"someaddress@example.com"],
        1=> ['id'=>3, 'value'=>"anotheraddress@example.com"]
    ]
]

ビューでさらに取り出す:
(Formヘルパに’value’で渡している)

$this->Form->input('Model.type_1.value',['type'=>'text', 'value'=> $this->request->data[1][0]['value']);
$this->Form->input('Model.type_2.0.value,['type'=>'text', 'value'=>$this->request->data[2][0]['value']]);
$this->Form->input('Model.type_2.1.value,['type'=>'text', 'value'=>$this->request->data[2][1]['value']]);

(あと、idをそれぞれhiddenフォームで送っていたりする)

フォームを送信すると、入ってくるのは…

[
    'Model'=> [
        'type_1'=> ['id'=> 1, 'value'=>5],
        'type_2'=> [
            0=> ['id'=> 2, 'value'=> 'someaddress@example.com],
            1=> ['id'=> 3, 'value'=> 'anotheraddress@example.com]]
	]
    ]
]

当然、DBからデータを取ってきたときとも最初フォームに渡したときとも違う構造。
このままではフォームの次の値が取れないから、前のコードではリクエスト関係なくDBからその都度データ読み込んで来ていた。
エラー値ならエラー値のままフォームに残しておく仕様なのに…。

とりあえず、その時はビューに渡すときの構造と受け取る時のデータ構造をそろえた。
(そうでないと変換が双方向に必要になるんで)
そろえれば使えなくはない。
Formヘルパに渡すvalue要素も要らなくなるし、入力チェックが通らないときフォームの初期値を作るためだけにDBを読みに行かなくていい。

でも、それでもやっぱりEAVはイケてない…。

この構造の時、まず絶対に1つしかないパラメータをこう…ひとつテーブルにして

INT model_id
INT type1_val

で、複数登録する必要がありそうな情報だけテーブルを分けてこう…。

INT model_mail_id
VARCHAR type2_mail

そしたら、DBからとってきた構造をパースしてフォームに渡す必要って本来それほどないはずなんです…。

僕がDB設計するときには、後輩に同じような愚痴を語らせないためにも、
あと、DB設計理論以上にそもそもコーディングがめちゃくちゃ往生するので、できることならEAVは避けていきたいという話でした。

投稿日:

Imagickを利用するにあたって

PDFファイルの1ページ目をイメージファイルjpgに変換するサンプルです。
imagickのサーバーへのインストールは、このブログからは除きます。
当初

$im = new Imagick();
//画像を生成したいPDFを読み込む
$im->readImage('hoge.pdf');
//特定のPDFのページ 0が表紙
$im->setImageIndex(0);
//サムネイルサイズ 640pxに収める
$im->thumbnailImage(640, 640, true);
//シャープ
$im->sharpenImage(0, 1);
//生成
$im->writeImage('out.jpg');

$im->destroy();

を元に関数を作成したのですが、
この記述では、階層が深いPDFを指定した場合は、エラーになるという致命的な問題が
潜んでいます。
そこで、ソースを

$im = new Imagick();
//画像を生成したいPDFを読み込む
$image = file_get_contents('/var/www/xxxx/xxxx/hoge.pdf');
$im->readImageBlob($image);
//特定のPDFのページ 0が表紙
$im->setImageIndex(0);
//サムネイルサイズ 640pxに収める
$im->thumbnailImage(640, 640, true);
//シャープ
$im->sharpenImage(0, 1);
//生成
$im->writeImage('/var/www/xxxx/xxxx/out.jpg');

$im->destroy();

で動くようになりました。
しかし、透明の箇所が真っ黒になるという不具合がありなおかつ
複数ページあるPDFと1ページのみのPDFでは動きが違うという問題にも直面
この難題を解決するのに、3時間程度要しました。
解決済みのサンプルがこちらです。

        $im = new Imagick();
        //画像を生成したいPDFを読み込む
        $image = file_get_contents('/var/www/xxxx/xxxx/hoge.pdf');
        $im->readImageBlob($image);
        $totalPage = $im->getImageScene();

        //サムネイルサイズ 640pxに収める
        $im->thumbnailImage(640, 640, true);
        //シャープ
        $im->sharpenImage(0, 1);
        // バックグラウンドを白にする。
        $im->setImageBackgroundColor('white');
        //特定のPDFのページ 0が表紙
        if ($totalPage != 0) {  // 複数ページの場合
            $im->flattenImages()->setImageIndex(0);
        } else {    // 単一ページの場合
            $im = $im->flattenImages();
        }
        //生成
        $im->writeImage('/var/www/xxxx/xxxx/out.jpg');
        $im->destroy();

以上です。

投稿日:

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()とかいろいろと紛らわしいです。

投稿日:

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

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

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

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

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

投稿日:

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の結果を返したりとかもあった。

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

投稿日:

公開フォルダーにないPDFファイルを表示する方法

PDFファイルアップロードの機能を作成する場合、公開フォルダでない場所にアップロードして、
そのPDFファイルをaタグで別タブに表示したい場合があります。
そこで、その場合のサンプルを記載します。

ファイル名やIPアドレスはご使用の環境にあわせてください。
main.php(表示したいファイルをフルパスでGETで渡す。)




download

表示したいフォルダのファイルをオープンしてPDFとして表示させるためのphpファイル
pdfdisp.php


以上です。試してみてください。

投稿日:

アウトオブ眼中

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

投稿日:

就職していた期間の合計を取得する方法

2000/01/08から2005/05/31まで就労
2006/01/08から2010/05/31まで就労
2011/01/08から現在も就労
の全就労日数を求める方法を紹介します。

データ構造サンプル
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(40) NOT NULL ,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci AUTO_INCREMENT=1 ;
CREATE TABLE works (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL ,
start_date DATE NOT NULL ,
retire_date DATE NULL DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci AUTO_INCREMENT=1 ;

データの情報としては、
users.id:1,users.name:奥進太郎
works.id:1,works.user_id:1,works.start_date:2000/01/08,works.retire_date:2005/05/31
works.id:1,works.user_id:1,works.start_date:2006/01/08,works.retire_date:2010/05/31
works.id:1,works.user_id:1,works.start_date:2011/01/08,works.retire_date:null

1回毎の就労日数を求めるVIEWを作成します。
CREATE VIEW work_diffs AS
select works.user_id AS user_id,
(case when isnull(works.retire_date) then (to_days(curdate()) – to_days(works.start_date))
else (to_days(works.retire_date) – to_days(works.start_date)) end end) AS work_diff
from `works`;

上記VIEWをgroup byで合計すると、全就労日数が取得できます。
CREATE VIEW work_sum_diffs as
select work_diffs.user_id as user_id,sum(work_diffs.work_diff) as sum_work_diff
from work_diffs group by work_diffs.user_id;

usersとwork_diffsをJOINすれば、users毎の全就労日数を取得することができます。