20200625のJavaScriptに関する記事は27件です。

Docker TypeScript で React 環境を構築

自分用のmemoのつもりで
Dockerで環境構築
Dockerはinstall済み

■Dockerfile作成
空のPJディレクトリ内にDokerfile作成し以下を記載

FROM node:13.5.0-alpine3.11
WORKDIR /usr/src/app

FROMは新しいイメージの元となるイメージの読み込み
alpineというのでめちゃくちゃファイルサイズを小さくできるらしい

■docker-compose.yml作成
※ymlは「yaml ain't markup language」の略で構造化データの表現方法
※簡単に言えば設定ファイル

version: '3'
services:
  sample:
    build:
      context: .
    tty: true
    environment:
      - NODE_ENV=production
    volumes:
      - ./:/usr/src/app
    command: sh -c "cd tips && yarn start"
    ports:
      - "3000:3000"

■イメージをビルド

docker-compose build

■React と create-react-app をインストール + アプリ作成

docker-compose run --rm sample sh -c 'npx create-react-app sample_app --template typescript'

出来上がるディレクトリ 構成

- Dockerfile
- docker-compose.yml
- アプリ名
    |- node_module
    |- public
    |- src

■起動

docker-compose up -d

※ローカルでyarn startする時よりも時間がかかる感覚はある

Localhost:3000でApp.jsの中身が表示される

■停止

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

初めてのJavaScript-備忘録03

初めてのJavaScript-備忘録03

今回はMapとSetオブジェクトについて書こうと思う。
オブジェクトの簡単な説明なので、手短に…

Mapについて

Mapは連想配列を扱うためのオブジェクトで連想配列を容易に扱う事ができる。
Mapは何かと使えるヤツなので是非抑えておきたいね。

Mapでよく使うメンバーは以下の通り

メンバー 概要
size サイズを取得
set キーと値をセットでセット
get キー指定でその対応する値を取得
delete キー指定でそのキーと値を削除
clear 全ての値とキーを削除

使い方

let hoge = new Map(); // オブジェクトの生成

//名前、年齢、性別をセット
hoge.set('name',"hoge");
hoge.set('age',18);
hoge.set('sex',"man");

//セットした年齢を変数に代入
let age = hoge.get('age');

//hogeのキーと値を順番にログに出力
for (let [key,val] of hoge){
  console.log(key);
  console.log(value);
}


最後にsetについて書こうかと

Setについて

Setも配列で配列の中の値は重複しない、性質がある。
使い道はぱっと思いつかないけど、
まぁ便利っちゃ便利そう?
あと、index指定やkey指定で要素にアクセスできない事も注意。

メンバー 概要
add 要素を追加
delete 指定した値を削除
clear 全て削除
values 全ての値を取得
size サイズ

使い方

let s = new Set();
// 要素の追加
s.add(1);
s.add(2);
s.add(3);
s.add(4);

let size = s.size; // サイズは4
s.delete(4); // 要素の4が削除されsの中身は(1,2,3)となる。
let size = s.size; // サイズは3

for(let val of s){
  console.log(val); //1,2,3と順番にログに出力する
}

s.clear();//要素を全て削除
size = s.size//サイズは0

今回はここまで、おわり

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

【javascript】DSL構築

書籍のアウトプットとして

DLSとは

言語の機能や構文をうまく使って、その言語に最初からサポートされていたかのように特定のタスクを解決することです。
以降では文字列を処理するためのヘルパー関数を作成していきます。

ヘルパー関数を作成する

前回のテンプレートの記事ではこのようなコードを持つ関数を定義しました。

function stringWS() {
  let stringParts = arguments[0];
  let values = [].slice.call(arguments, 1)
}

let values = [].slice.call(arguments, 1)で文字列部分と補間部分を取得しています。
しかし上記コードでは少しわかりにくいです。
さしあたり、この部分をレイヤ化して、ロジックの他の部分から隠してしまいます。
そのための関数を作成し、取得した値をタグ関数に渡すようにすれば良いでしょう。

function createTag(func) {
  return function() {
    const strs = arguments[0];
    const vals = [].slice.call(arguments, 1);
    return func(strs, vals);
  }
}

後は、タグ関数を次のように作成します。
javascript
const stringW = createTag(function(strs, vals) {
/* strsとvalsを使った処理 */
})

また渡された文字列strsと補間値valsをつなぎ合わせるためにreduceを使用する必要があります。これも抽象化してしまいます。

function interlace(strs, vals) {
  vals = vals.slice(0); //配列のコピーを作成
  return strs.reduce(function(all, str) {
    return all + String(vals.shift()) + str;
  });
}

vals = vals.slice(0);で配列をコピーしています。
shiftのように配列をその場で変更してしまうときにはこのようにします。
あとはcreateTagとinterlaceを組み合わせれば標準的な実装でテンプレートリテラルを処理できます。

const processNormally = createTag(interlace);

const text = 'Click Me';
const link = processNormally `<a>${text}</a>`; // <a>Click Me</a>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アリババクラウドが提供するサービスを利用したNode.jsアプリケーションの展開

このチュートリアルでは、Alibaba Cloudが提供するサービスを利用して、本番環境に対応したNode.jsアプリケーションのセットアップを取り上げます。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

序章

Node.jsは、高性能で高速なサーバーサイドアプリケーションを構築するために使用されるJavascriptのランタイム環境です。Node.js サーバーサイドアプリケーションは、間違った方法でデプロイされた場合、本当に遅くなり、トラフィックの増加をサポートできなくなる可能性があります。

このチュートリアルでは、アリババクラウドサービスが提供する素晴らしいサービスを使用して、本番環境に対応したNode.jsアプリケーションを設定する方法を説明します。Node.js Blogというアプリケーションをデプロイしますが、そのために以下のAlibaba Cloudサービスを使用します。

  • Elastic Compute Service:CPU、メモリ、OS、帯域、ディスクなどの基本的なコンピューティングコンポーネントを含む仮想コンピューティング環境。このサービスを使用してLinuxインスタンスを起動し、そこにNode.jsアプリケーションをデプロイします。
  • ApsaraDB for MongoDB: 安全で信頼性が高く、弾力的にスケーラブルなクラウドデータベースサービスです。このサービスを使ってNode.jsアプリケーション用のMongoDBデータベースを起動します。

前提条件

このチュートリアルを始める前に、以下のものが必要です。

  • デプロイする Node.js アプリケーション。以下の Github リポジトリにあります。
  • Ubuntu 16.04 64ビットECSインスタンス。このチュートリアルを使って作成することができます。
  • ApsaraDB for MongoDB のインスタンス。このチュートリアルを使って作成することができます。
  • ECS インスタンスへ接続します。SSH を使って接続するには、このチュートリアルに従ってください。

  • : ECS インスタンスと ApsaraDB for MongoDB インスタンスがまったく同じゾーンに作成されていることを確認してください。そうすることでアプリケーションのパフォーマンスが向上し、接続の問題や制限を回避することができます。

ステップ 1 - すべてのサーバーの依存関係をインストールする

このステップでは、node js アプリケーションを実行するために必要なすべての依存関係をインストールします。手順に従うには、ECS インスタンスにログインしていることを確認してください。

Git のインストール

GitHubからアプリケーションのコードを取得できるようにするには、gitを使ってリポジトリをクローンする必要があります。以下のコマンドでインスタンスにgitをインストールしてみましょう。

sudo apt-get install git

インストールを確認するには、以下のコマンドを実行してインストールされているgitのバージョンを確認します: git --version.私の場合、以下のような出力が出てきました: git version 2.7.4.

Node.jsのインストール

node.jsアプリケーションを実行するためには、このアプリケーションを実行するために必要な特定のnode.jsのバージョンをインストールする必要があります。私たちのnode.jsブログの場合は、Nodeのバージョン8以上が必要です。インストールするには、以下のコマンドを実行します。

curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh

このコマンドは、nodeource_setup.shというファイルをダウンロードします。これはnode jsをインスタンスにインストールするためのシェルスクリプトです。このシェルスクリプトを以下のコマンドで実行します。

sudo bash nodesource_setup.sh

そして最後に、インストールを完了させるために、以下を実行します。

sudo apt-get install -y nodejs

node jsのインストールを確認するには、node -vを実行して、インストールされているnodeのバージョンを確認します。私の場合は、最新のバージョン8のサブバージョン(v8.13.0)がインストールされています。

Nginxのインストール

Nginx は世界で最も人気のあるウェブサーバーの一つであり、いくつかの大規模なサイトで使用されています。アプリケーションをデプロイするために、プロキシサーバとしてnginxを使用してアプリケーションを提供します。これについては後ほど詳しく説明します。nginxをインストールするには、以下を実行してください。

sudo apt-get install nginx

インストールをテストするには、nginx -vを実行して、インストールされているバージョンを確認します。私の場合、以下のような出力が出てきました:nginx version: nginx/1.10.3 (Ubuntu)

PM2プロセスマネージャのインストール

Node.jsアプリケーションは非常に脆いため、運用環境では強力なプロセスマネージャを使用してnode.jsアプリケーションを監視し、プロセスが常に実行されていることを確認する必要があります。このケースで使用するプロセスマネージャは pm2 と呼ばれ、node.js パッケージとして提供されています。PM2はnode jsアプリケーションのクラッシュを監視するだけでなく、node jsアプリケーションを自動的にスケーリングし、インスタンス上のすべてのCPUを最適化します。PM2をインストールするには、次のように実行します。

npm i -g pm2

インストールを確認するには、pm2 -v を実行してください。私の場合、以下のような出力が表示されました。3.2.2.

ステップ 2 - アプリケーションコードの取得

このステップでは、インスタンス上で実行できるようにアプリケーションリポジトリをクローンします。

アプリケーションコードのクローン

アプリケーションのコードをクローンするには、以下のコマンドを実行します。

cd /home
git clone https://github.com/bahdcasts/node-js-blog.git

プロジェクトの依存関係のインストール

プロジェクトの依存関係をインストールするには、以下のコマンドを実行します。

cd /home/node-js-blog
npm install

環境変数の設定

ほとんどのnode jsアプリケーションでは、サーバの環境変数を設定する必要があります。このプロジェクトではdot-envというパッケージを使用しており、このパッケージを使用することで、すべての環境変数を.envというファイルに配置することができます。このファイルを作成し、環境変数を追加します。以下のコマンドを実行します。

cd /home/node-js-blog
nano .env

ターミナルコードエディタnanoを使って.envという新しいファイルを開きます。ファイルの中に以下の内容を入れます。

PORT=3000
DB_URI=
CLOUDINARY_API_KEY=xxxx
CLOUDINARY_API_SECRET=xxxx
CLOUDINARY_NAME=xxx
EXPRESS_SESSION_KEY=d29z4944SxETKPNWqKAkCNUjrZ4A

これらはこのプロジェクトが動作するために必要な環境変数です。PORTはNode.jsアプリケーションが動作する特定のポートです。EXPRESS_SESSION_KEYは、ユーザーセッションを暗号化するために使用されるランダムな文字列です。

