20200731のJavaScriptに関する記事は18件です。

自分オリジナルのGitHubプロフィールを作る

はじめに

最近GithubのプロフィールにREADMEを置けるようになりました。GitHub Readme Stats を利用してGitHubプロフィールをカッコよくするのように素晴らしい物も登場しているのですが、私は人と被ったりするのがあまり好きではないので自分で作ってみようと思います。

完成品

インパクト絶大ですね
scroll face

ちなみにリポジトリは以下になります。

構成

.
├── README.md
├── index.js
├── package.json
├── vercel.json
├── views
│   └── scroll-face.html
└── yarn.lock

まずviewsディレクトリの中に配信したいsvgを入れます。
基本的にはsvgにアニメーションなどを付けて配信していく形になります。

scroll-face.html
<svg>
  <style>
    ...
  </style>
  ...
</svg>

今回は特にbodyやheadなどはいらないと思います。
次にこれを配信するためのserverのコードを書いていきます。

index.js
const express = require('express');
const app = express();

app.use('/', express.static(__dirname + '/views'));
express.static.mime.define({ 'image/svg+xml': ['html'] });

app.get('/', (req, res) => {
  res.end('scroll-face');
});

app.listen(8080);

やっていることは単純でviewsディレクトリ以下のファイルを配信しています。
ここで肝になってくるところはここです。

express.static.mime.define({ 'image/svg+xml': ['html'] });

こうすることで実際にmarkdown上でsvgが表示されるようになります。

ここではとても簡単なことしかやっていませんがもっとカスタマイズしたい方はgithub-readme-statsを読むと色々と参考になると思います。

デプロイ

今回はvercelを使ってデプロイしていきます。
まずはプロジェクトのルートに以下のようなvercel.jsonを配置します。

vercel.json
{
  "builds": [
    {
      "src": "index.js",
      "use": "@now/node-server"
    }
  ],

  "routes": [
    {
      "src": "/.*",
      "dest": "/index.js"
    }
  ]
}

こうすることでindex.jsが実行されます。
次にvercelにアクセスしてgithubなどからimportします。
今回は特に設定をいじらずにDeployを押せば完了します。
ss

最後に

何か間違っているところなどがありましたらコメントお願いします。
特にvercelを初めて使ったので設定周りで不備があれば言っていただけると嬉しいです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DynamoDBのデータをnode.jsのReadableStreamに流す

DynamoDBテーブルのデータをざーっと ReadableStream に流し込みたかったが、どうやってバックプレッシャーに対応すればいいのか分からなかったので対応させてみた。

やりたいこと

  • DynamoDBからデータを読み込む(基本は内部バッファサイズ分)
  • ReadableStream にpush
  • ひたすら読み込んではpush
  • 詰まったら一時停止
  • 次回開始位置をpushしきれなかったアイテムに設定
  • またpushできるようになったら続きから読み込み再開
  • 以下繰り返し
  • 全て読み込んでpushし終わったら処理終了

DynamoDBのQuery/ScanとLimit

DynamoDBのQuery/Scan操作では Limit を指定することで取得件数を制限することができる。
ただ Limit は検索結果の件数制限でなので、QueryFilterScanFilter を掛けると検索結果がさらにフィルタリングされ指定した件数より少ない件数が返ってくる場合がある。

クエリーで検索し、指定件数分の結果が揃ったら、クエリーフィルターでさらに絞る、というのはDynamoDBに限らずよくあるのでおそらく同じ動作なんだと思う。

まぁこれは Limit を指定してもフィルターを指定すると思ったとおりの件数は返ってこないですよ、という注意事項。

DynamoDBのページネーション

DynamoDBのQuery/Scanは読み込みサイズが1MBを超えるとそこで帰ってくる。Limit を指定した場合も途中で帰ってくる。
まだ全てのデータを読み込み終えてない場合、次ページを取得するための開始地点がレスポンスに含まれる。
レスポンスに含まれる LastEvaluatedKey を次回Query/Scan時のリクエストに ExclusiveStartKey として含めれば次ページを取得できる仕組みになっている。

LastEvaluatedKey は実際にはプライマリーキー(パーティションキーあるいはパーティションキーとソートキーの組み合わせ)のオブジェクトである。
なので LastEvaluatedKey でなく、レスポンスに含まれるアイテムの何番目かをぶち込めば次回の読み込み開始位置はその項目の次からになる。なった。

これを利用して、ReadableStream にpushしている途中で ReadableStream の内部バッファが一杯になった場合は、入り切らなかった次のアイテムを読み込み開始位置として ExclusiveStartKey に設定することにした。

ReadableStreamのハイウォーターマーク

ReadableStream は内部バッファを持っていて、そのサイズ=閾値をハイウォーターマーク(HWM)という。
オブジェクトモードの場合はデフォルトで 16 に設定されている。
データを push し続けてHWMに達すると、

  • pushfalse を返す
  • _read が呼ばれなくなる

それで内部バッファに空きがでると再び _read が呼ばれるらしい。
また pushfalse を返しても内部バッファには詰め込まれてた気がする。たしか。HWMが上がるんだったかな・・・

ということで、Query/Scanで取得したデータをpushしてる最中に false を返した場合はそこで処理を中断し、次のアイテム以降は残念ながら次回のQuery/Scan時に再取得することにした。

諸々踏まえて

最終的にこんな感じの実装になった。
切り貼りしてたのでこのままでは動かないかも。雰囲気を感じ取って欲しい。

class DynamoDBStream extends stream.Readable {
    :
  _read(size) {
    // 何度も呼ばれるので
    if (!this.#querying) {
      this.#querying = true;
      this.#params.Limit = size || this.hwm;
      this._query(this.#params);
    }
  }
  // HWMに達するまで再帰的にQueryを実行する
  _query(params) {
    const recursiveQuery = (params) => {
      // AWS.DynamoDB.DocumentClient.query()
      this.#ddb.query(params, (err, data) => {
          :
        let abort = false;
        for (const item of queryResult.Items) {
          // HWMに達したらそのitemまで処理済みとし次回の開始位置を指定して中断
          if (!this.push(item)) {
            params.ExclusiveStartKey = item;
            abort = true;
            break;
          }
        }
        // 中断したら次の読み込み要求を待つ
        if (abort) {
          this.#querying = false;
        // 次ページがなかったら処理終了
        } else if (typeof queryResult.LastEvaluatedKey === "undefined") {
          this.push(null);
          this.#querying = false;
        // 中断せず、次ページがあれば続けて次ページを取得
        } else {
          params.ExclusiveStartKey = queryResult.LastEvaluatedKey;
          recursiveQuery(params);
        }
      });
    }
    recursiveQuery(params);
  }
    :
}

余談: DynamoDBのコスト

リクエスト単位で課金される。また、結果整合性のある読み込みが一番安い。
Limit を小さく指定しすぎるとリクエスト数が増えてしまい、ちゃりんちゃりん課金されてしまう。

かと言って、あんまり Limit を大きくしてしまうと、詰め込みきれなかったデータが無駄になってしまうのでうまく調整しないといけない。

頻繁にリクエストが細分化してしまう場合は、いっそのことStreamとは別にバッファを持ったほうがいいかもしれない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Contextのメモ

