かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

PHP Slim 2.x Ajaxでputで送った値を取得したい。

"Slim is a PHP micro-framework" Rubyのテンプレートエンジンじゃない娘のお話です。

3系が既に出ているのですが、先方のサーバーのPHPのバージョンの関係で2系で開発をしています。今回 Ajaxのputメソッドでデータを送ったのですが、Slim側で値をとるのに少しハマったのでメモ。

Ajaxを送る側はこんな感じ

$(function () {
  var appAPI = {
    update: function(id, data) {
      var defer = $.Deferred(),
          apiPath = '/api/item/';
      $.ajax({
        type: 'PUT',
        url: apiPath + id,
        data: data,
        dataType: 'json',
        success: defer.resolve,
        error: defer.reject
      });
      return defer.promise();
    },
  };
  
  // 編集を保存する時に実行される関数
  var onEdited = function(itemID) {
    var formData = $('#my-form').serialize();
    appAPI.update(itemID, formData)
      .done(function(res) {
        // SECCESS
      })
      .fail(function(XMLHttpRequest, status, errorThrow) {
        // ERROR
      });
    return;
  };
});

Slimの設定

<?php
require('./vendor/autoload.php');

class MyAPP {
  public $slim;

  public function __construct() {
    $this->slim = new \Slim\Slim();
    $this->setupRouter();
  }

  // Routing
  private function setupRouter() {
    // API
    $this->slim->get('/api/item/:id', array($this, 'getItemByID'));
    $this->slim->post('/api/item', array($this, 'insertItem'));
    $this->slim->put('/api/item/:id', array($this, 'updateItem'));
    $this->slim->delete('/api/item/:id', array($this, 'deleteItem'));
  }
  
  // GET
  public function getItemByID($id) {
  }
  
  // POST
  public function insertItem() {
    // Ajaxから送られてきたデータは $_POST で配列形式で取得できる
    $postData = $_POST;
  }
  
  // PUT
  public function updateItem($id) {
    // Ajaxから送られたデータ $_POST は空配列隣取得できない。
    // Slimの機能を使って取得する
    $res = $this->slim->request();
    // getBody() で取得できるデータは `id=1&name=job1&...`のような形式になる
    $postData = $res->getBody();
    // parse_str で配列に変換する
    parse_str($postData, $postDataArray);
  }
  
  // DELETE
  public function deleteItem($id) {
  }

  public function run(){
    $this->slim->run();
  }
}

$myAPP = new MyApp();
$myAPP->run();

Ajax側から$form.serializeArray()で送れば、Slim側ではjson形式で取得できるのではないか?
と思って試してみたのですが、$res->getBody()で取得できる値は&つなぎの文字列になっていて、json_decode()で配列にすることはできませんでした。メソッドによって取得できる値の形式が異なるとメンドーなので、ぜんぶjson形式で取得できるのが楽なのですが...
もしかすると、Ajaxで送信するデータを自前でjson形式にしてしまうと、Slim側の$res->getBody()で取得できる値もjson形式になるかもしれません。


javascript 確認ダイアログ(confirm)の文章を改行させたい。

削除するとか、元に戻せない動作を行う前には誤動作の可能性を考慮して、確認ダイアログを入れるのが好ましいと思っています。
独自実装しても良いのですが、デザインが無かったりで、とりあえずサクット作ってしまっておきたい場合はデフォルトのwindow.confirm()を使うことが多いです。
で、このデフォルトのconfirmで表示させる文字を改行させたい時は\nを入れればOK。(HTMLタグは効かなきので<br>とか書いてもそのまま表示されます。)

コードを書くとこんな感じです。

if( window.confirm('この動作は元に戻せません。\n那珂ちゃんのファンを辞めますか?') ) {
  // 那珂ちゃんのファンを辞める処理
}

サンプル

那珂ちゃんのファンを辞める


エンコードする時に便利です。

ある要素までスクロールしたらアクションをさせるJavascriptを作成する時に気をつけること。

画面をスクロールさせて、特定の要素の場所に来た時にアニメーションをさせるなどスクロールに応じたアクションの作成をすることがあります。
複雑なものの場合ライブラリを使うのが手っ取り早いと思いますが、簡易なものの時や大きなライブラリを読み込ませるのもなぁ〜って場合独自実装したりしますが、その際に気をつけることのメモです。

例えば下記のように単純にスクロール量が要素の位置にくると動作をさせるといった実装をしたとします。

var $w = $(window),
    $target = $('#js-target-element'),
    actionPos = $target.offset().top,
    timer;