また、このブログの画像はcloudinaryにアップロードされており、そのためにcloudinaryの環境変数が必要になります。このチュートリアルの焦点ではないので省略しても構いません。これは、独自のNode jsプロジェクトをデプロイする際に、独自のカスタム環境変数を設定する方法を紹介するだけです。

最後の環境変数は DB_URI で、これが ApsaraDB for MongoDB インスタンスに接続するための環境変数になります。これは後のステップで追加します。

: nano editorを終了するには、CTRL + Xを押してからYを押してください。

ステップ3 - MongoDBインスタンスへの接続

このステップでは、先ほど作成した ApsaraDB for MongoDB インスタンスにアプリケーションを接続します。

ApsaraDB for MongoDB のサーバー IP をホワイトリスト化する

デフォルトでは、MongoDB インスタンスはどのアドレスからの接続も許可しません。コンソールを使って、ECS インスタンスの IP アドレスをホワイトリストに登録して、アプリケーションが正常に接続できるようにします。

まず、コンソールにアクセスして MongoDB インスタンスを開き、「データセキュリティ」タブにアクセスします。ここで、Manually modify ボタンをクリックして新しい IP アドレスを追加します。

image.png

変更を保存します。このアクションは、アプリケーションがこの MongoDB インスタンスに接続するための許可を与えます。

DB_URI接続文字列の設定

Database Connection タブから、データベースへの接続文字列を取得します。

image.png

環境変数 DB_URI の設定

接続文字列をコピーし、環境変数 DB_URI の値を更新します。nano .envを使うと、.envファイルは以下のようになります。

PORT=3000
DB_URI="mongodb://root:passWORD1234@dds-gs50fb23291731d41.mongodb.singapore.rds.aliyuncs.com:3717,dds-gs50fb23291731d42.mongodb.singapore.rds.aliyuncs.com:3717/admin?replicaSet=mgset-300215248"
CLOUDINARY_API_KEY=132255634713478
CLOUDINARY_API_SECRET=aeBNzdZFcju9ZEAiL7KsEMbBlFI
CLOUDINARY_NAME=bahdcoder
EXPRESS_SESSION_KEY=d29z4944SxETKPNWqKAkCNUjrZ4P

: 文字列中の***は、インスタンスの実際のルートパスワードに置き換えることを忘れないでください。

ステップ4 - Nginxの設定をする

このステップでは、nginx ウェブサーバが node js アプリケーションを利用できるように、nginx の設定を行います。

これを行うために、デフォルトのnginx設定ファイルを編集します。以下のコマンドを実行します。

# Empty the current configuration file
echo > /etc/nginx/sites-available/default

# Open the file using nano
nano /etc/nginx/sites-available/default

このファイルの内容を以下のように置き換えてください。

server {
  listen 80;

  server_name 47.88.230.255;

  location / {
      proxy_pass http://localhost:3000;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_cache_bypass $http_upgrade;
  }
}

注意すべき2つの重要な設定は、server_nameproxy_passです。server_name は理想的にはアプリケーションのドメイン名であるべきで、ユーザーが特定のサイトにアクセスしようとした場合に基礎となる設定を使用するよう nginx に指示します。この場合、このアプリケーションのためにドメイン名を設定しないので、単に ECS のインスタンスの IP アドレスを使用します。

第二に、proxy_pass の設定は nginx に、http://localhost:3000 上で実行されているプロセスに、server_name のために入ってくるすべてのトラフィックをリダイレクトするように指示します。つまり、ユーザーが47.88.230.255にアクセスしようとすると、nginxはユーザーのリクエストをポート3000で動作しているnode jsアプリケーションにリダイレクトします。ここでは3000番ポートを使用していますが、これはnode jsアプリケーションを起動するポートであるためです。

: 47.88.230.255をECSインスタンスの実際のIPアドレスに置き換えることを忘れないでください。

最後に、設定変更を保存するためにnginxを再起動する必要があります。

以下のコマンドを実行してください。

sudo systemctl restart nginx

ステップ5 - アプリケーションの起動

最後に、プロセスマネージャPM2を使用してnode jsアプリケーションを起動する必要があります。 これを行うには、以下のコマンドを実行します。

cd /home/node-js-blog

pm2 start index.js --name "blog"

このコマンドは、index.jsファイルをルートとして使用してアプリケーションを起動し、このプロセスにblogという名前を与えます。このコマンドは以下のような出力が出てきます。

image.png

アプリケーションログのトラッキング

アプリケーションをデバッグして監視するには、以下のコマンドを使用して、アプリケーションからログに記録されたすべてのメッセージをリアルタイムで表示することができます。

pm2 logs blog

これはblogというプロセスのログをプリントアウトします。出力は以下のようになります。

image.png

結論

このチュートリアルでは、Alibaba Cloud Elastic Compute Service (ECS) と ApsaraDB for MongoDB を使用して本番アプリケーションをデプロイする方法を学びました。また、PM2と呼ばれる高度なプロセスマネージャを使用してNode.jsアプリケーションを管理し、自動的にスケーリングしました。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

古の書き方を卒業してちょいと今どきよりなJSを書こう

はじめに

基本的にはワタクシ様はPHPを書いて日々過ごしているのですが、たまにjsを書いたりすることもあります。
さて業界では未だに js = jQuery みたいな方も少なくなく、jQueryでjsを覚えてそのままAngularとかに手を出さずCoffeeScriptとか通らなかった方はなんでか知りませんが、古文のようなES5以前のjsを書くことが多い印象があります。
ES6(ES2015)が世に爆誕してから5年も経つと便利さ、書きやすさが桁違いにUPします。
ES5があまりにも書きづらくてCoffeeScriptやTypeScriptやDartや Reactに関係ない方のJSX などといったaltJSと呼ばれるものが2010年頃からポコポコ生まれていったわけですが、それらの影響を受け、格段に書きやすくなった今どきなjsの書き方をふたつばかり紹介していきたいと思います。

varからの卒業

// ES5以前
var hoge = ''; // よくない

// ES6以後
const hoge = ''; // 中身が変わらない予定ならこっち(そのうち書き換えが効かなくなると思う)
let hoge = ''; // 書き換わるならこっち

var で変数宣言をする時代は終わりです。今は constlet です。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/let
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/const

varがダメなわけではないのですが、varは関数全体だったりとかグローバル全体に影響を及ぼしてしまい大変危険だったわけです。
「お前は今まで食べたパンの数を数えてるのか」ばりに宣言した変数名を全部記憶してるならvarでもいいのですが、そんな自身がないなら constlet を使いましょう。
使い分けとしてはずっと中身が変わらないなら const 、変わるなら let です。

functionからの卒業

// ES5以前
var hoge = function(fuga){return '';};
var obj = {
    onClick: function(e){
        return e.target;
    }
};

// ES6以後
const hoge = fuga => {return '';};
const org = {
    // 旧来のメソッド定義と同じ
    onClick(e){
        return e.target;
    },
    // こちらはthisがうごかなくなる
    onClick: e => {
        return e.target;
    },
};

ES5の頃はfunction宣言地獄だったわけですが、ES6以降はそんな心配はありません。
というか、functionというキーワードを書く機会もあんまりなくなると思います。
普通の無名関数ならかっこいいアローファンクションが使えますし、アローファンクション使うと「呼び出し元によってthisが変わっちまう問題」から開放されます。違う問題には直面しますが……
オブジェクトのメンバーに関数を定義する「メソッド定義」はちょっとJavaっぽい書き方ができるようになりました。
もちろんアローファンクション使ってメソッド定義もできるのですが、thisの意味合いが変わってしまうので、全く同じようには動作しないので注意が必要です。

参考記事

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

ブラウザ上でzipファイルを圧縮展開できるWebアプリを作ってみた

こんにちわ。

創作意欲が湧いた勢いで、JavaScriptからzip形式のアーカイブを操作できる "JSZip" というライブラリを使用して、ブラウザ上でzipを圧縮展開できる簡単なWebアプリを作ってみました。

作ったもの

めちゃくちゃシンプルです。

Babelは通してないので、動作確認済のChrome以外は新生EdgeやFirefoxでなら多分動くと思います。

仕組み

凝ったことはしていないので、とても簡単です。

  1. Input要素でファイルを読込
  2. ファイルをワーカースレッドへ転送
  3. ワーカースレッドでJSZipをロードし圧縮展開
  4. 処理結果をメインスレッドへ転送
  5. a要素に処理結果のBlobURLを充ててダウンロード

全ての処理がローカルで完結しています。

ヘビータスクの圧縮展開はWebWorkerで処理しており、かつファイルはTypedArray化して参照渡し(Transferable Object)しているので、処理中の描画レスポンスへの影響はほとんどありません。

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

Firebase Cloud Messagingで「FirebaseError: Messaging: This browser doesn't support the API's required to use the firebase SDK. (messaging/unsupported-browser).」

Firebase Cloud Messaging(以下FCM)で「FirebaseError: Messaging: This browser doesn't support the API's required to use the firebase SDK. (messaging/unsupported-browser).」が出た場合

Chromeの最新版で FCM をつかっていて遭遇した。

原因

原因はFCM使ったアプリにhttpでアクセスしていたということ

ngrokをつかってデバッグしているときに、うっかり http でアクセスしてしまっていた。
エラーメッセージを読むと「ブラウザがAPIに対応していない」ようにみえてしまうので注意。

対策

FCMのクライアントとなるWebアプリはhttpsでアクセスしましょう。または開発・デバッグ時はhttp://localhostも可。

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

TestCafeを使ってみる①

TestCafeとは

アメリカのシステム開発会社が開発しているE2Eテストツール(2016年に登場した比較的新しいツール)
公式ドキュメント

特徴

  • ES2017(async/await)を含む最新のJavascript機能をサポート、TypeScriptにも対応
  • サポートブラウザが豊富
    • Chrome、firefoxなどの主要ブラウザは網羅されており、IE11もサポートされています。
    • スマホやIEはテスト実行時に発行されるURLおよびQRコード(オプションで設定)を読み込むことで実機テストを行うことができる。
  • 非WebDriver依存
    • 一昔前のE2Eテストツールは、各種ブラウザベンダ間の差異を吸収し抽象化して操作するためにWebDriverを使うのが常識でしたが、現在はブラウザ側がサポートするようになり、テストツール側が意識しなくてよくなりました。TestCafeもこれを前提に設計されています。
  • BDD(ビヘイベア駆動開発)に則ったテストコード
    • UIテスト手順をメソッドチェーン形式で記述。これにより、ユーザー操作や外部仕様をまるで自然言語のようにコードで表現できるので、テストコードがそのままテスト仕様書となります。
  • BrowserStackとの連携をサポート
  • 構築が簡単
    • Seleniumではwebdriverのクライアントとテストを実行する各ブラウザのwebdriverのインストールが必要ですが、TestCafeはnpmでインストールするだけでOK。設定ファイルも必要なくテスト環境が整います。