親コンポーネントのstateの値を、孫コンポーネントのpropsの値として使う場合はこのような書き方になる。

index.js
import React from "react";
import ReactDOM from "react-dom";

/* 親 -> 子 -> 孫 -> .. の順に値を渡すことをバケツリレーと呼ぶ(親のstateの値を孫やひ孫のpropsにする) */
class Grandchild extends React.Component {

    render() {
        return(
            <div>{this.props.text}</div>
        )
    }

}

class Child extends React.Component {

    render() {
        return(
            <Grandchild text={this.props.text} />
        )
    }

}

class Parent extends React.Component {

    constructor(props) {
        super(props);
        this.state = {"text": "まんち"};
    }

    render() {
        return(
            <div><Child text={this.state.text} /></div>
        )
    }

}

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

バケツリレーをせずに、親コンポーネントから直接孫やひ孫のコンポーネントに値を渡すのがContext(子コンポーネントにも渡せる)
関数コンポーネントの場合はpropsが存在しないのでこの書き方一択になる?

index.js
import React, {useState, createContext, useContext} from "react";
import ReactDOM from "react-dom";

var Context = createContext(); 

var GrandChild = function() {

    //受け取った値が文字列ならその文字列が、配列ならその配列がそのまま渡される
    var arr = useContext(Context); 
    return(
        <div>
            <p>{arr[0]}</p>
            <p>{arr[1]}</p>
        </div>
    )

}

var Parent = function() {

    var [cnt, addCnt] = useState(0);
    var [numState, switchNumState] = useState("偶数");

    function onClick(num) {
        num += 1;
        addCnt(num);
        if (num % 2 === 0) {
            numState = "偶数";
        } else {
            numState = "奇数";
        }
        switchNumState(numState);
    }

    return(
        <>
          <div>
            {/* providerコンポーネントの作成 value=渡す値 渡す値は文字数字などの単体の値やオブジェクトでも良い */}
            <Context.Provider value={[numState, cnt]}>
                <GrandChild />
            </Context.Provider>
            <input type="button" value="連打しろ!!" onClick={() => onClick(cnt)} />
          </div>
        </>
    )

}

ReactDOM.render(<Parent />, document.getElementById("app"));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebaseでデータの取得、追加、更新を行う

Firebaseでデータの読み込み、書き込み、更新を行う

目次

1.目的

2.データ操作の基本

3.データ取得

4.データ追加

5.データ更新

6.まとめ

1.目的

備忘録として
[前提条件]
・Firebaseの構築に関しては、もう終わっている。
(Firestoreの設定も含めて)

2.データ操作の基本

FirestoreにアクセスするためのAPIは既に用意されている。

データ操作の基本として、以下の構文が基本となる。
firebase.firestore() − ①
 .collection('[コレクション名]') − ②
 .where('[フィールド名]' ,'[比較演算子]' ,[検索する値]) − ③
 .orderBy('オーダーをかけるフィールド名' ,'[asc or desc]') − ④
 .limit([整数]) − ⑤

上記が基本構造となる。
必須項目は ①、②
任意項目は ③、④、⑤

また、whereはメソッドチェーンで繋げることができる。
この時、重要となるのが、「Firebaseは'AND'条件のみサポートしている」という点である。
現在は、'OR'条件は利用できない。

また、利用できる比較演算子は以下の通り。
・'=='
・'>'
・'>='
・'<'
・'<='
・'in'
・'array-contains'
・'arrat-contains-any'

上記の中で説明が必要なのは、以下だと思う。
・'array-contains'
・'arrat-contains-any'

'array-contains' は、配列形式で持っているデータの中から、完全一致する文言を探す。
 こんな書き方
  .where('[コレクション名]' ,'array-contains' ,'りんご')

'arrat-contains-any' は上記の拡張版で配列形式で持っているデータの中から、完全一致する文言を複数個探す。
 こんな書き方
  .where('[コレクション名]' ,'array-contains-any' ,['りんご', 'みかん'])

3.データ取得

データ操作の基本を踏まえ、取得は以下の様な記載方法となる。
firebase.firestore()
 .collection('[コレクション名]')
 .where('[フィールド名]' ,'[比較演算子]' ,[検索する値])
 .orderBy('オーダーをかけるフィールド名' ,'[asc or desc]')
 .limit([整数])
 .get();

記載しているのはjsなので、結果を待たない。
上記メソッドはpromise関数なので、「then()」を使うなり、「async await」を使うなりして取得する。

4.データ追加

firebase.firestore()
 .collection('[コレクション名]')
 .add(
  {
   'name':'フルーツ',
   'number':1,
   available:true,
   'fruit':[
    'りんご',
    'みかん'
   ],
   'creater':[{
    '生産者':'AA農場',
    '電話番号':'080-0000-0000'
   }],
   [{
    '生産者':'BB農場',
    '電話番号':'090-1111-1111'
   }]
  }
 );

こんな感じ。
色々な型を使える。
[]は配列indexが自動でインクリメントされて生成される。
{}はkeyとvalueを指定できる。

例えば「'creater'」は以下の様に保存される。
[0] => 生産者 => 'AA牧場'
   電話番号 => '080-0000-0000'
[1] => 生産者 => 'BB牧場'
   電話番号 => '090-1111-1111'

ドキュメントIDは、何も指定しなければ自動生成される。
ドキュメントIDとは、そのデータを表す一意の特定を行うために作成されるID。

実は追加のメソッドは「add()」だけでなく「set()」も使えるが、多分使わない方が無難。
この辺は説明すると長くなるので、一旦省略。

もしも、ドキュメントIDを指定したい場合は、set()を利用する必要があるが、その場合は・・・いずれ書くかもしれません。

5.データ更新

firebase.firestore()
 .collection('[コレクション名]')
 .doc(ドキュメントID)
 .update(
  {
   available:false,
  }
 );

上記で追加したデータを更新すると考えたときに、他のフィールドは変更なく、「available」フィールドのみ更新される。