// スクロールの監視
$w.on('scroll.myScrollAction', function(){
  clearTimeout(timer);
  timer = setTimeout(function() {
    // スクロール量
    var st = $w.scrollTop();
    // 動作させる要素の位置までスクロールされている場合はアクション
    if( st >= actionPos ) {
      // $target をアニメーションさせるとかの処理をココに記述
      targetActionStart();
      // スクロールの監視を停止
      $w.off('scroll.myScrollAction');
    }
  }, 100);
});

しかし、これでは要素が画面の下の方にあったり、サイトの高さが無いとか縦長のモニターでスクロール量が足りないとか、そもそもスクロールが無いとかでアクションが実行されないといったバグを出してしまう可能性があります。 f:id:kikiki-kiki:20160819175604p:plain

画面にどれだけスクロール量があるのかを事前に取得してアクション開始の条件を変更する

このバグを回避するには、表示されている画面にどれだけのスクロール量があるのかを事前に取得して、スクロール量が要素のアクションを起こす作業まで足りない時や、スクロールが無い時には直ぐにアクションを実行させるとかの処理をすればOKです。

ページのスクロール可能な量の取得

ページのスクロール可能な量は下記で取得が可能です。

スクロール可能な量 = ドキュメント全体の高さ - 表示エリアの高さ

また、下図のように上の計算で求められたスクロール可能な量0以下の時はスクロールが発生していないと判定することができます。
f:id:kikiki-kiki:20160819203400p:plain

ドキュメント全体の高さbody.clientHeight表示エリアの高さhtml.clientHeightで取得が可能のようです。
ただ、スクロール可能な量がマイナス値を求める必要は無いので、ドキュメント全体の高さはスクロール量を含めた高さを返すhtml.scrollHeightを使えば、

  1. ドキュメントの高さが表示エリアの高さより大きい場合はスクロールを含めたbody.clientHeightと同じ値で、スクロール量は正の値
  2. ドキュメントの高さが表示エリアの高さより小さい場合は表示されているエリアの高さhtml.clientHeightと同じ値となり、ドキュメントの高さがどれだけ小さいかに関わらず、スクロール可能な量の計算結果は常に0にする事ができます。

[参考] 結局「下からのスクロール位置」を取得するにはどうすればいいのか - Qiita

スクロール可能な量を返す関数

使い勝手が良いように、関数化します。

/**
 * html.scrollHeight = $('body')[0].scrollHeight
 * html.clientHeight = $('html').height(), $('body').height(), $('body')[0].offsetHeight
 */
var getPageScrollAmount = function() {
  var html = window.document.documentElement;
  return html.scrollHeight - html.clientHeight;
};

スクロール量が足りない場合でも動作するようにする

ページ全体のスクロール可能な量(高さ)動作させたい要素の位置(高さ)を比較して、動作させたい要素の位置(高さ)の方が下にある(値が大きい)場合は、動作を開始する位置を変更するようにします。

var $w = $(window),
    $target = $('#js-target-element'),
    targetPos = $target.offset().top,
    actionPos,
    timer;

// スクロール可能な量 を返す関数
var getPageScrollAmount = function() {
  var html = window.document.documentElement;
  return html.scrollHeight - html.clientHeight;
};

// アクションを開始する位置を調整する関数
var adjustActionPos = function() {
  var pageScrollAmount = getPageScrollAmount(),
     pos = targetPos;
  
  if( pageScrollAmount <= 0 ) {
    // スクロールがない時
    pos = 0;
  } else if( actionPos > pageScrollAmount ) {
    // スクロール量が足りない時
    pos = pageScrollAmount - 100; // ex ページ最下部より100px上の位置にする
  }
  return pos;
};

actionPos = adjustActionPos();

// スクロールの監視
$w.on('scroll.myScrollAction', function(){
  clearTimeout(timer);
  timer = setTimeout(function() {
    // スクロール量
    var st = $w.scrollTop();
    // 動作させる要素の位置までスクロールされている場合はアクション
    if( st >= actionPos ) {
      // $target をアニメーションさせるとかの処理をココに記述
      targetActionStart();
      // スクロールの監視を停止
      $w.off('scroll.myScrollAction');
    }
  }, 100);
})
.on('resize.myResize', function() {
  // 画面サイズが変更された時、アクション開始位置を調整し直す。
  actionPos = adjustActionPos();
});
// triggerでスクロールアクションを実行させてスクロールが無い時は直ぐにアクションを実行させる
$w.trigger('scroll.myScrollAction');

こんな感じで、スクロールがない時やスクロール量が足りない時に、アクションが実行されないといったバグが解消しました。
例によって古いIEとか色んなブラウザでの動作チェックしていません。
また、説明のしやすさを重視しているので、変数の定義の仕方とかイケてないので参考にされる場合は適時変更してください。

久々にjavascriptの記事書いた気がします。(だが、jQuery...


[参考]

オブジェクト指向JavaScriptの原則

オブジェクト指向JavaScriptの原則