セットアップ

npmもしくはyarnでtestcafeをインストール

$ npm install --save-dev testcafe
$ yarn add -D testcafe

テストコードを書く

test.jsなど任意のファイルを用意しコードを書いていきます。
まずはtestcafeをインポート

test.js
import {Selector} from 'testcafe’;

次にfixtureメソッドでフィクスチャを宣言(定義)
fixtureカテゴリのようなもので、テストケースをここで分類していきます。

test.js
import {Selector} from 'testcafe’;

fixture('Getting started');

ちなみにタグ付きテンプレートリテラル構文でも書くことができます。

fixture`FormTest Start`;

次にfixtureメソッドの戻り値からpageメソッドを実行します。
pageはフィクスチャの開始ページを宣言するものです。こちらもテンプレートリテラルで書くことができます。

test.js
import {Selector} from 'testcafe’;

fixture('FormTest Start');
  .page('https://example.com');
  // ↓テンプレートリテラル
  .page`https://example.com`;

テストケースを書く

testメソッドでケース名とテスト内容(UI操作手順)を記述していきます。
第一引数にケース名を渡し、これが実行ログに表示されるので分かりやすい名前が良いです。
第二引数も記述することができ、テスト内容を関数として設定します。
関数は戻り値がPromise型となっているのでasync/awaitを使います。

test.js
import {Selector} from 'testcafe’;

fixture('FormTest Start');
  .page('https://example.com');
  test('必須項目を入力し送信', async (t: TestController) => {
    const dataTarget = await Selector('[data-target-input]');
    const name = dataTarget.withAttribute('name', 'name');
    const mail = dataTarget.withAttribute('name', 'email');
    const phone = dataTarget.withAttribute('name', 'phone');
    const submitBtn = await Selector("button");

    await t
      .typeText(name, 'テスト名前')
      .typeText(mail, 'test_sample@gmail.com')
      .typeText(phone, '0312345678')
      .click(submitBtn.withExactText('送信'))
});

第二引数はなくてもOKです。その場合はこんな感じ

test('必須項目を入力し送信', async t => {
})

セレクタについて

エレメントに関する情報を取得するにはSelectorを使用します。
ページ要素を識別し、それらに対してアクション(クリックやドラッグなど)を実行するか、アサーションでその状態を確認することができます。
今回はwithAttributeメソッドを使い、指定したname属性と値が一致する要素を取得しています。
セレクタの詳細は次回の投稿で紹介します。

画面操作について

今回の例としてclicktypeTextを使用しています。
click :クリックオプション
typeText:キー(テキスト)入力オプション
その他メソッドについても次回以降の記事で紹介します。

テスト実行

package.jsonのscriptsにtestコマンドを追記し、コマンドラインから実行できるようにします。

package.json
"scripts": {
  "formTest": "testcafe chrome e2e/test.js --page-load-timeout 5000 --speed 0.8",
}

コマンドに実行環境(ブラウザ)と実行ファイルのパスを指定します。※この2つは必須
今回はchromeを設定。
インストールされている全てのブラウザでテストをする場合はallを指定します。

package.json
"scripts": {
  "formTest": "testcafe all e2e/test.js --page-load-timeout 5000 --speed 0.8",
}

オプション

--page-load-timeout 5000
 ページロード時のタイムアウト時間を指定できます。デフォルトは(3000ms)
--speed 1
 テスト実行速度を指定できます。デフォルトは最速(1)で実行されるので値を変更して遅くすることもできます。1が最速、0.01が最も遅い。値を設定する際は1~0.01の間でなくてはいけません。
:headless
 chrome、firefoxに対してこのパラメーターをつけるとUIシェルを表示せずにヘッドレスモードでテストが実行できます。

$ npm run formTest

コマンドを実行すると指定したブラウザが立ち上がりテストが実行されます。
スクリーンショット 2020-06-25 12.28.07.png

リモートブラウザ上でテスト実行

ホストマシンがMacやLinuxの場合にIE11でテストを実行したい場合は、ブラウザにremoteと指定することでリモートアクセス用のURLを発行できます。

package.json
"scripts": {
  "formTest": "testcafe remote e2e/test.js",
}

同一のネットワーク上にいるマシンからであればOSやブラウザ問わずURLにアクセスすることでテストを行えます。
スマホ端末からアクセスする場合は、--qr-codeオプションを指定することでQRコードが発行され、読み込むことでスマホからもテストができます。

package.json
"scripts": {
  "formTest": "testcafe remote e2e/test.js --qr-code",
}

E2EテストツールというとSelenium、Puppeteerがよく聞きますが、他にないかなーと調べてTestCafeを知りました。(テストカフェ:coffee:って名前も可愛くて好きです:blush:)
まだまだ勉強中で基本的な使い方しか出来ていませんが、所感としては直感的に分かりやすいコードになるのでTestCafe良い感じです。
今回は備忘録としてセットアップをメインにまとめました。
オプションやセレクタ、メソッドはたくさんの種類があるので、よく使いそうなものを中心に今後の記事でまとめ、スクリーンショットを撮る方法についても書いていきたいと思います。

参考: TestCafe で E2E テストを始めよう #1 - 概要説明 と Hello World

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

[1行で完了]最速でReact環境構築

※自分のアウトプット用(メモ)です

結論だけ知りたい人用

1行のコマンドでReactの環境構築を完了する。
[条件]
1.Node.js 8.10以上
2.npm 5.6以上

$ npx create-react-app プロジェクト名

順番通り説明していきます。

必要なパッケージのインストール方法

1. brewをインストール

Macのパッケージ管理システムであるbrewをインストール。
brew公式サイト

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

2. nodebrewbrewでインストール

nodeのバージョン管理ができるnodebrewをインストール。
(brewでインストールするので先にbrewをインストールした)。

$ brew install nodebrew

3. Node.jsnodebrewを使ってインストール

Node.jsはnodebrewで管理した方が使いやすいと思います。他のバージョンに移るの簡単ですし、普通にインストールすると削除するのに手間なんですよね...
ほとんどの場合、安定版をインストールしておけば問題ないと思います。

// リモートにあるインストールできるNode.jsの一覧表示
$ nodebrew ls-remote

// 安定版をローカル(自分のMac)にインストール
$ nodebrew install stable

// ローカルにインストールされている`Node.js`のバージョンを一覧表示
$ nodebrew ls

// インストールしたバージョンの`Node.js`を選択する
$ nodebrew use 使用したいバージョン番号

4. Node.jsのパスを通す

// "~/.bash_profile"にパスを追記する(bashの場合)
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile

// ターミナルを再起動して反映
// または以下を実行して反映
$ source ~/.bash_profile

5. nodenpmが使用可能か確認

Node.jsをインストールした時点でnpm(node package manager)もインストールされているので、うまくいっていればこの時点で使用可能になっているはずです。

// Node.js
$ node -v

// npm
$ npm -v

これで準備は整いました。

create-react-appReactの環境構築

任意の場所で以下のコマンドを叩いてください。
npxコマンドを簡潔に説明すると、パッケージをローカルにインストールすることなく実行できるコマンドです。
環境を汚染しないので手軽に実行して見たいときに便利です。
詳細はこちら

$npx create-react-app プロジェクト名

これでカレントディレクトリにreactのプロジェクトが作成されました。
以下のコマンドでブラウザ表示します。

$ npm start

スクリーンショット 2020-06-25 15.21.19.png

お疲れ様でした。

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

vis.jsを利用し相関図を作成してみた

初めに

相関図を作成する機会があり簡単に描画できるライブラリはないかと探した所、
vis.js の vis-network が良さそうでした。
サンプルを参考に自分の好きな飲み屋 徳田酒店 の店舗と利用頻度を描画してみます。

ソースコード

tokuda.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>test</title>
    <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
    <style type="text/css">
        #mynetwork {
            width: 600px;
            height: 400px;
            border: 1px solid lightgray;
        }
    </style>
</head>
<body>
<div id="mynetwork"></div>
<script>
    // create an array with nodes
    const nodes = new vis.DataSet([
        {id:1 , label: '徳田酒店', shape: 'box', color:'#C2FABC'},
        {id:'1-1',  label:'京橋本店', size:25},
        {id:'1-2',  label:'片町店', size:25},
        {id:'1-3',  label:'プロムナード店', size:10},
        {id:'1-4',  label:'西梅田店', size:15},
        {id:'1-5',  label:'御肉', size:20},
        {id:'1-6',  label:'第3ビルB1店', size:30},
        {id:'1-7',  label:'第3ビルB2店', size:20},
        {id:'1-8',  label:'第4ビルB2店', size:5},
        {id:'1-9',  label:'瓦町店', size:15},
        {id:'1-10', label:'ルクア大阪店', size:5},
        {id:'1-11', label:'京阪高架下店', size:5},
        {id:'1-12', label:'ホワイティうめだ店', size:5},
    ]);

    // create an array with edges
    var edges = new vis.DataSet([
        {from: 1, to: '1-1' },
        {from: 1, to: '1-2' },
        {from: 1, to: '1-3' },
        {from: 1, to: '1-4' },
        {from: 1, to: '1-5' },
        {from: 1, to: '1-6' },
        {from: 1, to: '1-7' },
        {from: 1, to: '1-8' },
        {from: 1, to: '1-9' },
        {from: 1, to: '1-10' },
        {from: 1, to: '1-11' },
        {from: 1, to: '1-12' },
    ]);

    // create a network
    var container = document.getElementById('mynetwork');

    // provide the data in the vis format
    var data = {
        nodes: nodes,
        edges: edges
    };
    var options = {
        nodes: {
            shape: 'dot',
        }
    };
    // initialize your network!
    var network = new vis.Network(container, data, options);
</script>
</body>
</html>

表示結果

第3ビルB1店の利用頻度が高いことが可視化できました。
tokuda.png

最後に

今回は簡単なものを表現してみましたが大量のノード表示や画像に変更するなども可能です。
どのような表現ができるかは サンプル をご確認下さい。

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

自分を励ますアプリ

自分を励ますアプリ

ひたすらポジティブな言葉をかけてくれる励ましアプリ。
HTML5、CSS3、Javascriptにて製作。

HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>励ましてくれるアプリ</title>
<link rel="stylesheet" href="./style.css">
<link href="https://fonts.googleapis.com/css?family=M+PLUS+Rounded+1c&display=swap" rel="stylesheet">
<meta name="robots" content="noindex,nofollow,noarchive">
</head>
<body>

<div id = "container">

  <header>
    <h1>励ましアプリ</h1>
  </header>

  <div id = "message"><strong>悩みや不安を書き出してみよう!</strong></div>

  <div id = "commentBox">
    <form action="" method="post">
      <input id="comment" type="text" value="">
    </form>
  </div>

  <div id ="button"><strong>悩みを聞いてもらおう</strong></div>

  <div id ="result"></div>

  <div id ="buttonClear"><strong>参考になったよありがとう</strong></div>

