k01ken’s b10g

He110 W0r1d!

CakePHP3でformatResults()を用いて後処理をする

開発環境は、Windows 10 Pro(64bit) + PHP 7.2.12 + CakePHP 3.7.9。

findメソッドを用いて、containなどで関連もつなげて作り終わったクエリーに対して、formatResults()を用います。今回は、受け取った値に応じて、違う値を、新たに生成したカラムに入れたいと思います。

<?php
      /* $query変数に、クエリーを作り終わった後に・・・ */
      $query->formatResults(function($results) use($flug){
        return $results->map(function ($row) use($flug){
          $row['flug'] = 0;
          if($flug === 'black'){
            $row['flug'] = 1;
          }
          return $row;
        });
      });
?>

これによって、$flugがblackであれば、新たに生成したカラムflugの値は1、そうでなければ、0が入るようになっています。


■参考リンク
https://book.cakephp.org/3.0/ja/orm/query-builder.html#format-results

TwitterのDM部分みたいに下にメッセージをどんどん追加する

TwitterのDM部分は、スクロールが最初から一番下に位置していて、一番上に持っていくと、無限スクロールによって、古いDMが読み込まれるような構造です。

最初に投稿した段階で、メッセージは一番下に来るようにするには、positionをabsoluteにしなくてはいけません。
ただし、positionをabsoluteにすると、overflow:scroll;が機能しませんので(※後で検証してみると、position:absolute;でも、top,left,right,bottomのすべてを指定していないとスクロールが表示されます)、データを追加していって、一定の高さを超えた段階でpositionをstaticにして、スクロールできるようにします。

そして、データを追加するたびに、スクロール位置は一番下に移動するようにしています。

CSS部分

#dm_scroller{
  position:relative;
  width:450px;
  height:450px;
  overflow:scroll;
  overflow-x:hidden;
}

#dm_block{
  padding:0px;
  list-style-type:none;
  width:420px;
  bottom:0px;
}

#dm_block li{
  word-wrap:break-word;
}

JavaScript(jQuery)部分

$(function(){
  var initial_dm_block_height = 420;

  $(document).on('click','#dm_btn',function(e){
    e.stopPropagation();
    e.preventDefault();
    if($('#dm_text').val() === ''){ return false; }
    $('#dm_block').append('<li style="border:solid 1px blue;">' + $('#dm_text').val() + '</li>');
    if($('#dm_block').height() > initial_dm_block_height){
      $('#dm_block').css('position','static');
      $('#dm_scroller').scrollTop($('#dm_block').height());
    }else{
      $('#dm_block').css('position','absolute');
    }
  });  

  if($('#dm_block').height() > initial_dm_block_height){
    $('#dm_block').css('position','static');
    $('#dm_scroller').scrollTop($('#dm_block').height());
  }else{
    $('#dm_block').css('position','absolute');
  }
});


■HTML部分

<div id="dm_scroller">
  <ol id="dm_block">
    <li>メッセージ1</li>
    <li>メッセージ2</li>
    <li>メッセージ3</li>
    <li>メッセージ4</li>
  </ol>
</div>
<input type="text" id="dm_text">
<input type="button" id="dm_btn">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


■参考リンク
JavaScriptでスクロールを最下部に移動する方法 - Qiita

inline-blockの要素を右寄せする方法

幅をwidthで最初から指定せずに、文字列の長さに応じて動的に変更したい場合は、

display:inline-block;

を追加すればいいんですが、これだと、

margin-left:auto;
margin-right:0px;

のような形で右寄せすることができません。今回はその解決メモです。
解決方法は、大枠のブロックで何重か囲って、

margin-left:auto;
margin-right0px;

を複数回繰り返すことです。

以下、具体的なコード。

CSS部分

body{
 margin:0;
 padding:0;
}

.frame{
  width:100%;
  height:auto;
  border:solid 1px #000;
}

.right-justification-outer-block{
  display:flex;
  border: solid 1px #0000FF;
  max-width:350px;
  margin-left:auto;
  margin-right:0px;
}

.right-justification-inner-block{
  display:flex;
  flex-direction:row;
  align-items:center;
  margin-left:auto;
  margin-right:0px;
}

.text-parts{
  border-radius:20px;
  padding:8px 12px;
  background-color:#ffc0cb;
  max-width:300px;
  display:inline-block;
  word-wrap:break-word;
}