更新でも「set()」メソッドを使えるが、使わない方が無難。理由は(ry

6.まとめ

まずは基本を覚えて、
.get()
.add()
.update()

を覚えれば、基本的にはfirestoreを使える。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jsonをcsv(カンマ区切り、項目行あり)に変換する(構文破壊に対応)

以下の記事にあるJSON変換用関数、すごくわかりやすくて重宝しているのですが、
文字列をダブルクオーテーションで囲んでないので、
「あいうえお"」や「あい,うえお」のような、構文破壊系の文言が来ると死にます。

https://qiita.com/_shimizu/items/38e6d75afcacec25eb7e

ので、見出し行以外を全てダブルクォーテーションで囲んでカンマによる構文破壊を防ぎ、
ダブルクォーテーション一つだけの文字列は二つにすることで、そちらの構文破壊も防ぐようにします。

おまけで、見出し行を出力するかどうかを選択できるようにしました

実装

/**
 * Convert json to csv
 * Supports syntactic destruction
 *
 * @params array-object records
 * @params boolean isHeaderDraw [default true]
 * @return string
 * @link https://qiita.com/_shimizu/items/38e6d75afcacec25eb7e
 * @link https://qiita.com/qwe001/items/60f6cb264110490d7d90
 */
function json2csv(records, isHeaderDraw)
{
  var isHeaderDraw = isHeaderDraw !== undefined && isHeaderDraw === false ? false : true;

  if(! records || records.length === 0){
    return "";
  }

  var header = Object.keys(records[0]).join(',') + "\n";

  var body = records.map(record => {
    record = Object.keys(record).map(key => {
      var field = record[key];
      if (field) {
        field = field.replace(/\"/g, "\"\"");
        //field = field.replace(/\r?\n/g, " "); // 改行を無効にしたい場合
      }
      return field;
    }).join("\",\"");

    return "\"" + record + "\"";
  }).join("\n");

  return isHeaderDraw ? header + body : body;
}

使い方

var a = [
    {id:1, name:"test", address:"tokyo"},
    {id:2, name:"hoge", address:"構文破壊1\""},
    {id:3, name:"hello", address:"構文,破壊2"},
    {id:4, name:"world", address:"saitama"}
];

json2csv(a);

//output
id,name,address
"1","test","tokyo"
"2","hoge","構文破壊1"""
"3","hello","構文,破壊2"
"4","world","saitama"

// 見出し行が要らへんとき
json2csv(a, false);

//output
"1","test","tokyo"
"2","hoge","構文破壊1"""
"3","hello","構文,破壊2"
"4","world","saitama"

バグなどあったら教えてくれると助かります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptを一から勉強する(JavaScriptの基本事項編)

一時期勉強していたJavascriptを1からざっくり勉強し直すための記事です.他人に教えることが3倍ぐらい勉強したことになるという理論がモチベーションです.
自分と同じようにJavascriptを一からざっくり勉強したい人にはおすすめですが,それ以外の人は閲覧注意(間違った解釈が多いかもしれないので).

https://www.javadrive.jp/javascript/ini/index1.html
↑を参考に勉強していきます

JavaScriptとは?

https://techacademy.jp/magazine/8801 から引用

JavaScriptはプログラミング言語であり、ユーザー側のWebブラウザと、Webサイトまたはウェブサービスの相互間のやりとりを、円滑にするために使われています。

実質的に、私たちがブラウジングで体験できることのすべては、ブラウザの中で使われているJavaScriptの処理によるものです。

Webサイトの外観のデザインや使い心地から、Netflixのサイトに表示される映画の題名のようなサイト内の情報表示に至るまで、JavaScriptが使われています。

つまりWeb系言語で,HTMLの拡張機能的な何かで,UXに重要なプログラミング言語ってことかなるほど.

スクリプトの記述

  1. 大文字小文字は区別する(Num=num)
  2. スクリプト内の空白と改行は無視される(var n= 0とvar num=0は同じ)
  3. 文の区切りにはセミコロンを付ける(var num=0;)

基本的にはProcessingと一緒やな,大文字小文字の区別はコンピュータ君頭いいなと思えばいいし,改行や空白はめんどくさがりには大変うれしくてありがとうございましたやし,セミコロンは日本語の句読点と同じ役割やし簡単やな,次.

スクリプトが実行されるタイミング

Script要素が表れるとHTMLの記述の中にあろうが実行される

<p>HTML文の解析中</p>
<p>この次にスクリプトが実行される</p>

<script type="text/javascript"</p>
document.write("<p>スクリプトが出力</p>");
</script</p>

<p>スクリプトが終了</p>
<p>HTML文の解析を続行</p> 

なるほど(なるほど),実行したい順番にコードを書かないといけないことね

分散して記述されたスクリプトの扱い

分散してても1つのスクリプトに記述されたように実行される

 <p>XHTMLの文1</p>

<script type="text/javascript">
var num = 10;
</script>

<p>XHTMLの文2</p>

<script type="text/javascript">
num = num * 10;
</script>

<p>XHTMLの文3</p>

<script type="text/javascript">
document.write("<p>結果は" + num + "です</p>");
</script> 

だとしても

<script type="text/javascript">
var num = 10;
num = num * 10;
document.write("<p>結果は" + num + "です</p>");
</script>

のようなスクリプトとして実行されるらしい.分散されてても変数はそのファイル内でちゃんと共有されてるのよって感じですね

特殊な文字の入力(エスケープシーケンス)

入力できない文字を他の文字の組み合わせで表現したものをエスケープシーケンスといいます。
(¥b=改行,¥'=シングルクオーテーションなど)

'It's a pen'だと意味の違うシングルクオーテーションが文の中にもあってバグるけど'It¥'s a pen'にするとバグらなくなるらしい.時々必要になる秘密のコマンドみたいなもんだね.

ということで今日はここまで.初心者が作ったノートは果たして本当に初心者に分かりやすいものなのかは神のみそしる.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kintoneで性別判定をしてみよう

はじめに

  • ES2015以降の構文を含むため、Chromeでのみ動作確認をしています
  • サンプルコードのため実際の使用は自己責任でお願いします?‍♂️

作るもの

  • 名前を元に性別を自動判定する

タイトルなし.gif

フォーム構成

スクリーンショット 2020-07-31 17.31.51.png

フィールド名(フィールドコード) 種別
姓(姓) 文字列(1行)
名(名) 文字列(1行)
LastName(LastName) 文字列(1行)
FirstName(FirstName) 文字列(1行)
性別(性別) ドロップダウン

API

今回は GenderizeというAPIを使って性別判定を行います。
使用方法は簡単で、https://api.genderize.io/?name=●● と叩くと推定性別、パーセンテージが返ってきます。
ただし、日本語には対応していませんのでアルファベットで渡してあげる必要があります。
また、国の指定もできるので今回はcountry_id=JPというパラメータを付与します。
このパラメータが結構重要で、mika(みか)という日本名は概ね女性につけられますが、
英語圏だとmichaelの略名だったりする関係で、パラメータなしだと男性判定、パラメータありだと女性判定になります。
よって外国人も含む場合は国名などのフィールドを用意してパラメータを変化させることを要件に追加するのもありでしょう。

カスタマイズ

今回はレコード追加画面を保存したタイミングで性別判定を行い、ドロップダウンに性別を反映します。

gender.js
(() => {
  'use strict';
  kintone.events.on('app.record.create.submit', event => {
    const record = event.record;
    const firstName = record.FirstName.value;
    const apiUrl = `https://api.genderize.io/?name=${firstName}&country_id=JP`;
    return kintone.proxy(apiUrl, 'GET', {}, {}).then((args) => {
      const res = JSON.parse(args[0]);
      const gender = res.gender;
      record.性別.value = gender === 'male' ? '' : '';
      return event;
    });
  });
})();

留意点

  • Genderize APIは1日1000件までが無料範囲内です。
  • 実際の運用では、Possibilityに基づき、閾値を設定する必要がありそうです。
  • 今回のコードにはエラー処理を含めていません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js】Nuxt文法編:fetch

? この記事はWP専用です
https://wp.me/pc9NHC-vM

前置き

非同期通信に使えるfetchをご紹介?

APIからデータを取得する方法は2つ!
fetch
asyncData

2つの違いについても解説しました??‍?

⬇️asyncDataの記事はこちら
https://wp.me/pc9NHC-ut

asyncDataとの違い

値のセット先が違う

asyncData:直接コンポーネントにセット
fetch:Vuexのstoreに格納?

呼び出されるタイミングが違う

asyncData:インスタンス作成前
fetch:インスタンス作成後

The Nuxt.js fetch hook is called after the component instance is created on the server-side: this is available inside it.

https://ja.nuxtjs.org/guides/features/data-fetching/

【翻訳】
Nuxt.jsフェッチフックは、
サーバー側でコンポーネントインスタンスが
作成された後に呼び出されます。
これは、その内部で使用できます。

違いはこれくらいです!
他はほとんど変わりません?

基本的にはasyncDataと同じ

使い方・書き方

asyncDataとほとんど一緒です?
Promiseを返すか
async/awaitを使うかです?
詳しい書き方はasyncDataの記事をご覧ください?

thisが使えない

thisを通してコンポーネントのインスタンスに
アクセスすることができません?

使用箇所

pages内コンポーネントのみ使用可能

fetch

fetchとは

? 続きはWPでご覧ください?
https://wp.me/pc9NHC-vM

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SharePoint】JSリンクのフィールド名(内部名・プロパティ名)がわかりづらい

SharePointのJSリンクの改修で、フィールド名がわかりづらく結構手間取ったのでそれについてまとめ

経緯

SharePointにJSリンクというのがあり、Excelでいう条件付き書式を実現するのに使われている(らしい)。

今回対応したのはSharePoint製社内イントラのリストで共通で使っていたJSリンクが、特定のリストで使えないというイレギュラー。原因を調べたところ、そのリストについてはフィールドの内部名が他と異なっていたことが判明。このイレギュラーなリストに対応するために既存のJSリンクのフィールド名のみ修正した差分を作成することになった。

わかりづらい点1: 表示上のフィールド名とJSで取得する内部名が違う

今回の例では表示上のフィールド名は投稿日
表示上のフィールド名とJSで取得する内部名が違う、具体的には問題のフィールドは表示上は
大本の原因は以下の2つ

  1. フィールドの内部名は作成時の作成時のフィールド名をもとに決定され、以降フィールド名を変えても内部名は変わらない
  2. 全角文字は文字化けしていてそのままでは何のフィールドなのかわからない

厄介なのは後者で、ただでさえ読めない置き換えられた文字が、場合によってはさらにパーセントエンコーディングされていたりもするという…。どうせ置き換えるならパーセントエンコーディングいらない文字列にしてくれればいいのに…。

内部名の取得

列の内部名はというと、列の編集画面のアドレスから見つかる
(詳細はSharePoint 2013 で列の内部名を調べる方法 | idea.toString();を参照)
今回の問題のフィールドの場合、アドレス
http://example.com/(中略)&Field=%5Fx767b%5F%5Fx6821%5F%5Fx65e5%5Fとなっていたので、
Field=に続く%5Fx767b%5F%5Fx6821%5F%5Fx65e5%5Fが内部名になる。

フィールド名が半角文字なら内部名も文字化けせずフィールド名そのままなのでこれで話は終わりになるところだが、今回は見ての通り全角文字なので案の定文字化けている。

パーセントエンコーディングのデコード(必要なら)

今思えば今どきのブラウザなら表示の際にはパーセントエンコーディングをデコードしてくれている気もするのだが、今回1のように何かの拍子でパーセントエンコーディングされることがあるので、その場合デコードする必要がある。

const str = `%5Fx767b%5F%5Fx6821%5F%5Fx65e5%5F`;
const internalname = decodeURI(str);
console.log(internalname); // "_x767b__x6821__x65e5_"が返ってくる

これをデコードすると_x767b__x6821__x65e5_という内部名が手に入る。

本来の列名の取得

JSリンクで扱う分にはこれで十分だが、やっぱりこれだと何の列だかわからなくてモヤモヤするので変換してみる。
平たく言うとUnicodeらしい。(詳細は日本語で作成した列の内部名から列名を調べる方法 - Qiitaを参照)

せっかくなのでUnicode(¥uxxx形式)を文字列へアンエスケープ | JavaScript逆引き | Webサイト制作支援 | ShanaBrian Websiteを参考に内部名の変換用に関数を作って試してみた。

function decodeInternalname(internalname) {
    const re = /_x([0-9a-f]{4})_/g;
    const arr = internalname.match(re);
    let result = '';
    for (str of arr) {
        result += String.fromCharCode(str.replace(re, '0x$1'));
    }
    return result;
};

// 問題のフィールド名を確認する
const internalname = '_x767b__x6821__x65e5_'; 
const result = decodeInternalname(internalname);
console.log(result); // "登校日"が返ってくる

どうやら投稿日というフィールドをつくるはずが登校日と変換間違いしたらしい。

そのまま表示されてたら気になるから結果的に文字化けしててよかったと思った。

わかりづらい点2: ピリオドを含むプロパティ名が存在する。

今回だけならともかく、同様のイレギュラーが今後も全くないとは言えない(実際もう1件あった)ので、差分の作成に先駆けて、簡単に列名を変えられるように列名を定数に切り分けるという改修をした。

具体的には仮にフィールド名がFieldNameだとすると、ctx.CurrentItem.FieldNameといったようになっているのを複数個所修正する必要があったので、これを

var FIELD_NAME = "FieldName";
ctx.CurrentItem[FIELD_NAME];

といった感じで列名を定数に切り出し、'FIELD_NAME'一か所だけ変えれば済むようにした。

問題

そこで問題となったのが以下のようなコード。

ctx.CurrentItem.FieldName.desc

このフィールド名を変数に置き換えようとすると、以下のようにしたくなるが、これはエラーになる

var FIELD_NAME = "FieldName";
(中略)
ctx.CurrentItem[FIELD_NAME].desc // 間違い

結論としては、FieldName.descはこれ全体で一つのプロパティ名であって、FieldNameオブジェクトのプロパティdescではない
そのため正しくは以下のようにする必要があった。

var FIELD_NAME = "FieldName";
(中略)
ctx.CurrentItem[fieldName + ".desc"]

詳細

SharePointのリストは1フィールドが複数の値を持っていることがある。

具体的な例としてはハイパーリンク型のフィールドで、これはURLと説明文の2つの値を持っている。
この場合のプロパティ名はURLがFieldName、説明文がFieldName.descになっているらしい。

GASのようなクラスの詳細がわかるリファレンスがほしいのだけれど見つからなかったので、Chromeのコンソールで調べたところ以下のように確認できた。

console.log(location.pathname) //リストを開いていることを確認。
//"/path/to/list/AnyView.aspx"
console.log(GetCurrentCtx().ListData.Row[0]) // リストの1行目を取得
//{(中略), FieldName: "https://example.com", FieldName.desc: "リンクの説明", (中略)}

その他参考サイト

リストおよびライブラリの列の種類とオプション - SharePoint
decodeURI() - JavaScript | MDN
String.fromCharCode() - JavaScript | MDN


  1. この時はブラウザで確認せず、アドレスをその場でコピぺして作業をしたのでパーセントエンコーディングされてしまったらしい。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ウェブサイト作成用備忘録・1号:background-image の疑似アニメーション制御その1

日々の学習のアウトプットの為、自主学習の際に工夫した内容を記録していきます。

今回は background-image プロパティの疑似アニメーション制御について

background-image プロパティは transition プロパティや animation プロパティ等のアニメーション制御には対応していません。

そこで、自分なりに試行錯誤することで、疑似的なアニメーション制御に成功したので、その方法などを記録していきます。

方法1:JavaScript の動的制御で画面を暗転させ、background-image を変更してから setTimeout() メソッドで画面の暗転を解除する

記述例

HTML

<html>
  <body id="background" class="none">
    <div id="hide_screen" class=""></div>
    <button id="change_screen" class="" type="button">背景変更</button>
  </body>
</html>

CSS

#hide_screen {
  min-height: 100%;
  min-width: 100%;
  background-color: #000;
  position: fixed;
  z-index: 2
  transition: all .5s;
  -webkit-transition: all .5s;
}

#hide_screen.none {
  opacity: 0;
  pointer-events: none;
}