</div>

<script src="encouragement.js"></script>

</body>

</html>

Javascript

//メッセージを用意
let encouragement = [
    "十分睡眠を取ろう", "海を見に行こう", "陽の光を浴びよう",
    "明日は今よりいい日", "自分を信じろ", "努力は裏切らない",
    "諦めちゃダメだよ", "人事を尽くして天命を待て", "神は見捨てない"
    ,"君ならできる"
];

//表示領域保持
let resultBox =  document.getElementById("result");

//クリアボタンの表示領域を隠す
document.getElementById('buttonClear').style.display="none";

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//ボタンを押したらの動き
document.getElementById("button").onclick = function(){

    //入力値保持
    let commentBox = document.getElementById("comment").value;

    //もしもコメントボックスに何か入力されていたら
    if (commentBox != "") {

        //あらかじめ用意したメッセージから.lengthで配列の要素数取得、その要素数だけループ処理、この場合条件を満たしている限りずっとループ
        while (0 < encouragement.length) {
            //クリアボタン表示
            document.getElementById('buttonClear').style.display="block";
            //配列の要素数に乱数を発生させてそれを整数に直す。
            let eNumber = Math.floor(Math.random()*encouragement.length);
            //配列の中身の文字[ランダムに取得したインデックスナンバー]、つまりランダムでメッセージを取得しそれを表示領域に表示
            resultBox.innerHTML = encouragement[eNumber];
            //処理終わり
            break;
        }

    }
    //そうでない場合、つまりコメントが何も入力されていなかったら。
    else {

        //結果を空に
        resultBox.innerHTML = "";
        //クリアボタン削除
        document.getElementById('buttonClear').style.display="none";
        //警告文表示
        alert("本当に悩みはありませんか?");

    }

}

//ボタンを押したらの動き
document.getElementById("buttonClear").onclick = function(){

    //コメントを空に
    document.getElementById("comment").value = "";
    //結果を空に
    resultBox.innerHTML = "";
    //クリアボタン削除
    document.getElementById('buttonClear').style.display="none";

}

css

* {
    box-sizing: border-box;
}

html, body {
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    height:100vh;
}

#container {
    margin:0 auto;
    width: 80vw;
    min-width: 300px;
    min-height: 50vh;
    border-radius: 5px;
    font-size: 1.2rem;
    text-align:center;
    overflow: auto;
    -webkit-box-shadow: 4px 4px 8px 6px rgba(0,0,0,0.17);
    -moz-box-shadow: 4px 4px 8px 6px rgba(0,0,0,0.17);
    box-shadow: 4px 4px 8px 6px rgba(0,0,0,0.17);
}

#container h1 {
    margin: 0;
    font-size: 1.3rem;
    padding:2vh 0vh;
    letter-spacing: 5px;
}

header {
    font-family: 'M PLUS Rounded 1c', sans-serif;
    background: #27e0b7;
    width:100%;
    color:#333;
    background: repeating-linear-gradient(45deg, #2affd0, #2affd0 10px, #27e0b7 0, #27e0b7 20px);
    text-shadow: white 3px 0px, white -3px 0px, white 0px -3px, white -3px 0px, white 3px 3px, white -3px 3px, white 3px -3px, white -3px -3px, white 1px 3px, white -1px 3px, white 1px -3px, white -1px -3px, white 3px 1px, white -3px 1px, white 3px -1px, white -3px -1px, white 1px 1px, white -1px 1px, white 1px -1px, white -1px -1px;
}

#message {
    padding:5vh 0vh;
    color:#333;
    font-weight:500;
}

#comment {
    width: 80%;
    height: 5vh;
    font-size: 1.15rem;
}

#button {
    background: #27e0b7;
    margin: 0 auto;
    text-align: center;
    cursor: pointer;
    width: 50vw;
    padding: 2vh;
    margin-top: 7vh;
    margin-bottom: 7vh;
    display: inline-block;
    padding: 0.5em 1em;
    text-decoration: none;
    color: #FFF;
    border-bottom: solid 4px rgb(201, 201, 201);
    border-radius: 5px;
}

#button:active {
    -webkit-transform: translateY(4px);
    transform: translateY(4px);
    border-bottom: none;
}

#result {
    font-family: 'M PLUS Rounded 1c', sans-serif;
    color: #ffeb3b;
    font-size: 1.5rem;
    font-weight: bold;
    letter-spacing: 5px;
    text-shadow: #806b20 3px 0px, #806b20 -3px 0px, #806b20 0px -3px, #806b20 -3px 0px, #806b20 3px 3px, #806b20 -3px 3px, #806b20 3px -3px, #806b20 -3px -3px, #806b20 1px 3px, #806b20 -1px 3px, #806b20 1px -3px, #806b20 -1px -3px, #806b20 3px 1px, #806b20 -3px 1px, #806b20 3px -1px, #806b20 -3px -1px, #806b20 1px 1px, #806b20 -1px 1px, #806b20 1px -1px, #806b20 -1px -1px;
}

#buttonClear {
    background: #27e0b7;
    margin: 0 auto;
    text-align: center;
    cursor: pointer;
    width: 50vw;
    padding: 2vh;
    margin-top: 5vh;
    margin-bottom: 7vh;
    display: inline-block;
    padding: 0.5em 1em;
    text-decoration: none;
    color: #FFF;
    border-bottom: solid 4px rgb(201, 201, 201);
    border-radius: 5px;
}

#buttonClear:active {
    -webkit-transform: translateY(4px);
    transform: translateY(4px);
    border-bottom: none;
}

デモなどGithubはこちら。

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

素のJavaScriptでパラメーターを取得する方法

まずは全体像から

アクセスするURLを以下のようにします。大切なのはパラメーター部分です。

http://localhost/index.html?param=hoge&name=aaa

ローカルホストはVSCodeの機能で簡単に作成ができます。以下の記事を参考にしてください。

https://qiita.com/sventouz/items/af54ce2ba27350b72343

let param = window.location.search;
param = param.substring(1);
let parameters = param.split('&');
for (let i = 0; i < parameters.length; i++) {
    let element = parameters[i].split('=');
    let name = decodeURIComponent(element[0]);
    let value = decodeURIComponent(element[1]);
    console.log(name);
    console.log(value);
    break;
}

解説

まず?以下のパラメーター部分を全て取得し、変数に入れます。

let param = window.location.search;

substring

substringでいらない文字を削除します。本来は開始と終了を指定し、文字を切り取るのですが、1つのみの選択だと2文字目からの切り取りになるので今回であれば「?」が削除され、それ以下の文字列が変数に入ります。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/substring

param = param.substring(1);

split

splitは指定した文字で文字列を区切ることができます。今回であれば「&」があるタイミングで文字列を区切っています。

今回であれば以下がパラメーター部分なのです。「?」がsubstringで削除されて、「&」で文字列が区切られるので変数に入っているのは

Array [ "param=hoge", "name=aaa" ]

です。

?param=hoge&name=aaa

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/split

let parameters = param.split('&');

decodeURIComponent

ぶっちゃけまだよくわかってない。decodeってことはencodeされたやつを戻すもの?日本語パラメーターが英語とか記号に変わってしまったものを直してくれるやつ?ってことかな?

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent

let name = decodeURIComponent(element[0]);

まとめ

JavaScriptは奥が深い。が勉強を再始動してからいろいろ思い出してきた!

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

2020/6/25 これまでのプログラミング学習 独学

これまでの学習

3月・4月 progate

・HTML&CSS 初級・中級・上級
・Ruby
・Ruby on Rails
・SQL

「そもそもプログラミングって何?」という疑問を解決するために、progateから始めた。

5月 ドットインストール

・HTML&CSS 復習
・JavaScript メイン

ここから本格的にプログラミング学習を開始した時期、動画を使った方が自分には合っているなと実感した。

6月 上旬

基礎学習を終え道に迷う

6月 中旬・下旬 (これを書いてる最中)

Udemyの教材を使って基礎学習の深掘りをする。

同時並行で模写コーディング・自分が作ってみたいと思ったサイトを作ってみる
(Udemyで学んだことを生かせるような内容で)

Qiitaを使ってその日の学習内容を記録する。

Qliitaを利用するきっかけとして、3月から6月上旬までインプットばかりしていたので、アウトプットする機会を少しでも増やそうと思ったから。
模写コーディングもアウトプットの一貫。

環境構築

ドットインストールで使用していたものを継続して使い続けている。
・Google Chrome(ブラウザ)
・Visual Studio Code(コードエディター)
https://azure.microsoft.com/ja-jp/products/visual-studio-code/

今後の学習内容と流れ

Udemyでインプットしながら、Qiitaにアウトプットする。
(これを主軸)
仕事でHTMLとCSSを使うので、復習も兼ねてHTML&CSSから開始し、Qiitaに学習メモを残す。

模写コーディングに関して、自分のアイデアでCSSを書いた後に、CSSのフレームワークであるbootstrapを使ったコーディングもやってみる。

最後に

プログラミング独学自体は1年以内に終えることを目標とし、継続することに重きを置きながら頑張る。

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

Rails6のJavasqriptとJ.Queryの取り扱い方法

概要

Rails6からJS周りの取り扱いが大きく変わったようなので、
JavascriptとJ.Queryの取り扱いについてまとめました。

概念

まずRailsではブラウザにJ/Sファイルの内容を表示するときには、
基本的にwebpackで複数のJ/Sファイルコンパイルして出力するという流れがあります。

Rails6におけるJavasqriptの使用方法

Rails6では下記のjsに参照を書き込む事でコンパイル対象のモジュールを追加できる。
app/javascript/packs/application.js

コンパイルされたjsは、下記のタグヘルパーを記載する事でscriptタグが記載される。

javascript_pack_tag

デフォルトでは共通レイアウト(※)に書かれていて、全てのページにこれが読み込まれるようになっている。

app/views/layout/application.html.erb
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

実際作成したJ/Sファイルを仕込む方法

application.jsに参照を追加する事でコンパイル対象にする。
app/javascript以下の任意のディレクトリもしくは直下にjsを作成する。
(※packs以下に作らない事。)

// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

// 追加
require ('user/sample')

// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)

Rails6でjQueryの導入方法

ターミナル

$ yarn add jquery
webpacker/environment.js
const { environment } = require('@rails/webpacker')

#追記
const webpack = require('webpack')
environment.plugins.prepend('Provide',
    new webpack.ProvidePlugin({
        $: 'jquery/src/jquery',
        jQuery: 'jquery/src/jquery'
    })
)
#ここまで

module.exports = environment
app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require('jquery') #追記
require("J/Sファイル名")#実際に読み込むファイル
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(デザイナー向け?)jsのデバッグコンソールに見覚えのないエラーが大量発生した時に原因解析を少し楽にしたい(特殊ケース)

タイトルについて