.icon-parts{
  margin-left:10px;
}

.icon-circle{
  border:solid 1px #000;
  border-radius:50%;
  width:20px;
  height:20px;
}

■HTML部分

<div class="frame">
  <div class="right-justification-outer-block">
    <div class="right-justification-inner-block">
       <div class="text-parts">
         ここにテキストが入ります
       </div>
       <div class="icon-parts">
         <div class="icon-circle"></div>
       </div>
    </div>
  </div>
</div>

ポイントは、right-justification-outer-blockのdisplay:flex;部分で、これがない場合、text-partsの幅が300pxを切っている場合、左に寄ってしまい、ある場合は、きちんと右寄せのままになります。


■参考リンク
css – ブロックレベルから文字がはみ出してしまう – memorandum-plus
CSSで文字の長さにwidthを合わせてbacgroundやborderを表示させるやり方

PHPでis_numericを使用する際に注意点

開発環境は、Windows10 Pro(64bit) + PHP 7.2.12。

is_numericだと、文字列化された数値はtrue扱い。
逆に、文字列などをintval、(int)した場合、0が返り、is_numericではtrue扱いになる。
だから、数値の文字列は変換しないでそのまま渡した方が良い。
以下のコードを書いておきます。

■数値の文字列化のケース
※結果は"数値です"と表示される

<?php
$a = "123";
if(is_numeric($a)){
  echo "数値です";
}else{
  echo "数値ではありません";
}
?>

■文字列の数値化のケース
※結果は"数値です"と表示される

<?php
$a = (int)"abc"; // $aには0が入る
if(is_numeric($a)){
  echo "数値です";
}else{
  echo "数値ではありません";
}
?>

JavaScriptでオブジェクトのコピーをする際の注意点

開発環境は、Windows10 64bit(Pro) + Firefox 69.0(64bit)。

書くのが楽だからといってオブジェクトを直接入れてしまった場合、obj_aの値を書き換えると、obj_bの値も変わってしまう。これは、シャローコピーと言って、単に参照を渡しているだけだから、そうなってしまう。例えば、以下のようなコード。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: "baseball" };
obj_a = obj_b;
obj_a.age = 30;
console.log(obj_b.age); // 30

対策としては、調べたところ、2つの方法があったので、メモする。
1つは、Object.assignを使う方法。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: "baseball" };
obj_a = Object.assign({}, obj_b);
obj_a.age = 30;
console.log(obj_b.age); // 13

もう1つは、Object.createを使う方法。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: "baseball" };
obj_a = Object.create(obj_b);
obj_a.age = 30;
console.log(obj_b.age); // 13

自分が作っている際にきちんとコピーできた際は、Object.assignだったらうまくいったのに、Object.createだと、なぜかうまくいきませんでした。
基本的には、Object.assignの方を使った方が良いみたいです。


ただ、これらの方法にも、問題があって、オブジェクト内にオブジェクトがあった場合は、参照を渡すことになってしまう。例えば、以下のようなコード。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: {sports: "baseball"} };
obj_a = Object.assign({},obj_b);
obj_a.favorite.sports = "golf";
console.log(obj_b.favorite.sports); // golf
let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: {sports: "baseball"} };
obj_a = Object.create(obj_b);
obj_a.favorite.sports = "golf";
console.log(obj_b.favorite.sports); // golf


もし、オブジェクト内のオブジェクトも、コピーする場合は、オブジェクト内の値を、1つ1つ再帰処理で抜き出して、新しく作ったオブジェクトに入れていき、そのオブジェクトを最終的に返すような関数を作らないといけない。一応、書けるところまで書いてみました。階層があまりに深くて、再帰処理を何度も呼び出すわけじゃない限り、これで大丈夫と思うけど、何かエラーがあったら報告お願いします。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: {sports: "baseball"} };

createNewObject = (obj) => {
  let result = {};
  if(typeof obj === 'object' && !Array.isArray(obj)){ 
    extract = (no, o) => {
      for(let item in o){
        if(typeof o[item] === 'object' && !Array.isArray(o[item])){
          no[item] = {};
          extract(no[item], o[item]);
        }else if(typeof o[item] === 'object' && Array.isArray(o[item])){
          no[item] = [];
          extract(no[item], o[item]);
        }else{
          no[item] = o[item];
        }
      }
    }
    extract(result, obj);
  }else{
    return false;
  }
  
  return result;
};