#background {
  background-image: url(背景画像1)
}

#background.change {
  background-image: url(背景画像2)
}

javascript

jQuery(document).ready(function(){

$("#change_screen").click(function(){
  $("#hide_screen").removeClass("none");
  setTimeout(function(){
    $("#background").toggleClass("change");  
  },100);
  setTimeout(function(){
    $("#hide_screen").addClass("none");
  },200);
});

解説

1・背景色を黒に設定した空の div タグで画面全体を覆い隠す。

2・予めクラス none を設定し、透過処理の初期設定を行う。

3・背景変更ボタンをクリックすると、none クラスが解除され、画面全体が暗転する。

4・次に、setTimeout() で、最初に背景変更ボタンを押してから1秒後に toggleClass メソッドで body タグの change クラスを切り替え、画面暗転後に背景画像を変更する。

5・最後に setTimeout() で最初に背景変更ボタンを押してから2秒後に再び none クラスが追加され、画面の暗転が解除される。

今回はこれで以上になります。

あくまで自分用の備忘録ですが、他の方の参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React]React コンポーネントでJSXが複数行になる場合になぜ()で囲むのか?

はじめに

今更ですが、みなさんReactコンポーネントを作る際に「なんで、JSXが複数行になる場合にのみ()で囲まないといけないのか?:thinking:」と疑問に思ったことはないでしょうか?

僕は夜も眠れないくらい気になったので、ちょっと調べてみました。

↓こんな場合の()です!

hello.js
import React from 'react';
const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

結論

最初に結論からいうと、ReactでJSXが複数行になる場合には()で囲んだ方が良いです!

主な理由

  • 可読性向上のため
  • フォーマットを揃えるため

これだけだとまだ眠れないと思うので、調査した内容をまとめます。

まず、あの()は何であるか

グループ化演算子の()です。

みなさんもよく(1 + (2 * 3))などで利用する評価の優先順位を制御する演算子ですね!
ちょっと形式が変わるだけで人間すぐに混乱してしまいます。

この()で囲うことで、複数行のJSXも1つの式(Expression)として解釈されるようにしているんですね!

参考: MDN グループ化演算子
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Grouping

React公式ドキュメントの見解

読みやすさのため JSX を複数行に分けています。必須ではありませんが、複数行に分割する場合には、自動セミコロン挿入の落とし穴にはまらないように括弧で囲むことをおすすめします

可読性の向上と自動で文末にコロンが挿入されるのを防ぐために括弧で囲むのをオススメしてます。

参考:JSX の導入
https://ja.reactjs.org/docs/introducing-jsx.html

実際に()で囲わないとどうなるのか試してみる

では、実際に()で囲わないとどうなるのか試してみましょう。
今回は、下記のReact公式からリンクされているCodepenのコードを利用して色々試してみました。

参考:CodePen
https://ja.reactjs.org/redirect-to-codepen/components-and-props/rendering-a-component

例1: JSXを変数宣言時に代入

hello.js
var hello = 
<div>
  <h1>Hello</h1>
</div>;

const element = <Hello name="Sara" />;
ReactDOM.render(element, document.getElementById('root'));

=> 問題なく動作する。

例2: JSXをアロー関数でリターン

hello.js
const Hello = (props) => 
  <div>
    <h1> Hello, {props.name} </h1>
  </div>

const element = <Hello name="Sara" />;
ReactDOM.render(element, document.getElementById('root'));

=> 問題なく動作する。

例3(OKパターン): JSXをfunction内でリターン

hello.js
function Hello(props) {
  return  <div>
    <h1> Hello, {props.name} </h1>
  </div>
}

const element = <Hello name="Sara" />;
ReactDOM.render(element, document.getElementById('root'));

=> 問題なく動作する。

ただし、下記のようにreturnで改行された場合は当然ですが、returnで関数が終了してしまうので動作しません。

例3(NGパターン): JSXをfunction内でリターン

hello.js
function Hello(props) {
  return
  <div>  
    <h1> Hello, {props.name} </h1>
  </div>
}

const element = <Hello name="Sara" />;
ReactDOM.render(element, document.getElementById('root'));

このようにみていくとreturn文が登場するような

スタイルガイドではどうなっているか?

JavaScript Standard Styleでの推奨の書き方

JavaScript Standard Style で推奨の記載は下記です。
推奨以外の書き方以外はerrorになるようにeslintrc.jsonで定義されています。

eslintrc.json
"react/jsx-wrap-multilines": ["error", {
      "declaration": "parens-new-line",
      "assignment": "parens-new-line",
      "return": "parens-new-line",
      "arrow": "parens-new-line",
      "condition": "parens-new-line",
      "logical": "ignore",
      "prop": "ignore"
    }]

変数宣言時に代入

hello.js
var hello = (
  <div>
    <p>Hello</p>
  </div>
);

JSXをアロー関数でリターン

hello.js
var hello = () => (
  <div>
    <p>World</p>
  </div>
);

JSXをfunction内でリターン

hello.js
function hello() {
  return (
    <div>
      <p>Hello</p>
    </div>
  );
}

参考:
JavaScript Standard Styleでの方針
- https://github.com/standard/standard/issues/710
- https://github.com/standard/standard/commit/ccaf4390d9ae0829fdd31b2d69df143e9138e77d

EsLint React Pluginでの推奨の書き方

EsLint React PluginでもJSXが複数行になる場合には()で囲もうという方針ですね。
ただEsLint React Pluginではデフォルトでの設定がparensとなっている点が異なります。(JavaScript Standard Style では parens-new-lineとなっています。)

Prevent missing parentheses around multiline JSX (react/jsx-wrap-multilines)
Wrapping multiline JSX in parentheses can improve readability and/or convenience.

参考:jsx-wrap-multilines.md
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.md

デフォルト設定

default
{
  "declaration": "parens",
  "assignment": "parens",
  "return": "parens",
  "arrow": "parens",
  "condition": "ignore",
  "logical": "ignore",
  "prop": "ignore"
}

parensparens-new-lineの違い

どちらも()で囲おうというのは同じなのですが、parens-new-lineの方が少し厳しいです。

どちらもOK

hello.js
var hello = (
  <div>
    <p>Hello</p>
  </div>
);

どちらもNG

hello.js
var hello = <div>
  <p>Hello</p>
</div>;

parensではOKだが、parens-new-lineでNG

hello.js
var hello = (<div>
  <p>Hello</p>
</div>);

最後に

ReactでJSXが複数行になる場合には()をつけましょう!
夜、()に悩まずに眠れるようになった人が少しでも増えたなら、幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】CSVを作成し、ZIPに圧縮してダウンロード

はじめに

前回の記事【PHP】Excelで文字化けしないCSVファイル作成の続き?というより最終的にやりたかったことを備忘録として記事にします。

非同期通信で、DBからテーブルごとのCSVを作成し、ZIPに圧縮してダウンロードさせる!!!

ZIPに圧縮まではすんなりできたのですが(前回記事は多少躓きましたが…)、ダウンロードしたZIPファイルが開けない/解凍できない現象に悩まされました。

非同期をjQueryでやっていたんですが上手くいかず、XMLHttpRequestに変えたら成功しました。
何がダメだったんだろう。。。

コード

ルーティングとかは省きます。

クライアント側 JS
var request = new XMLHttpRequest();
request.open('GET', '/getZip', true);
request.responseType = "blob";
request.onload = function (oEvent) {
    var blob = request.response;
    var objectURL = window.URL.createObjectURL(blob);
    // リンクを作成し、JavaScriptからクリック
    var link = document.createElement("a");
    document.body.appendChild(link);
    link.href = objectURL;
    link.download = 'ZIPファイル名';
    link.click();
    document.body.removeChild(link);
};
request.send(null);
サーバ側 PHP
// ZipArchiveクラス初期化
$zip = new ZipArchive();
// Zipファイルパス
$zipFileNm = 'ZIPファイル名(拡張子付)';
$zipFilepath = 'ZIPファイルパス'.$zipFileNm;
// Zipファイルオープン
$result = $zip->open($zipFilepath, ZIPARCHIVE::CREATE);
if($result !== true){
  //ZIPファイル作成失敗時の処理
}

// CSV作成 前回記事関数
$csvFilePath = createCSV('CSVファイル名', 'データカラム', 'データ本体');
// CSVファイルをZIPに追加
$zip->addFile($csvFilePath, 'CSVファイル名');
// Zipファイルクローズ
$zip->close();

// HTTPヘッダを設定
mb_http_output( "pass" );
header("Pragma: public");
header('Content-Type: application/force-download;');
header('Content-Length: '.filesize($zipFilepath));
header("Content-Disposition: attachment; filename=$zipFileNm");
ob_end_clean();

// ファイル出力(ダウンロードさせる)
readfile($zipFilepath);

// Zipファイル削除
if(!unlink($zipFilepath)){
  //ZIPファイル削除失敗時の処理
}

// CSVファイルの削除
$delFileName = "CSVファイル保存パス/*.csv";
foreach(glob($delFileName) as $val){
  if(!unlink($val)){
    // CSVファイル削除失敗時の処理
  }
}

最後に

だらだら長いコードだと思います。
あと、これで本当に良いのかな~と疑問。処理は動くけども。

こうした方が良い等ありましたら、アドバイスください!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Typescriptを勉強しよう①~導入

Typescriptとは

TypescriptとはMicrosoft社が開発(2012年公開)したJavascriptの進化版言語である。
JavaScriptの欠点を補うために作られた言語で、Webアプリケーション開発、大規模開発での注目が高まっている。
Typescriptで書かれたコードはJavascriptにコンパイル(変換)され、コンパイルされたJavascriptが動作する仕組みである。

なぜTypescriptを使うのか

  • JavaScriptと同じ環境で動かすことが可能

    Typescriptはコンパイルし、JavaScriptに変換できるので、JavaScriptが動かせる環境なら、TypeScriptは動かせる。
    そのため、開発環境のハードルは低くい。

  • 型を宣言するためエラーを未然に防げる

    Typescriptは静的型システムである。
    型が指定できると、変数に違った型のデータが入り、プログラムが動作しないことがある。この変数にはこの型のデータが入ると、その変数に入るデータを予測しやすくなる。
    また、Editorによっては型が違うとエラー表示されるため、開発効率が上がる。

  • TypescriptはES5にコンパイルされる。

    Javascriptは毎年アップデートされているため、ES6ではブラウザで認識されず表示されないことがあります。ES5(2009年)では基本的にどのブラウザでも認識される。なので、Typescriptで書いたコードは基本どのブラウザでも表示される。

*補足*

静的型システム

Typescriptがコンパイルされる時に、型がチェックされる。変数の値がNumberなのか、Stringなのかそれ以外なのかすべてチェックされる。これが静的型システムがである。
型が違う場合(本来StringではないといけないところがNumberになっている)エラーが出力される。

開発環境を整えましょう

手順

  1. ターミナルを開く *私はWindowsに内蔵されているCMDを使用した
  2. Nodeがインストールされているか確認*node -vを実行すると確認できる
    スクリーンショット (213).png
    →インストールされていない場合 Nodeダウンロード

  3. ターミナル上で npm install -g typescript を実行し、Typescriptのインストールする

  4. Editarを開く*Typescriptのサポートが内蔵されているVscodeがオススメ

  5. ファイルを作製(拡張子はts)

  6. Typescriptを記述する。このように: string型を指定する
    let greeting: string = 'こんにちは';
    console.log(greeting);

  7. Typescriptをコンパイルする
    Editor内のターミナルを開く、Vscodeならメニューバーのターミナルの新しいターミナルを選択する

  8. ターミナル上で tsc ファイル名 を実行

実行前

実行前のindex.ts
スクリーンショット (219).png

実行後

実行後はindex.jsが生成されました。
スクリーンショット (222).png

問題発生

スクリーンショット (223).png
tsc : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Users\user\AppData\Roaming\npm\tsc.ps1 を読み込むことができません。
詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170) を参照してください