私用のアプリをサクッと作っていた時にコンソールがエラーで埋め尽くされたので原因を調べていたら、参照できないオブジェクト(null)に対し操作をしているらしい事が分かった。
ページごとに必ず発生しているがエラー件数も違うので原因がつかめずハマったのでviewを見ながらビルドしたところjsファイルに問題がある事が発覚した。

昨今のフレームワークはこういう作りになっている事は珍しいが、古いCMSだとありそうだな、と思ったので提案。

良かれと思って書いているがバッドノウハウである(後述)事は留意しておいてほしい。

前提

  • 生成側ではhtmlソースを管理していない
    • たとえばindex.phpとかcontents.phpのようなファイルは存在しないか、編集不可である
    • 生成されたhtmlファイルをview側で変更できない
  • 複数のページで同じjsファイルを呼び出している
  • 呼び出されるjsファイルはscript.jsしかない
  • jsとcssは編集できる

という場合を想定。
なお、実際はユーザーが参照するファイルはhtmlではないのですが、説明をしやすいようにここでは生成物をhtmlとしている。

生成されるソースコード例

index.html
(中略)
<script src="script.js">
(中略)
<section id="top_block">
(中略)
<!-- こちらにid:contents_blockは存在しない -->
contents.html
(中略)
<script src='script.js'>
(中略)
<section id="contents_block">
(中略)
<!-- こちらにid:top_blockは存在しない -->

この時、id:top_blockid:contents_blockに対して何かしらのアプローチをしたい場合、他のページで存在しない要素に対して制御を行うとブラウザのコンソールに期待しないエラーが発生するためデバッグが面倒になる。
これが少ない数なら無視できるが、それぞれのページや要素に対して何か行っている場合、呼び出している回数だけエラーが発生することになってしまう。

解決(提案)

script.js
function run_judge_function ( obj, func )
{
  obj == null ? null : func();
}

関数を実施させる際に直接呼び出すのではなく、run_judge_function対象となるオブジェクト実施したい関数を渡してnull(undefined)を検出してあげて、見つかれば実施したい関数を実行するというアプローチを検討する。

注意

この関数が想定される・本来は検出したいエラーを揉み消す可能性があり、本番環境に乗せてしまうと異常が発生した場合に適切なエラーログが取得できない事が考えられる。

script.js
let stage_flg = true;  // ※1
function run_judge_function ( obj, func )
{
  if (stage_flg) obj == null ? null : func();
}