obj_a = createNewObject(obj_b);
obj_a.favorite.sports = "golf";
console.log(obj_b.favorite.sports); // baseball

方法を調べると、もっと簡単に、できる方法がありました。
JSON.stringifyを用いた後に、JSON.parseを用いれば良いみたいです。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: {sports: "baseball"} };
obj_a = Object.assign({},JSON.parse(JSON.stringify(obj_b)));
obj_a.favorite.sports = "golf";
console.log(obj_b.favorite.sports); // baseball

■参考リンク
JavaScriptで連想配列(オブジェクト)のコピーをする正しい方法 | PisukeCode - Web開発まとめ
オブジェクトの値をコピーするObject.assign() - 30歳からのプログラミング
Object.create() - JavaScript | MDN
Object.assign() - JavaScript | MDN
Object.assign()を使ったコピーいろいろ - Qiita
【JavaScript】「Object.assign vs Object.create」のおもしろい議論みつけた | 武骨日記
javascript – Object.create()とObject.assign()を使ってオブジェクトを作成することの違いは何ですか? - コードログ

Reactで詰まったポイントの解決メモ

開発環境は、Windows10 64bit(Pro) + react@16.8.6。


■ライフサイクルメソッドについて
・stateが変更されるたびに、componentWillUpdate,componentDidUpdateメソッドがそれぞれ呼び出される。


■componentWillMountを使用すると、querySelectorAllでは、まだ要素は読み込めない。componentDidMonutでようやく読み込める。
・イベントは、addEventListenerで追加するのではなくて、タグにonClickやonFocusなど書いて、その中にコールバック関数を指定する方法が一般的か。


■ルーティングで、ログインページとユーザーの個別ページの場合、LinkコンポーネントをSwitchコンポーネントで囲っておいて、ユーザーの個別ページを一番最後に設置しておかないと、BrowserRouterコンポーネントでは個別ページをログインページと認識して両方表示されてしまう。

<Switch>
  <Link to="/login" render={Login} />
  <Link to="/:username" render={UserPage} />
</Switch>

Switchコンポーネントだと、一番最初にマッチしたものだけを表示してくれる。


参考リンク

React入門



■LinkコンポーネントでonClickメソッド属性にメソッドを指定していることで無限ループしてしまう際の対処法

例えば、バックエンド側からデータを引っ張ってくるメソッドをreadPageとして、下記のように記述すると、

<Link onClick={this.readPage('/')} to="/" />

無限ループしてしまう。(無限に指定ページを読み込もうとする)
括弧をつけて、記述しているから起動している?
ただ、

<Link onClick={() => { this.readPage('/')} } to="/" />

という風にアロー関数で指定すると大丈夫。

メソッドを最初からアロー関数で定義していたらどうなのだろうか?



■子コンポーネントから親コンポーネントのstateを変更する

stateを変更するメソッドは、コンストラクタでバインドしておくこと。
そうしないと、変更できない。

コンポーネント

constructor(props){
  super(props);
  this.state = {
    isClicked: false
  };
  this.clickStateChanger = this.clickStateChanger.bind(this);
}

clickStateChanger(){
  let current_state = this.state.isClicked;
  this.setState({isClicked: !current_state});
  console.log("current_state: " + !current_state);
}

<Child clickStateChanger={this.clickStateChanger} />

コンポーネント

<button onClick={this.props.clickStateChanger} >click me</button>

参考リンク
[react]子のコンポーネントから親のstateを変更する方法 - Qiita
コンポーネントに関数を渡す – React



■親コンポーネントから子コンポーネントへメソッドを渡して使用する際の注意点
コンポーネントからメソッドを受け取って、子コンポーネントのbuttonのonClick属性などに設定して、呼び出すパターンです。

コンポーネント

handleAlert(){
  console.log("handleAlert");
}

<Child handleAlert={this.handleAlert} />
/*
ここで、
<Child handleAlert={() => { this.handleAlert} } />
のようにアロー関数で渡すとhandleAlertが動作しないので注意。
*/

コンポーネント

<button onClick={this.props.handleAlert}>click me</button>

これだと、問題なく呼び出せますが、例えば、子コンポーネント側で、メソッドを用意して、その中で、親コンポーネントのメソッドを呼び出したい場合は、子コンポーネント側を以下のようにすると、wrap()メソッド内の、thisが、親コンポーネントを指していなくて、TypeError: this is undefinedとなる。