原因 

実行ポリシーが初期値(Restricted)なので、制限を解除する必要がある。

解決方法

手順

  1. スタートからWindows PowerShellを開く
  2. PowerShell上でPowerShell Get-ExecutionPolicyを実行すると、現在の設定を確認できる。 恐らく、Restrictedとなっているはず...確認出来たら
  3. Set-ExecutionPolicy RemoteSignedを実行すると、実行ポリシーの変更が表示される
  4. 実行ポリシーの変更 実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies のヘルプ トピック (https://go.microsoft.com/fwlink/?LinkID=135170) で説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか? [Y] はい(Y) [A] すべて続行(A) [N] いいえ(N) [L] すべて無視(L) [S] 中断(S) [?] ヘルプ (既定値は "N"): Y ###Yを入力する
  5. 再度、PowerShell Get-ExecutionPolicy で設定を確認するとRemoteSignedに変更されています

スクリーンショット (225).png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Typescriptを勉強しよう①~コンパイルしよう

Typescriptとは

TypescriptとはMicrosoft社が開発(2012年公開)したJavascriptの進化版言語である。
JavaScriptの欠点を補うために作られた言語で、Webアプリケーション開発、大規模開発での注目が高まっている。
Typescriptで書かれたコードはJavascriptにコンパイル(変換)され、コンパイルされたJavascriptが動作する仕組みである。

なぜTypescriptを使うのか

  • JavaScriptと同じ環境で動かすことが可能

    Typescriptはコンパイルし、JavaScriptに変換できるので、JavaScriptが動かせる環境なら、TypeScriptは動かせる。
    そのため、開発環境のハードルは低くい。

  • 型を宣言するためエラーを未然に防げる

    Typescriptは静的型システムである。
    型が指定できると、変数に違った型のデータが入り、プログラムが動作しないことがある。この変数にはこの型のデータが入ると、その変数に入るデータを予測しやすくなる。
    また、Editorによっては型が違うとエラー表示されるため、開発効率が上がる。

  • TypescriptはES5にコンパイルされる。

    Javascriptは毎年アップデートされているため、ES6ではブラウザで認識されず表示されないことがあります。ES5(2009年)では基本的にどのブラウザでも認識される。なので、Typescriptで書いたコードは基本どのブラウザでも表示される。

*補足*

静的型システム

Typescriptがコンパイルされる時に、型がチェックされる。変数の値がNumberなのか、Stringなのかそれ以外なのかすべてチェックされる。これが静的型システムがである。
型が違う場合(本来StringではないといけないところがNumberになっている)エラーが出力される。

開発環境を整えましょう

手順

  1. ターミナルを開く *私はWindowsに内蔵されているCMDを使用した
  2. Nodeがインストールされているか確認*node -vを実行すると確認できる
    スクリーンショット (213).png
    →インストールされていない場合 Nodeダウンロード

  3. ターミナル上で npm install -g typescript を実行し、Typescriptのインストールする

  4. Editarを開く*Typescriptのサポートが内蔵されているVscodeがオススメ

  5. ファイルを作製(拡張子はts)

  6. Typescriptを記述する。このように: string型を指定する
    let greeting: string = 'こんにちは';
    console.log(greeting);

  7. Typescriptをコンパイルする
    Editor内のターミナルを開く、Vscodeならメニューバーのターミナルの新しいターミナルを選択する

  8. ターミナル上で tsc ファイル名 を実行

実行前

実行前のindex.ts
スクリーンショット (219).png

実行後

実行後はindex.jsが生成されました。
スクリーンショット (222).png

問題発生

スクリーンショット (223).png
tsc : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Users\user\AppData\Roaming\npm\tsc.ps1 を読み込むことができません。
詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170) を参照してください