(※11
としておくと、消し忘れを防止できるが検証環境と本番環境で差異が発生する事に留意。
検証環境の時はtrue、そうでなければfalseにする環境変数を用意しておく事も検討すべきか。

備考

本来は呼び出し側で適切なスクリプトを参照するようにしてあげるのがスマートだが、これらのhtmlソースを生成する側のファイルでヘッダ情報などを共通化しているために起こってしまう。
生成側のファイルを変更できるなら調整や対応をissueに上げるべきだが、今回はあくまでview側で解決する場合を考えた。

注意とお節介

この問題は現場でも起こる可能性があるのではないかと考えており、たとえばプロジェクト初期に想定されなかった要素を継ぎ接ぎで追加した時に起こりがちな問題ではないだろうか?
後で継ぎ接ぎした時に良かれと思ってこういう関数をかますと(既存部分への影響が大きすぎてテストが大変になり)メチャクチャ怒られるのが普通なので導入時は注意するか、なんならstage環境にも上げるべきではない。

githubなどコードレビュー文化が根付いているならチェックが入るはずだが、コードを書けるプロの人はレビューもプロか?というとそうではないので運用上の注意が必要。

お願い

ページを参照した(view)がイコール最後まで読んでもらった(complete)と思っていないので、お手数を掛けて恐縮だがLGTMをいただけると、読みやすい書き方や記事がどういうものか分析しやすくなり大変助かります。

注釈


  1. 【※1】実際の使用例としてstage_flgは環境変数か何かで設定する事が望ましい。 

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

AdonisJSとReactを使ってイベントを管理するためのモダンなWebアプリを構築する

このチュートリアルでは、ReactとAdonis Node.jsフレームワークを使って、Alibaba Cloud上でREST APIのバックエンドとフロントエンドを構築します。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

序章

Adonisは、スケーラブルで安定した効率的なWebアプリケーションやREST APIを構築するために使用できるNode.jsフレームワークです。Reactを使用して、Adonisで構築されたREST APIを消費するためのモダンで堅牢なフロントエンドWebアプリケーションを構築します。

これから構築するWebアプリケーションは、MySQLのデータベースにイベントを格納し、これらのイベントを読み込んで削除します。

完成したら、以下のことができるようになります。

  • Adonis.jsを使ったREST APIの構築
  • Reactを使ってREST APIを消費する堅牢なクライアントサイドアプリケーションを構築する
  • Bootstrap 4とReactを統合する

前提条件

このガイドを始める前に、以下のものが必要です。

  • Ubuntu 18.04を搭載したElastic Compute Service (ECS)開発マシン。
  • Node.js 8.0.0.0+とNpm 3.0.0.0+がマシンにインストールされている。
  • ローカルにインストールされたMySQL 5.7+。

ステップ1 - Adonis APIプロジェクトの作成と依存関係のインストール

このステップでは、Adonisプロジェクトを足場にするのに役立つAdonis CLIをインストールし、アプリケーションのデータベース接続をセットアップするために必要な依存関係をインストールします。

ターミナルを開き、以下のコマンドを実行してAdonis.js CLIをグローバルにインストールします。

npm i -g @adonisjs/cli

これでインストールが完了したら、ターミナルのどこからでもadonisを実行できるようになるはずです。以下のコマンドを実行して、インストールしたCLIのバージョンを確認することで試すことができます。

adonis --version

次に、新しいAdonisアプリケーションを足場にする必要があります。任意のディレクトリに移動し、以下のコマンドを実行します。

adonis new adonis-events --api-only

新しいコマンドは adonis-events という新しいプロジェクトを足場にし、--api-only は生成されたプロジェクトが特に REST API 用であることを指定します。新しいコマンドは、新しいプロジェクトに必要なすべての npm 依存関係をインストールします。

生成されるプロジェクトは以下のような構造になっているはずです。

image.png

次に、データベース接続を設定する必要があります。Adonis は多くの SQL データベースをサポートしていますが、使用する特定のデータベース、この場合は MySQL 用のパッケージをインストールする必要があります。プロジェクトフォルダに移動し、以下のコマンドを実行します。

npm i --save mysql

このプロジェクトでは、.envというファイルにアプリケーションを設定するための環境変数が含まれています。データベース接続が mysql に設定されていることを確認してください。

[~/adonis-events/.env]
...
DB_CONNECTION=mysql
...

最後に、このプロジェクトで使用する MySQL データベースをローカルに作成する必要があります。ローカルにMySQLがインストールされている場合は、以下のコマンドを実行してデータベースを作成します。

mysql -u root
mysql> CREATE DATABASE events;

新しいデータベースを作成したら、.env ファイルの資格情報を作成したデータベースと一致するように更新します。

[~/adonis-events/.env]
...
DB_CONNECTION=mysql
DB_USERNAME=root
DB_DATABASE=events
DB_PASSWORD=
...

これが完了したら、REST APIとReactフロントエンドの作成に進みます。

ステップ2 - Reactフロントエンドを作成する

このステップでは、Reactを使ってアプリケーションのフロントエンドを作成します。webpack の設定や本番のビルドプロセスを気にすることなく、React アプリケーションを素早く足場にするために、crease-react-app という人気のあるパッケージを使用します。ローカルにcreate-react-appがインストールされていない場合は、以下のコマンドを実行してください。

npm i -g create-react-app

インストールしたら、新しいフォルダに移動し、以下のコマンドを実行して新しいReactプロジェクトを作成します。

create-react-app adonis-events-react

新たに生成されたReactプロジェクトの構造は以下のようになります。

image.png

新しく作成したReactプロジェクトを起動するには、Reactプロジェクトディレクトリに移動し、以下のコマンドを実行します。

npm start

これにより開発サーバーが起動し、デフォルトのブラウザで以下のようなReactアプリケーションが開くはずです。

image.png

最後に、基本的なスタイリングを行うためのBootstrap CSSフレームワークをインストールしてみましょう。

Reactプロジェクト内のsrcフォルダに入り、index.cssファイルを開きます。このファイルの内容を以下のように置き換えます。

[~/adonis-events-react/src/index.css]
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';

上記は、ブートストラップCSSファイルへのリンクをインポートしたもので、フロントエンドプロジェクトのスタイルを素早く設定するために使用できるすべてのスタイルとクラスが含まれています。

フロントエンドアプリケーションのセットアップが完了したので、データベースモデルと移行を開発しましょう。

ステップ3 - イベントの移行とモデルの作成

このステップでは、データベーステーブルをどのように構造化するかを決定する移行をセットアップし、このデータベーステーブルとの対話に役立つモデルをセットアップします。

まず、ターミナルでAdonisアプリケーションに移動し、次のコマンドを実行します。

adonis make:model Event -m

このコマンドは、モデルとそれに対応するマイグレーションを素早く作成する方法です。以下のような出力が得られます。

 create  app/Models/Event.js
 create  database/migrations/1541120387036_event_schema.js

作成したファイルは2つで、app/Models/Event.jsはこんな感じです。

'use strict'

const Model = use('Model')

class Event extends Model {
}

module.exports = Event

これは基本的なES6のクラスで、ベースとなるModelクラスを継承しています。見ての通り、このクラスには多くの静的メソッドがあり、データベースと簡単にやりとりするのに役立ちます。

2つ目のファイルは、database/migrations/1541120387036_event_schema.jsで、以下のようになっています。

'use strict'

const Schema = use('Schema')

class EventSchema extends Schema {
  up () {
    this.create('events', (table) => {
      table.increments()
      table.timestamps()
    })
  }

  down () {
    this.drop('events')
  }
}

module.exports = EventSchema

Adonisのマイグレーションは、ベースのSchemaクラスを拡張したES6クラスで、2つの重要なメソッドを含んでいます:upは、データベーステーブルの作成や変更を含むデータベースへの変更を行うために使用され、downはそれらの変更を反転させるために使用されます。up関数では、データベーステーブルの構造を記述するAdonisが提供するメソッドを呼び出します。

現時点では、up関数の中の関数this.create('events')がデータベース内にイベントテーブルを作成します。また、down関数のthis.drop('events’)は、必要に応じてデータベース内のイベントテーブルを削除します。

イベントテーブルには以下のフィールドがあります。

  • title - イベントのタイトル
  • start_date - イベントの開始日.
  • end_date - イベントの終了日.
  • location - 偶数の場所
  • price - イベントの費用

マイグレーションでは、以下のようにこれらのフィールドを定義します。

次に、このマイグレーションファイルを実行して、Adonisが接続されたデータベースにこれらの変更を影響させることができるようにする必要があります。

プロジェクトフォルダにあることを確認し、以下のコマンドを実行してください。

adonis migration:run

以下のような出力が表示されるはずです。

migrate: 1503250034279_user.js
migrate: 1503250034280_token.js
migrate: 1541120387036_event_schema.js
Database migrated successfully in 206 ms

このコマンドを実行すると、Adonisは各マイグレーションファイルを読み込み、これらのファイル内でup機能を実行します。したがって、現時点では、私たちのデータベースには、userstokenseventsの3つのテーブルが新たに作成されています。最初の2つのテーブルは、Adonisの新規インストール時にデフォルトで出荷されます。

データベースのセットアップが完了したら、REST APIの作成に進みます。

ステップ4 - REST APIの作成

このステップでは、REST APIを作成します。イベントに対するさまざまなアクションを処理するために、いくつかの API エンドポイントを作成します。作成するエンドポイントは以下の通りです。

  • [POST] /api/events は、イベントを作成
  • [DELETE] /api/events/:idは、 特定のイベントを削除
  • [GET] /api/eventsは、すべてのイベントのリストを取得

[POST]/api/eventsエンドポイントの実装

まずは、これらのエンドポイントを処理するためのコントローラを生成することから始めましょう。コントローラは基本的に ES6 クラスで、すべてのビジネスロジックを処理するメソッドを含んでいます。以下のコマンドを実行してください。

adonis make:controller EventController

以下のような出力が表示されるはずです。

> Select controller type For HTTP requests
 create  app/Controllers/Http/EventController.js

次に、この特定のエンドポイントを扱うルートを登録する必要があります。start/routes.jsファイルを開き、以下を追加します。

Routeクラスにはpostという静的関数があり、このメソッドはpostエンドポイントを登録します。最初のパラメータはパス、api/events、2番目のパラメータはこのエンドポイントへのリクエストを処理するコントローラとメソッドです。この場合、EventControllerstore メソッドです。つまり、このメソッドを作成する必要があります。EventControllerのように追加します。

Adonis は context と呼ばれるオブジェクトを注入しますが、この context には request オブジェクトと response オブジェクトを含む、requestに関する多くの関数とオブジェクトが含まれており、requestオブジェクトにはrequestに関するすべての情報が含まれています。現時点でのstoreメソッドでは、request.all()関数を呼び出すと、POSTリクエストで送信された全てのデータを返してくれます。今回は、イベントを作成するために、リクエストデータにtitle、start_date、end_date、location、priceが提供されていることを想定しています。

次に、このデータをデータベースに保存しなければなりません。データベース、特にeventsテーブルと通信するために、モデルを使用します。eventsモデルを使用すると、Adonisは自動的にeventsテーブルに接続します。

eventsモデルを使用して、create関数を呼び出しました。これは、POSTリクエストから取得したデータを使用して、自動的に新しい行をeventsデータベーステーブルに保存します。この関数は新しく作成されたイベントを返し、このeventsと一緒にJSONレスポンスを返します。

: Adonisのuse関数はNode.jsのrequire関数のラッパーです。これは名前空間を使用し、上で行ったようにファイルを簡単にrequireするのに役立ちます。ですから、require('../.../.../Models/Event.js’)の代わりに、単に `use('App/Models/Event') と書きます。

先ほど実装した/POSTエンドポイントを試してみましょう。そのためには、Adonis APIを実行する必要があります。

以下のコマンドでadonisサーバーを実行します。

adonis serve --dev
info: serving app on http://127.0.0.1:3333

新しく作成したPOST /api/eventsエンドポイントを試すために、任意のRESTクライアントを使用することができます。今回はPOSTMANを使ってみます。

操作結果は以下の通りです。

image.png

:POSTMANではContent-typeapplication/jsonに設定することを忘れないでください。

[取得] api/eventsエンドポイントの実装

このエンドポイントのルートを追加してみましょう。routes.jsファイルで、次のように追加します。

[~/adonis-events/start/routes.js]

Route.get(api/events', 'EventController.index')

これはエンドポイントを EventControllerindexメソッドにマッピングします。このコントローラでは、次のように追加してみましょう。

indexメソッドは、eventsモデルのall()関数を使用してeventsテーブルからデータベースのすべての行を取得します。これをPOSTMANで試してみると、以下のような結果が得られるはずです。

image.png

[削除] api/events/:id エンドポイントの実装

最後に実装しなければならないエンドポイントは、イベントを削除するためのものです。このエンドポイントはこれまでの他のエンドポイントとは少し異なり、動的パラメータを使用します。このエンドポイントのルートを登録してみましょう。

[~/adonis-events/start/routes.js]

Route.delete(api/events/:id', 'EventController.delete')

ルートの :id は動的パラメータで、削除したいイベントの特定の id を渡すことができます。このルートを EventControllerdelete メソッドにマッチさせます。

delete メソッドでは、ルートから取得した動的 ID を使用してイベントを検出し、検出されたイベントに対して delete 関数を呼び出します。以下に POSTMAN でのテストの様子を示します。

image.png

それだけです。Adonisで完全に機能するAPIを作るのはこんなに簡単なことなのです。フロントエンドに切り替える前にもう一つ。現時点では、Adonis APIは他のサーバーやドメインからのリクエストを許可しません。これはCORS保護と呼ばれるもので、不正なリクエストがAPIに行われないようにするために本当に重要です。この特定のケースでは、ReactフロントエンドがAPIリクエストを行うことを許可するようにAdonisに指示しなければなりません。これを行うには、Adonisには設定ファイルが既に設定されているので、アプリケーションのCORS設定を変更することができます。config/cors.jsに移動して、このファイルをこのように修正します。

[~/adonis-events/config/cors.js]

  origin: ['http://localhost:3000'],

ここでは、すべての認可されたオリジンの配列を作成し、この配列にリストされたすべてのドメインがAdonisアプリケーションにAPIリクエストを行うことができるようになります。クライアント側のReact開発サーバーのドメインは http://localhost:300 なので、これをorigin配列に追加しました。

次のステップでは、Reactフロントエンドに切り替えて、堅牢なクライアントサイドアプリケーションを構築するために、このデータの消費を開始しましょう。

ステップ5 - クライアントサイドコンポーネントを設定する

最初に設定するコンポーネントは、App コンポーネントです。これは、最初にアプリケーションにアクセスしたときに表示されるコンポーネントです。他の不要なファイルもすべてクリーンアップします。

不要なファイルを削除した後の現在のフォルダ構造は以下の通りです。

image.png

次に、単一のイベントを表現するために使用するコンポーネントを作成してみましょう。プロジェクトフォルダに移動し、srcフォルダ内にEvent.jsというファイルを作成します。

このコンポーネントは、現時点での静的データを表示し、単一のイベントを表示するためのテンプレートを表しています。

最後に、AddEvent.jsという別のファイルを作成します。これは、新しいイベントを追加するためのフォームとして機能します。

必要なコンポーネントの作成が完了したので、APIサービスを作成してみましょう。

ステップ6 - Axios APIサービスの作成

Adonis API と通信できるようにするために、シンプルな HTTP クライアントを使用します。このクライアントをインストールするには、Reactプロジェクトに移動し、次のコマンドを実行します。

npm install axios --save

これをインストールしたら、src フォルダに apiService.js というファイルを作成し、以下のクラスを追加します。

このクラスは axios のシンプルなラッパーであり、Adonis API への HTTP リクエストを行うためのヘルパー関数を提供します。コンストラクタでは、すべてのリクエストのBaseURLをAdonisアプリケーションのURLに設定します。

サービスの準備ができたので、これらのコンポーネントを機能させてみましょう。

ステップ7 - イベントの作成

イベントを作成するために、Create Eventボタンを用意します。ユーザーがこのボタンをクリックすると、イベントを作成するためのフォームを持つコンポーネントが表示されます。Appコンポーネントを以下のように変更してみましょう。

現時点では以下のようになっています。

image.png

まず、AddEventコンポーネントをインポートしました。showCreateEventFormというステート・プロパティを設定し、レンダー関数の中で、このプロパティが真であればAddEventコンポーネントをマウントし、そうでなければマウントしません。Create Eventボタンがクリックされると、このステート・プロパティの値を切り替えます。また、showCreateEventFormtrue の場合は Create Event ボタンに btn-danger クラスを動的に追加し、このプロパティが false の場合は btn-info を追加します。

次のステップは、このフォームを実際に動作させて、ユーザーがイベントの詳細を入力して保存できるようにすることです。

イベントの作成機能の実装

最初のステップは、ユーザーが入力した入力を取得することです。これを管理するためにステートを使います。ここにAddEvent.jsコンポーネントのアップデート版があります。

この時点では、入力フィールドが変更されるたびに、その値は自動的にこのコンポーネントの状態に設定されます。

現時点では、デフォルトのブラウザの日付ピッカーを使用しており、これには時間ピッカーが統合されていません。さらに、この日付ピッカーの動作はブラウザによって非常に一貫性がありません。そこで、start_dateend_dateのフィールドには、FlatPickrと呼ばれるサードパーティ製の日付ピッカーを使用します。この日付ピッカーをインストールするには、以下のコマンドを実行します。

npm i --save react-flatpickr

インストールできたら、このようにフォームを修正してみましょう。

まず、インストールしたFlatPickrコンポーネントをインポートします。前回の日付入力の代わりに、FlatPickrをマウントし、handleDateChangeと呼ばれる別のハンドラを渡します。これはほとんど同じことを行い、start_dateend_dateの値が変更されたときの状態に設定します。

さて、このイベントを保存するためのAPIリクエストを実際に行う時が来ました。これを行うために、Appコンポーネントから関数を渡し、呼び出されたときにイベントを保存します。Appコンポーネントをこのように修正してみましょう。

コンポーネントをこのように更新してみましょう。

まず、ApiServiceをインポートし、Appコンポーネントのコンストラクタで新しいインスタンスを作成します。次に、apiServiceからの関数であるstoreEventというAddEventコンポーネントにプロップを渡します。

次に、AddEventコンポーネントのCreate Eventボタンが作成された際に、このプロップを呼び出す必要があります。

Create Eventボタンがクリックされると、this.props.storeEvent()を呼び出し、状態をパラメータとして渡します。この関数は axios を呼び出し、イベントを保存するための HTTP リクエストを行います。開発者ツールで発信HTTPリクエストを確認すると、AdonisサーバーからのHTTPレスポンスが表示されているはずです。

すべてのイベントのリストが表示されるまでは、作成されたことを確信することはできませんよね?次のステップでは、Adonis APIからすべてのイベントのリストをフェッチして表示します。

すべてのイベントをフェッチして表示する

すべてのイベントを取得するために、コンポーネントがマウントされた直後に [GET] リクエストを行い、結果を取得したら、その結果をステートに設定してコンポーネントに表示します。これを実現するために、componentDidMountというReactのライフサイクルフックを追加してみましょう。この関数は、コンポーネントがマウントされるとすぐに実行されます。

まず、イベントという新しいステートプロパティを空の配列に設定します。次に、componentDidMount関数の中で、APIリクエストをしてすべてのイベントを取得し、レスポンスが返ってきたら、その結果をstateに設定します。この時点でReact Dev toolsなどのツールを使ってコンポーネントの状態を検査すると、すべてのイベントのリストが表示されているはずです。

次のステップは、このイベントのリストをコンポーネントに表示することです。このために作成したEventコンポーネントを使用します。App コンポーネントをこのように変更してみましょう。

各イベントをマッピングして、1つのイベントを所定の場所に表示します。現時点では、アプリケーションがマウントされると、対応するイベントのリストが表示されるはずです。

image.png

イベント コンポーネントはまだ動的ではありません。これは、特定のイベント(Appコンポーネントから渡された)のためのプロップを受け取り、代わりにそれらを表示する必要があります。この動作をサポートするように変更してみましょう。

これで、各イベントはeventプロップを介して渡されたカスタムデータを表示するようになりました。

現時点では、イベントを作成しても何も起こりません。より良いエクスペリエンスを得るために、App コンポーネントを修正して、AddEvent フォームを非表示にし、すべてのイベントを再度取得して、ユーザーが新しく作成されたイベントを見ることができるようにします。

最初に、fetchEventsという新しい関数を作成します。次に、storeEventという新しい関数を作成します。この関数はAPIを呼び出して新しいイベントを保存し、レスポンスを取得したらfetchEventsを呼び出してすべてのイベントを取得し、toggleShowCreateEventFormを呼び出してcreateイベントフォームを非表示にします。

ステップ8 - Deleteイベントの実装

最後のタスクは、イベントを削除できるようにすることです。App コンポーネント上でイベントを削除する関数を作成し、この関数をEvent コンポーネントのプロップとして渡し、ユーザーが削除ボタンをクリックするたびにこの関数が呼び出されます。Appコンポーネントは次のようになります。

新しく追加された関数deleteEventはAPIサービスを呼び出し、イベントが削除されると、再びfetchEventsを呼び出して、すべてのイベントの新鮮なコピーを取得します。最後に、Eventコンポーネントで、idパラメータを指定してこの関数を呼び出します。このようにEventコンポーネントを修正します。

すべての変更を行った後のプロジェクトの動作は次のようになります。

image.png

このアプリケーションが完成したことで、実際のプロジェクトの参考として使用できる最新のフルスタックJavascriptアプリケーションの基礎を手に入れることができます。

結論

このチュートリアルでは、Adonis.jsとReact.jsを使ってフルスタックのJavascriptデモアプリケーションを作成しました。REST APIの構築にはAdonis.js、APIの消費にはReact、アプリケーションのスタイリングにはBootstrap 4を使用しました。Adonis REST APIのソースコードはこちらのGitHubリポジトリに、ReactフロントエンドアプリケーションのソースコードはこちらのGitHubリポジトリにあります。

Adonisフレームワークについてもっと詳しく知りたい場合は、公式ドキュメントがとても便利で、ビデオチュートリアルも提供されています。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

プロジェクト内にtsconfigが複数あるときに、今開いているtsファイルに適用されるコンフィグを簡単に開く for VSCode

front,server,api,testなどで、tsconfigを分けて管理したいときに、
当該tsファイルに適用しているtsconfigファイルを簡単に開く方法の紹介。

  • 該当のファイルを選択した状態で、VSCodeの右下にあるTypeScriptのバージョンを選択
    スクリーンショット 2020-06-25 12.06.32.png

  • コマンドパレット上に選択子がでてくるので、 tsconfigを開くを選択
    スクリーンショット 2020-06-25 12.08.10.png

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

JavaScriptだけでIoTネットワークを構築する方法

この記事では、Alibaba CloudIoTプラットフォーム上でJavaScriptを使って開発者が簡単にIoTネットワークを構築する方法を説明しています。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

概要

この例では、開発ハードウェアとしてDeveloper Kit(AliOS Thingsを移植したもの)を使用して、基板実装の温度センサからLink Developへのデータアップロードを実装し、デモにはWeb Appを使用しています。

image.png

開発プロセス

環境の設定

AliOS Thingsは、アリババクラウドのIoTチームが開発中のIoT指向で拡張性の高いIoTオペレーティングシステムです。フロントエンド開発者の要求に応えるため、AliOS Thingsは、これまでCとC++でしか実装されていなかったIoT開発のためのJavaScriptをサポートするJavaエンジン「BoneEngine」を統合しています。BoneEngineは構文をシンプルにすることで、リソースが限られている組み込み機器でもJavaScriptアプリケーションを実行できるようにしました。

BoneEngineを使ってデバイスサイドの開発を行うには、事前に環境設定を行う必要があります。設定手順は以下の通りです。

1、Node.js(バージョン6.4.0以降、フロントエンド開発の場合はバージョン8.9.0以降を推奨)をインストールします。
2、シリアルポートまたはネットワーク経由でapp.binを更新するためのサポートを提供するbe-cliツールをインストールします: npm i be-cli -g -P先行のグローバルインストールが完了した後にbeを実行してもコマンドが見つからないと表示される場合は、npm prefix -gコマンドを実行してNode.jsのモジュールインストールパスを取得し、出力にあるグローバルインストールパスを環境変数の "Path "エントリに追加するだけでOKです。
3、Windowsユーザーのみ)Python、Visual Studio、AliOSプラグインをインストールします。続いて、ハードウェアUSBドライバをインストールします。
4、Gitをインストールし、AliOS Thingsのソースコードをダウンロードします: git clone git@github.com:alibaba/AliOS-Things.git

これで環境が設定できたので、開発を開始する準備ができました。

クラウド上での開発

今回はLink Developを使用して、Alibaba Cloud上にIoTネットワークを設定します。Create Project(任意の名前をつけても構いません)>Device Development>Add Productを選択します。製品の種類はOtherを選択し、通信方式は「WiFi」を選択し、データ形式は「Alink」を選択します。

Product DetailsでAdd Custom Featuresを選択し、以下の機能を追加します。

image.png

Device Development > Add Debugging Devices を選択し、デバイスの三重項に注意してください。

デバイスでの開発

コンパイラでAliOS Thingsのソースコードディレクトリを開き、AliOS-Things/framework/tinyengine/samples/app/lcd_shtc1_mqtt/device.jsファイルを開き、Three Device Elementsを先ほど取得したDevice Tripletsに変更します。変更内容を保存します。

ターミナルを使ってサンプルアプリケーションをコンパイルします。be -p samples/app/lcd_shtc1_mqtt.

Developer Kit をコンピュータに接続し、be connnectを実行します。

コンパイルしたbinファイルをデバイスに書き込みます。be push app.bin

デバイスを再起動します。デバイスを再起動すると、デバイスは自動的にこのファイルをロードして実行し、同時にLink Developプラットフォームにアップロードします。

image.png

Webアプリでの開発

すぐに始められるように、ここではLink Developの新バージョンに統合されたWeb Appのビジュアル作成機能を利用します。必要なのは、吸湿器の部品をドラッグしてデータに接続するだけです。

image.png

経験豊富なフロントエンド開発者は、Bone.jsフレームワークを使ってハードコアなプログラミング機能を実行してみてください。ここでも JavaScript のみで実装できます: https://bone.aliyun.com/bone-web/bonewebsdk.html?name=wpgl7p.

結論

AliOS Things TinyEngineとLink Develop Bone.jsのミックスにより、フロントエンドの開発者は他のプログラミング言語を学ぶことなく、迅速かつシームレスにIoT開発を始めることができます。これは、Alibaba Cloud IoT Platformの互換性と独自性をも表しています。

Alibaba Cloud IoT Platformの詳細については、www.alibabacloud.com/product/iot をご覧ください。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

意外と複雑な「クリップボードにコピー」

メールアドレスなどでよく見かける、「クリックしてクリップボードにコピー」
よく見かけるが故にcopy('コピーしたい文言')みたいな感じで簡単に出来ると思ったら、意外と複雑だったので備忘録です。

clipboardCopy: function(e) {
        e.preventDefault()
        const targetSentence = 'コピーしたい文言'
        let input = document.createElement('input')
        input.readOnly = true
        document.body.appendChild(input)
        input.value = targetSentence
        input.select()
        document.execCommand('copy')
        document.body.removeChild(input)
      }

ざっくりとした流れ

①inputを生成する
let input = document.createElement('input')

②body内の一番最後の要素に、①のinputノードを追加する
document.body.appendChild(input)

③inputの中に、任意のコピーしたい文字列を挿入する
input.value = targetSentence

④inputの内容をselectする
input.select()

⑤選択している内容に対して、コピーコマンドを実行する
document.execCommand('copy')

⑥inputは用済みなのでさようならする
document.body.removeChild(input)

なぜinputか

select()を使えるのがキモなんだと思います。

補足

execCommand()について

コマンドキーを押して出来ることがだいたいできるみたいです。
cutやpasteもできます。ただし、pasteはバックグラウンドのみ。
(個人情報などがクリップボードに入っていた場合勝手に抜き取られる可能性があるため。)
ちなみにIEだとpasteも許容しているみたいです。さすがですね。
https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipboard

ん?と思ったのは、これ非推奨なんですよね。
ほかにクリップボードとやりとりできる代替え手段があるのでしょうか?また調べてみます。
https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand

時間がかかったところ

iOSで確認すると、クリックと同時に勝手にスクロールされる

iOSですと、input系を選択(タップ)した時にその要素がズームされたり、中央よりちょい上の位置にスクロールされたりします。
この例では、作成したinputをbodyの中の最後の子ノードのうしろに追加しているので、ページの最後にinputが出現している形になります。

そのため、テキストリンクをタップした瞬間に、ページ下方へのスクロールが走ります。
よかれと思ってやってくれているんだろうけども、inputはユーザが認識するよりも先に消えてしまうのでなんの意味もないです…。

ここで、inputをreadonlyに設定することで、スクロールを防ぎます。
input.readOnly = true

ズームされる

こちらはjsの問題ではなく、iOSの仕様と関係しています。
iOSですと16px未満のfont-sizeをもつinputをタップすると、問答無用でズームされます。
上記の実装でも目にはinputはみえませんが、input.selectをしているので謎にズームされます。
ズーム禁止にしてしまうとユーザビリティに影響が出るので、ひとまずSPでのfont-sizeを16pxにしてことなきを得ました。
(これを知ってたら最初から16pxにしてたのに…次回から気をつけよう)

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

window.location.hrefで別タブでリンクを開く方法

window.location.hrefを使用すると、外部URLへ遷移させることができます。

window.location.href = 'https://qiita.com'

上記では、同一タブで開くので、別タブで外部サイトを開かせる場合はwindow.open()を使います。

window.open()で外部URLを開く

const url = 'https://qiita.com'
window.open(url, '_blank')

上記のコードで、別タブで外部URLを開くことが可能です。

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

DOMとは何か(初心者からの目線)

DOMとは何か

そもそもDOMって一体何?

https://ja.wikipedia.org/wiki/Document_Object_Model

DOMはDocumentObjectModelのこと。
HTLMなどの階層化、構造化された文章のこと。

階層化された文章のオブジェクト群だよ(各要素群の集合だよ)という理解。

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

PropsとStateの違い

Reactを学習していて、PropsとStateの違いってなんやろ
とふとした時に思い調べました。

学習メモなのでふわっとしていますが、どうぞよしなに。

*間違いがあれば教えていただけると、ありがたいです。

【Propsとは】
・オブジェクト
・親コンポーネントからもらう値
・Propsを受け取る側の子コンポーネントではPropsを変化させれない
・親コンポーネントから変化させられる

【Stateとは】
・オブジェクト
・コンポーネント自身で定義する
・初期値を設定したらsetStateでStateの値を変更する

<まとめ>

Props→上から下に流れるデータ
State→コンポーネント自身が持つデータ

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

インクリメンタルサーチ

はじめに・目的

  • search controllerのhtml viewでの検索機能だと、ワード全て入力して検索しないといけない。
  • jsonを使ったインクリメンタルサーチだと、非同期で検索可能。
  • テーブル内で検索が頻繁に行われるカラムにインデックスを設定することで検索の高速化を図る。
  • 今回はtweetsテーブルのtextカラムにインデックスを貼ることで、データの検索を高速化する。
terminal
$ rails g migration AddIndexToTweets
マイグレーションファイル
class AddIndexToTweets < ActiveRecord::Migration
  def change
    add_index :tweets, :text, length: 32
  end
end

→migrate実行

1.ルーティングなどAPI側の準備

Formatごとの条件分岐

app/controlers/tweets_controller.rb
def search
    @tweets = Tweet.search(params[:keyword])
    respond_to do |format|
      format.html
      format.json
    end
end
#投稿情報を取得したら、jbuilderを使ってJavaScript側に返す。
#検索結果は、複数の投稿情報を表示させます。
#そのため、複数の投稿情報が格納された配列を返すようなjbuilderの記述にする必要があります

json.jbuilderの作成

image.png

app/views/tweets/search.json.jbuilder
json.array! @tweets do |tweet|
  json.id tweet.id
  json.text tweet.text
  json.image tweet.image
  json.user_id tweet.user_id
  json.nickname tweet.user.nickname
  json.user_sign_in current_user
end

JSON形式のデータを配列で返したい場合は、上記のようにarray!を使用。

2. テキストフィールドを作成

views/tweets/index.html.erb
<%= form_with(url: search_tweets_path, local: true, method: :get, class: "search-form") do |form| %>
  <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "search-input" %>
  <%= form.submit "検索", class: "search-btn" %>
<% end %>

3.テキストフィールドに文字入力するたびに、イベント発火

  • search.js File作成 image.png
app/assets/javascripts/search.js
$(function() {
  $(".search-input").on("keyup", function() {
    var input = $(".search-input").val();
  });
});
//キーを離したら処理をさせたいときはkeyupメソッドを使用

4.イベント時に非同期通信できるよう$.ajaxメソッドの記載

app/assets/javascripts/search.js
$(function() {
  $(".search-input").on("keyup", function() {
    var input = $(".search-input").val();
    $.ajax({
      type: 'GET',              //HTTPメソッドはGET
      url: '/tweets/search',    //rails routesで確認
      data: { keyword: input },
      dataType: 'json'
    })
  });
});

5.非同期通信の結果を得て、HTMLを作成

app/assets/javascripts/search.js
     .done(function(tweets) {
      search_list.empty();
      if (tweets.length !== 0) {        //tweetsの数が0でない時、
        tweets.forEach(function(tweet){ //forEachメソッド
          appendTweet(tweet);
        });
      }
      else {
        appendErrMsgToHTML("一致するツイートがありません");
      }
    })
    .fail(function() {    //error時のaleart実装
      alert('error');
    });
  });
});
  • forEachは、与えられた関数を配列に含まれる各要素に対して一度ずつ呼び出します