コンポーネント

wrap(){
  this.props.handleAlert();
}

<button onClick={this.wrap}>click me</button>

そこで、wrap()メソッドをアロー関数として呼び出すと問題なく動作する。

コンポーネント

wrap = () => {
  this.props.handleAlert();
}

<button onClick={this.wrap}>click me</button>



■子コンポーネントへメソッドを渡す際に、引数を指定できるようにする

コンポーネント

import React from 'react';
import Child from './Child';

class Parent extends React.Component{
  testMethod(name){
    console.log("Hello, " + name + "!");
  }

  render(){
    return(
    <Child testMethod={(name) => this.testMethod(name)} />
    );
  }
}

コンポーネント

<button onClick={() => this.props.testMethod("k01ken")}>click me</button>

export default Child;

参考リンク
React子コンポーネントから親の引数付きメソッドを実行する - Qiita


■JSX文法内部で条件分岐させる
JSX内にて、stateの状態によって、CSSのclass属性を切り替えたい場合は、

<div className={this.state.isClicked === true ? "isClicked" : "notClicked"}></div>

のように書いていきます。

他にも、一部の文字列は共通だが、残りの文字列は状態に応じて切り替えたい場合は、状態に応じて切り替わる方を()で括っておくこと。

例えば、アイコン画像がアップロードされていれば、アイコン画像を、なければ、デフォルトのアイコン画像を表示させたい場合は、

<img src={"/img/user_icon/" + (this.state.icon_image !== null ? "useruploadicon.jpg" : "defaulticon.jpg")} />

としておく。

もっと細かくネストしたい場合は、三項演算子をネストします。

<div>
{
  this.state.isClicked === true ?
    this.state.isChecked === true ?
      クリックして、かつ、チェックしています
    :
      クリックしましたが、チェックはしてません
  :
    クリックしてません
}
</div>

参考リンク
React.jsの知っておいて損はないTips - Qiita


■class属性をタグ内に書く場合は、classNameと書くこと

<div class="test"></div>

じゃなくて、

<div className="test"></div>

という風にする。


■this.State内の配列の一部を変更する方法

constructor(props){
  super(props);
  this.state = {
    frults: ["apple", "orange", "grape"]
  };
}

fruits_change(){
  var copy_fruits_array = this.state.fruits.slice();
  copy_fruits_array[0] = 'banana';
  this.setState({fruits: copy_fruits_array });
} 

参考リンク
ReactのsetStateで配列の一部を変更する - Qiita


■this.state内にデータを入れると自動的にサニタイズしてくれる
例えば、チャット機能など作る際に、ユーザーが、

<script>alert("hello");</script>

などのプログラムを書くと、プログラムが実行してしまいhelloと書かれたalertが表示され、セキュリティ的に危険なので、普通だったら、HTMLサニタイズするプログラムを書かなくてはならないが、Reactの場合は、this.stateに入れた時点で自動的に行ってくれるようだ。

Reactで親コンポーネントから子コンポーネントへstateを渡す

コンポーネントのstateの状態に応じて、子コンポーネントの状態を切り替えたいと思います。
今回はサンプルプログラムとして、clickボタンを押すたびに、ONとOFFの表示が交互に切り替わるプログラムを書いてみたいと思います。

コンポーネントのstateの状態が変化するたびに、子コンポーネントの内容も切り替わっています。

コンポーネント

import React from 'react';
import {render} from 'react-dom';
import Child from './Child';

class Parent extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      flug: false;
    };
  }

  handleClick(){
    let state = this.state.flug;
    this.setState({flug: !state});
  }

  render(){
    return(
    <div>
      <Child isClicked={this.state.flug} />
      <button onClick={() => this.handleClick} >click</button>
    </div>
    );
  }
}

render(
<Parent />,
document.getElementById("root")
);


コンポーネント

import React from 'react';

class Child extends React.Component{
  constructor(props){
    super(props);
  }

  render(){
    if(this.props.isClicked === true){
       return(<div>ON</div>);
    }else{
      return(<div>OFF</div>);
    }
  }
}

export default Child;


参考リンク
Reactで親から子へstate/メソッドをpropsで渡す時の考え方 - shibe23のはてなブログ
[react]子のコンポーネントから親のstateを変更する方法 - Qiita
逆に子コンポーネントから親コンポーネントのstateを変更させる方法について書かれています。