原因 

実行ポリシーが初期値(Restricted)なので、制限を解除する必要がある。

解決方法

手順

  1. スタートからWindows PowerShellを開く
  2. PowerShell上でPowerShell Get-ExecutionPolicyを実行すると、現在の設定を確認できる 恐らく、Restrictedとなっているはず...確認出来たら
  3. Set-ExecutionPolicy RemoteSignedを実行すると、実行ポリシーの変更が表示される
  4. 実行ポリシーの変更 実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies のヘルプ トピック (https://go.microsoft.com/fwlink/?LinkID=135170) で説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか? [Y] はい(Y) [A] すべて続行(A) [N] いいえ(N) [L] すべて無視(L) [S] 中断(S) [?] ヘルプ (既定値は "N"): Y ###Yを入力する
  5. 再度、PowerShell Get-ExecutionPolicy で設定を確認するとRemoteSignedに変更されています

スクリーンショット (225).png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript(jQuery)で画像をリサイズする方法(JS以外でも可)

画像をリサイズするJS以外でも使用可

画像をクリックしたら画像の縦横比を維持した状態で指定のサイズに画像をリサイズする

$('img')on.('click', function(){
  //画像のsrcを取得
  let src = $(this).attr('src');

  let width = 400;//任意の数値
  let height = 400;//任意の数値

  //元の画像サイズ
  let originalWidth = $(this).width();
  let originalHeight = $(this).height();

  //割合計算(小数点が出る場合はコンソールで注意が出る場合も。その場合はMath.ceil()などを使用して切り上げる)
  let rate = originalWidth / originalHeight;
  height = (width / rate);
  if(rate < 1){
    height = width;
    width = width * rate;
  }
  //リサイズした画像をwidth、heightともに追加
  $('body').append(`<div><img src="${src}" width="${width}" height="${height}"></div>`);

});

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TrelloみたいなカンバンUIを作りたいので、Javascriptのドラッグ&ドロップについて調べてみた

Trelloのようなタスク管理で、タスクをドラッグ&ドロップで移動する操作がありますよね。
今までドラッグ&ドロップの処理を書いたことがないので、調べてみました。

作ったサンプル

調べた結果、こんな感じのサンプルを実装してみました。
kanban_ui_sample.gif

APIについて調べる

以下、調べていく過程を記載します。
MDNによると、JavascriptのAPIがちゃんと用意されているようです。
https://developer.mozilla.org/ja/docs/Web/API/HTML_Drag_and_Drop_API

以下のAPIを使えば、考えているものが作れそうに思えました。
- ドラッグを開始した時はondragstart
- ドラッグしている項目が、ドロップ対象に入るとondragover
- ドロップした時はondrop
- ドラッグしている項目が、ドロップ対象から外れるとondragleave

ドラッグするデータは、ドラッグ開始時にdataTransferオブジェクトというのを使うらしい。
以下、MDNからサンプルコードを引用

function dragstart_handler(ev) {
  ev.dataTransfer.setData("text/plain", ev.target.innerText);
  ev.dataTransfer.setData("text/html", ev.target.outerHTML);
  ev.dataTransfer.setData("text/uri-list", ev.target.ownerDocument.location.href);
}

そして、ドロップする場所で、同じくdataTransferオブジェクトを受け取るらしい。
以下、MDNからサンプルコードを引用

function drop_handler(ev) {
 ev.preventDefault();
 var data = ev.dataTransfer.getData("text/plain");
 ev.target.appendChild(document.getElementById(data));
}

reactで実装

なんだ、意外とカンタンにできそうだと思ったので、reactでちょっとサンプルを実装してみます。
TaskListとTaskというコンポーネントを作って、登録したタスクが移動できるか試してみます。
ドラッグ&ドロップAPIの動作確認することが目的なので、つくりが雑なのは悪しからず。

タスク一覧を表示するTaskList

const TaskList = ({ status, title, tasks }) => {
  const { globalState, dispatch } = useContext(StateContext)

  const handleDragOver = (e) => {
    e.preventDefault()
    if (e.dataTransfer) {
      console.log('drop ok')
    }
  }

  const handleDrop = (e) => {
    e.preventDefault()
    const data = e.dataTransfer.getData('text/plain').split(',')
    dispatch({ type: 'MOVE_TASK', payload: { id: Number(data[0]), prevStatus: data[1], newStatus: status } })
  }

  const handleDragLeave = (e) => {
    e.preventDefault()
    console.log('dragleave')
  }

  return (
    <div className="box"
      id={status}
      onDrop={handleDrop}
      onDragOver={handleDragOver}
      onDragLeave={e => handleDragLeave(e)}>
      <div className="box-title">{title}</div>
      {status === 'beforeWork' && <button className="new-task">課題を作成</button>}

      {tasks && tasks.map((task, idx) => (
        <Task key={idx} {...task} />
      ))}
    </div>
  )
}

export default TaskList

個別のタスクを表示するTask

const Task = ({ id, name, status }) => {

  const handleDragStart = (e) => {
    e.dataTransfer.setData('text/plain', `${e.target.id.replace('task-', '')},${status}`)
    e.dataTransfer.effectAllowed = 'move'
  }

  return (
    <div className="task" draggable="true" id={`task-${id}`} onDragStart={e => handleDragStart(e)}>
      <div className="task-name">{name}</div>
    </div>
  )
}

export default Task

こんな感じで、ドラッグ&ドロップAPIの動作が確認できました。
前からドラッグ&ドロップの処理が気になってたので、今回調べたのはいい機会でした。
今回のサンプルコードはここに上げてあります。
https://github.com/koyoukai/kanban-ui-sample

ドラッグ&ドロップAPIを使うなら、こうした方がいい的な改善等ありましたら、
ぜひご指摘ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js の Composition API における this.$refs の取得方法

Vue.js でテンプレート内の DOM 要素や子コンポーネントの参照は、旧来の Options API だと this.$refs で取得できました。
では、Composition API ではどうなっているのでしょうか。

答えは 公式サイトに書いてあります。

公式サイトより引用
<template>
  <div ref="root"></div>
</template>

<script>
  import { ref, onMounted } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      onMounted(() => {
        // the DOM element will be assigned to the ref after initial render
        console.log(root.value) // <div/>
      })

      return {
        root
      }
    }
  }