search.js変数・メソッド定義
  var search_list = $(".contents.row");

  function appendTweet(tweet) {
    if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){
      var current_user = `<li>
                            <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a>
                          </li>
                          <li>
                            <a href="/tweets/${tweet.id}" data-method="delete" >削除</a>
                          </li>`
    } else {
      var current_user = ""
    }

    var html = `<div class="content_post" style="background-image: url(${tweet.image});">
                  <div class="more">
                    <span><img src="/assets/arrow_top.png"></span>
                    <ul class="more_list">
                      <li>
                        <a href="/tweets/${tweet.id}" data-method="get" >詳細</a>
                      </li>
                      ${current_user}
                    </ul>
                  </div>
                  <p>${tweet.text}</p><br>
                  <span class="name">
                    <a href="/users/${tweet.user_id}">
                      <span>投稿者</span>${tweet.nickname}
                    </a>
                  </span>
                </div>`
    search_list.append(html);
  }

  function appendErrMsgToHTML(msg) {
    var html = `<div class='name'>${ msg }</div>`
    search_list.append(html);
  }

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

Fetch APIでPHPにデータを送信する方法

画面遷移をせず、ajaxでデータをPHPに渡す方法です。

下記は「ボタンをクリックした時にPHPにデータを渡す」処理についてのサンプルです。