</script>

ref 関数で作った変数(上記の場合は root)をテンプレート内の要素の ref 属性に与えます。
初回レンダリング後(onMounted のタイミング)、変数の value プロパティに DOM 要素の参照が代入される、という流れです。

TypeScript で型をつける

さて、このまま終わってしまうのも味気ないので TypeScript ではどう書くのか見ていきましょう。
Vite で作ったプロジェクトをベースにして検証しました。1

DOM 要素の場合

<template>
  <img ref="imgRef" alt="Vue logo" src="./assets/logo.png" />
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'

export default defineComponent({
  setup() {
    const imgRef = ref<HTMLImageElement>() // 1: 型を指定

    console.log(imgRef.value) // 2: undefined が出力される

    onMounted(() => {
      console.log(imgRef.value) // 3: <img>
      console.log(imgRef.value?.clientHeight) // 4: 0 が出力される

      imgRef.value?.addEventListener('load', () => {
        console.log(imgRef.value?.clientHeight) // 5: 200 が出力される
      })
    })

    return { imgRef }
  },
})
</script>
  1. ref 関数は引数なしの場合、ジェネリクスで指定した型と undefined のユニオンになります。2
    すなわち上記の場合は Ref<HTMLImageElement | undefined> 型です。
  2. レンダリング前は undefined です。
  3. レンダリング後だと img 要素の参照が取得できます。
  4. この時点では画像データはまだ読み込まれていないので高さは 0 です。
    また、imgRef.valueHTMLImageElement | undefined 型なので、?. を使用しています。気になる場合は if (!imgRef.value) return などを書いて、undefined の可能性を除外してしまいましょう。
  5. 画像が読み込まれると、正しいサイズが取得できます。