Javascript

id="start"のボタンがあるとします。

index.html
<div class="Container">
   <button class="btn" id="start">start</button>
</div>

vanilla Javascriptでは、下記のように記述します。

main.js
start.addEventListener('click', () => {
        const postData = new FormData; // フォーム方式で送る場合
        postData.set('firstName', 'hoge'); // set()で格納する
        postData.set('lastName', 'fuga');

        const data = {
          method: 'POST',
          body: postData
        };

        fetch('test.php', data)
          .then((res) => res.text())
          .then(console.log);
    });

PHP

PHP側で、$_POST連想配列に値が保存されます。

test.php
<?php
echo $_POST['firstName']; // hoge

参考

github/fetch
MDN/Fetch API
MDN/Promise

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

ReactでSemantic Uiを 使ってみる

■semantic ui react

yarn add semantic-ui-react

※フォントを変えたいのでsemantic-cssは読み込まない

LESSを読むためにcracoをインストール

yarn add @craco/craco @semantic-ui-react/craco-less semantic-ui-less --dev

Package JSONを書き換え

{
  "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "craco eject"
  }
}

craco.config.jsを作成

module.exports = {
  plugins: [{ plugin: require('@semantic-ui-react/craco-less') }],
}

必要なbuildツールをインストール

npx @semantic-ui-react/bootstrap

インストールされた新しいフォルダ内にあるtheme.configの設定を変更
src/semantic-ui/theme.config

/*******************************
            Folders
*******************************/

@themesFolder : 'themes';
@siteFolder  : '../../src/semantic-ui/site';


/*******************************
         Import Theme
*******************************/

@import (multiple) "~semantic-ui-less/theme.less";
@fontPath : '../../../themes/@{theme}/assets/fonts';

Index.jsでimport

import 'semantic-ui-less/semantic.less'

font の変更

src/semantic-ui/site/globals/site.variables
@headerFont        : 'Muli', 'Noto Sans JP', sans-serif;
@pageFont          : 'Muli', 'Noto Sans JP', sans-serif;
@googleFontFamily  : 'Noto+Sans+JP:wght@300;400;700|Muli:wght@300;400;700';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.jsでvercelを使って環境変数を設定するメモ

環境変数設定メモ

vercel では、環境変数を本番環境、プレビュー環境、および開発環境用に簡単に分けることができる。


手順

1. vercle プロジェクト設定の[ 全般]ページ に移動して、[環境変数] セクションを見つける。

スクリーンショット 2020-06-25 1.12.55.png

2.環境変数の名前を入力する。

たとえば、Node.jsを使用していて、SENTRY_KEYという名前の環境変数を作成すると、コード内でprocess.env.SENTRY_KEYで使用できるようになる。

環境 説明
Production 環境変数が次の本番デプロイメントに適用される。本番デプロイメントを作成するには、デフォルトのブランチにcommitをpushする。
Preview 環境変数は、プレビューデプロイメントに適用される。プレビューデプロイメントは、ブランチ(例えば my-new-feature )にpushすると作成される。
Development 環境変数は、プロジェクトをローカルで、vercel dev または任意の開発コマンドで実行するときに使用する。開発環境変数をダウンロードするには、vercel env pull を実行する。


これだけ!


Enjoy Hacking!:sunglasses:

参考

https://vercel.com/docs/v2/build-step?query=envirome#development-environment-variables

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

iOSで100vhが効かない問題で、楽な方法をNuxt.jsでやってみる

iOS safariで100vhがうまくいかない時に、
window.innerHeightを取って各要素のstyle属性に一個一個付ける、
というのをやりがちでしたが、楽な方法が無いかと探すとカスタムプロパティが使えるそうなので、
その手順をNuxt.js版で書きます。

mixinを作る

--whという名前でカスタムプロパティを定義する。
ロード時と、リサイズ時にwindow.innerHeightを取って--whに入れる。

mixins/device.js
export default {
  data: () => ({
    style: {
      '--wh': '100vh'
    }
  }),
  mounted() {
    this.$nextTick(() => {
      this.getWindowSize()
      window.addEventListener('resize', this.getWindowSize)
    })
  },
  methods: {
    getWindowSize() {
      this.style['--wh'] = `${window.innerHeight}px`
    }
  }
}

mixinを読み込む

mixinを読み込んで、styleオブジェクトをwrapper要素のstyle属性に入れる。

layouts/default.vue
<template lang="pug">
.l-wrap(:style="style")
  nuxt
</template>

<script>
import deviceMixin from '~/mixins/device'

export default {
  mixins: [deviceMixin]
}
</script>

カスタムプロパティを使う

heightにvar(--wh, 100vh)と書いて、使用する。
書き方: var(設定したカスタムプロパティ名, プロパティが無い時用の初期値)

pages/index.vue
<template lang="pug">
.p-top
</template>

<script>
export default {}
</script>

<style lang="stylus" scoped>
.p-top
  height var(--wh, 100vh)
</style>

まとめ

これで、iOSのツールバーを考慮した実装が

  1. wrapperでwindow.innerHeight取って、変数に格納する
  2. wrapperかそれ以下の小要素でheight var(--wh, 100vh)する

という形でほぼCSSで指定出来るようになりました。

IEはまだ未検証ですが、Nuxt.jsの場合はheight var(--wh, 100vh)とすると、
height 100vhを自動的に手前に追加してくれるようなので、
varが認識されなくても問題なくvhできるかと思います。

<style lang="stylus" scoped>
.p-top
  height 100vh
  height var(--wh, 100vh)
</style>

※mixinを使ったのはlayoutsが複数あるときに、各所に同じ処理を入れることになりそうで面倒だと思ったからなので、default.vueでmixinでやってる処理をそのまま書いても問題ないです。

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