コンポーネントの場合

<template>
  <HelloWorld ref="componentRef" msg="Hello Vue 3.0 + Vite" />
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default defineComponent({
  components: {
    HelloWorld,
  },
  setup() {
    const componentRef = ref<InstanceType<typeof HelloWorld>>() // 1: 型を指定

    onMounted(() => {
      if (!componentRef.value) return // 型から undefined をなくす

      console.log(componentRef.value) // 2: Proxy {…}

      console.log(componentRef.value.$el) // 3: #text

      console.log(componentRef.value.msg) // 4: Hello Vue 3.0 + Vite
      console.log(componentRef.value.count) // 5: 0

      componentRef.value.count++ // 6: バッドプラクティス!
      console.log(componentRef.value.count) // 1 (増えている)
    })

    return { componentRef }
  },
})
</script>
  1. コンポーネントの実態はコンストラクタなので、このような型になります。
  2. インスタンスの Proxy オブジェクトが入っています。
  3. Vue 3 ではテキストノード(<template> 開きタグと最初の DOM 要素の間)になります。3
  4. props のプロパティが直接生えており、値が取得できます(componentRef.value.$props.msg でも同じ)
  5. data も同様です(componentRef.value.$data.count でも同じ)
  6. 外から data の値を変えることもできてしまいますが、大変危険なのでやめましょう。

まとめ

以前にくらべると少し手間は増えましたが、型の恩恵を受けるためなので仕方ないですね。4

今までコンポーネント ref はほとんど使っていませんでした。唯一使いそうな $el がテキストノードに変更されてしまい、さらに使いどころが分からなくなりました。誰か教えて下さい。


  1. 参考になるか分かりませんが、リポジトリはこちら https://github.com/jay-es/composition-api-refs 

  2. tsconfig.json"strictNullChecks": true にしている場合("strict": true でも OK)。
    false になっていると | undefined にはなりません。 

  3. Vue 2 + @vue/composition-api の頃はコンポーネント全体の DOM 要素が取得できました。
    テンプレートのルートに複数の要素を置けるようになったことが関係していると思われます。 

  4. 実は Vue 2 + @vue/composition-api の場合、setup 関数の第二引数に refs が生えています。なんとなく理由は分かりますよね。
    ただし、型情報には存在しないため as などでごまかさないといけないのでオススメできません。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Angularで特定のルートにデータを持たせる

はじめに

Angularでは、Routeという個々のルートを定義するオブジェクトがあり、そこにユーザーが追加定義できるdataというプロパティがあります。これを使って、ActivatedRouteを介してコンポーネントに何らかのデータを渡すことができます。

環境

Angular 9.1.7

1.特定のパスにデータを持たせる

ルーティングを設定するモジュールでRouteオブジェクトにdataというプロパティを持たせます。

app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'notes',
    data: {
      isNotShowFooter: true,
    },
    loadChildren: () =>
      import('./notes/notes.module').then((m) => m.NotesModule),
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule { }

2.ActivatedRouteを使ってコンポーネントにデータを渡す

Routerのイベントを監視(Subscribe)し、ActivatedRouteのプロパティのfirstChildにあるdata、つまり現在表示されているコンポーネントのRouteオブジェクトのdataプロパティを読み取ります。

app.component.ts
export class AppComponent {
  activatedRouteData: Observable<Data>;

  constructor(private router: Router, private activatedRoute: ActivatedRoute) {
    this.router.events.subscribe(event => {
      if ((event instanceof NavigationEnd)) {
        this.activatedRouteData = this.activatedRoute.firstChild.data;
      }
    });
  }
}

以上でコンポーネントにRouteのdataを渡すことができました。

使用例:ヘッダーやフッターを非表示にしたい時

  • 最上層のAppModuleでヘッダーやフッターを読み込んでおり、特定のモジュールで非表示にしたい場合、data内のプロパティを使うことで切り替え、非表示にすることができます。
app.component.html
<ng-container *ngIf="activatedRouteData | async as data">
  <app-footer *ngIf="!data.isNotShowFooter"></app-footer>
</ng-container>

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む