20200703のJavaScriptに関する記事は30件です。

なぜ、Vue Composition APIを使うのか、理解する

はじめに

Vue3のリリースが迫ってきました。
順調に行けば、公式リリースは8月上旬のようです。楽しみですね。

Our current target dates are mid July for the RC (release candidate) and early August for the official release of 3.0.
https://github.com/vuejs/rfcs/issues/183

Vue3の目玉の機能として、「Composition API」があります。

この記事では、「”なぜ” Composition APIを使うのか」について、まとめています。

「Composition APIの使い方」についての説明は、この記事では割愛します。

Composition APIを使う理由、なんもわからん

これまで、私自身、Composition APIを使う理由が、よくわかっていませんでした。

なんとなく、「setup関数でまとめて処理を登録しておく」くらいのことを雰囲気で理解していました。

「Composition APIで書くほうがイケてるし、カッコいい(重要)」くらいのノリで

メリットもよく分からない状態でした。

62783026-810e6180-ba89-11e9-8774-e7771c8095d6.png

Composition APIについて調べていると、↑の図、よく見ませんか?

既存APIとComposition APIのコードを比較して、メリットを提示しているようですけど

私は

そんなに違わないように見える」

違いがよくわからん

っといった感想を持ちました。

私と同じように

「Vueに新しく、Composition APIってのが出るらしいけど、なんとなく使おうかな〜」

「でも、なんのために使うんだっけ

と思っている方が多いのではないかなと考え、この記事を執筆しています。

この記事が、誰かのお役に立てれば幸いです。

Composition API RFC を読んだら、「なぜ、使うのか」を理解した

先日 @shwld が主催した

「Vue Composition APIを知る会」

で、Composition APIのRFCを読む機会があり、そこで改めて「”なぜ” Composition APIを使うのか」理解することができました。

IMG_0559.jpg

Composition APIの公式の説明資料として、以下のページにRFCがあります。
Composition API (https://vue-composition-api-rfc.netlify.com)

↑を読むと、「”なぜ” Composition APIを使うのか」答えが書いてありました。

以下にRFCに書いてある内容をまとめます。

Composition APIとは

Composition APIは、
ロジックを合成関数(composition function)カプセル化することで、コンポーネント間でのロジックを再利用を可能にするAPIです。
(React HooksのVue版みたいなもの)

サンプルコード

<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })

    function increment() {
      state.count++
    }

    return {
      state,
      increment
    }
  }
}
</script>

Vueの作成者であるEvan You氏いわく、Composition APIを提案する背景となった動機について、次のように説明しています。

論理合成(logic composition)は、プロジェクトをスケールアップする場合には、おそらく最も深刻な問題のひとつです。さまざまなタイプのプロジェクトを扱うユーザは、さまざまなニーズに直面します。その中には、オブジェクトベースのAPIを使用して簡単に処理できるものと、できないものがあります。主な例としては、
1. 複数の論理タスクをカプセル化した、大規模な(数百行の)コンポーネント
2. 複数のコンポーネント間においてタスクにロジックを共有するニーズ

https://www.infoq.com/jp/news/2019/10/vue3-function-based-api-rfc/ より引用

”なぜ” Composition APIを使うのか

結論から言いますと、

「”なぜ” Composition APIを使うのか」

その理由は、

「ロジックの抽出と再利用」をするためです。

(他にも「TypeScript 型強化」などが理由として挙げられますが、この記事では割愛します)

ロジックの抽出と再利用

「ロジックの抽出と再利用」は、なぜするのか。

「ロジックの抽出と再利用」が必要な理由は、
複雑に肥大化したコンポーネントを、小分けにして関心事で分別し、クリーンな状態に整理するためです。

近年、Vue を使用して大規模なプロジェクトを構築することが増えました。

肥大化してメンテしにくいコンポーネントを目の当たりにして、我々エンジニアが苦しむことも増えました。

character_program_fat.png

肥大化したコンポーネントのコードは、依存関係が複雑で量も多くコードを読むこと自体難しくなります。

これまでのVue2.xでは、「複数のコンポーネント間でロジックを抽出して再利用するためのクリーンでコストのかからないメカニズム」が欠如していました。

これを解決するのが、Composition APIです。

Composition APIによって、コンポーネントのコードを整理する際に、開発者により高い柔軟性を提供します。
コンポーネント間で、ロジックを抽出して再利用することが、簡単になります。

game_ken_seiken.png

つまり、Composition APIは、肥大化したコンポーネントを小さく切り刻むための聖剣なのです。

Composition APIを使って「ロジックを抽出して再利用する」具体的なやり方について、以下の記事が参考になります。

vue-composition-apiを使って機能単位のコード分割と合成を試してみた
https://qiita.com/s_nagasawa/items/ef70032f996face318e5

逆に言えば、100行にも満たないような小さなコンポーネントをComposition APIで書き直しても、あまりメリットを享受できないです。
もし小規模〜中規模なVueプロジェクトにおいて、現状のVue2.xのクラスベースのコードでコンポーネントの肥大化に苦しんでいないのであれば、Composition APIで書き直してもコスパが悪いのでやめておいたほうがよいのではないかなと思います。
(新規プロダクトで、最初から全部Composition APIで書くのはありだと思います)

mixinは、使えない?

Vue2.xのロジックの再利用の仕組みとして、「mixin」があります。
しかし、「mixin」には、罠が多くあり、今では「mixin」を使うことはアンチパターンという認識です。
極力「mixin」は使わないようにしましょう。

なぜmixinがアンチパターンなのかについては、下記の記事に詳しく書かれてあります。

俺がやらかしたVue mixinのアンチパターンから学ぶmixinの使い方と代替案
https://aloerina01.github.io/blog/2018-12-25-1

Composition APIを使う際のデメリット

Composition APIを使うメリットは、理解できたかと思います。

じゃあ、デメリットはないのか?

あります。

デメリットを一言でいうと、「自由すぎ」です。

Composition APIによって、自由にロジックを切り出せるようになりますが、一方で、いままでVueが暗黙的に行ってくれていたレールから外れることを意味します。

どういう単位でロジックを切り出したり、共通化したり、といった疑問に対して、明確な正解がないのです。

つまり、Vueが管理している壁の中の世界から、壁の外の無秩序な荒野へと放り出されるのです。

無秩序な荒野で、テキトーに開発していると、隠れていた猛獣に襲われたり、食料が尽きたりといった致命的なアクシデントに見舞われます。計画的に外の世界を開拓しないと危険がいっぱいなのです。

「設計」や「アーキテクチャ」が重要になる

その荒野に秩序を与えるのは、我々、調査兵団(エンジニア)の「設計」です。

map_takara_chizu.png

今後は、「アーキテクチャ」が重要になります。

つまり、デメリットは、ちゃんと「設計」や「アーキテクチャ」を考えなければならないことです。

例えば、「クリーンアーキテクチャ」を理解して、実装していくことが求められます。

実装クリーンアーキテクチャ
https://qiita.com/nrslib/items/a5f902c4defc83bd46b8

「Vueの中の世界」と「外の世界」を意識して、自由への翼を獲得しましょう。

まとめ

https://vue-composition-api-rfc.netlify.com を読もう。

・Composition APIを使う理由は、「ロジックの抽出と再利用」のため。(TypeScriptの型強化の側面もあり)

・肥大化したコンポーネントを、小さく切り刻むための聖剣だと理解した。(mixin、お前はダメだ)

・もともと小さいコンポーネントをCompositon APIで書き直しても、あまりメリットはない

・今後はちゃんと「設計」「アーキテクチャ」を考えなければならない。

参考リンク

Composition API RFC
https://vue-composition-api-rfc.netlify.com

vue-composition-apiを使って機能単位のコード分割と合成を試してみた
https://qiita.com/s_nagasawa/items/ef70032f996face318e5

俺がやらかしたVue mixinのアンチパターンから学ぶmixinの使い方と代替案
https://aloerina01.github.io/blog/2018-12-25-1

実装クリーンアーキテクチャ
https://qiita.com/nrslib/items/a5f902c4defc83bd46b8

【Vue3に備える】実務で使うComposition APIについて考える
https://medium.com/finatext/composition-api-for-vue3-63631dbadcef

先取りVue 3.x !! Composition API を試してみる
https://qiita.com/ryo2132/items/f055679e9974dbc3f977

Composition API Demo
https://github.com/LinusBorg/composition-api-demos

API Reference
https://composition-api.vuejs.org/api.html

Vue Composition API v1-beta で使えるリアクティブ関連のAPI一覧
https://qiita.com/ryo2132/items/6dc51ede8082dea75812

Vue Composition API を使ったストアパターンと TypeScript の組み合わせはどのくらいスケールするか?
https://qiita.com/tmy/items/a545e44100247c364a71

Composition API 勘所など
https://webneko.dev/posts/notices-of-composition-api-in-vue3-eve

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

JavascriptでiPhone風の電卓アプリを作ってみた

こんにちは、Mottyです。
今回はJavascriptを使ってみました。

概要

今回はJavascriptの処女作で電卓アプリを作ってみました。
定番ですが、HTML・JavascriptによるWebアプリ作品です。

ソースコード

Calculator.html
<!DOCTYPE html>
<html lang = "en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content = "width = device-width, instial-sale = 1.0">
   <meta http-equiv= "X-UA-Compatible" content = "ie=edge">
   <title> Document</title>
   <link rel = "stylesheet" href="Design.css">

</head>
<body>
    <script src = "System.js"></script>
    <input id ="result" type = "text" size ="20" style = "width:208px"/>
<br/>
    <input type = "button" value = "7" onClick ="disp(7)"/> 
    <input type = "button" value = "8" onClick ="disp(8)"/> 
    <input type = "button" value = "9" onClick ="disp(9)"/> 
    <input type = "button" value = "/" onClick ="disp('/')"/>
<br/>
    <input type = "button" value = "4" onClick ="disp(4)"/>
    <input type = "button" value = "5" onClick ="disp(5)"/>
    <input type = "button" value = "6" onClick ="disp(6)"/>
    <input type = "button" value = "*" onClick ="disp('*')"/>
<br/>
    <input type = "button" value = "1" onClick ="disp(1)"/>
    <input type = "button" value = "2" onClick ="disp(2)"/>
    <input type = "button" value = "3" onClick ="disp(3)"/>
    <input type = "button" value = "-" onClick ="disp('-')"/>
<br>
    <input type = "button" value = "0" onClick ="disp(0)"/>
    <input type = "button" value = "C" onClick ="clear()"/>
    <input type = "button" value = "=" onClick ="enter()"/>
    <input type = "button" value = "+" onClick ="disp('+')"/>
</body>
</html>

Design.css
input{
    width:50px;
    height:50px;
}
textbox{
    width:200px;
    height:50px;
}
System.js
var EnterFlag = false;

function disp(n){
    var obj = document.getElementById("result");
    obj.value += n;
}

function enter(){
    var obj = document.getElementById("result");
    obj.value = eval(obj.value);
    EnterFlag = true;
}

function clear(){
    var obj = document.getElementById("result");
    obj.value == "0";
}

結果

しっかり表示してくれました。
 2020-07-03 22.37.39.png
パーツとメソッドの繋ぎ方がポイントということを分かっていれば
プログラミング言語が変わっても物自体はちゃんと作れそう。
(しかし配列とかクラスを使えばもっと綺麗なコードをかけたのでは・・・。)

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

メッセージ送信の非同期化

フォームが送信されたら、イベントが発火するようにしよう

スクリーンショット 2020-07-03 22.12.39.png
この記述の解説をします。
$(**)には、formのクラス名を記述します。
.on(
**,にはイベント名を記述します。
e.preventDefaulでは、非同期通信を行う為にデフォルトのイベントを止めています。

イベントが発火したときにAjaxを使用して、messages#createが動くようしましょう

スクリーンショット 2020-07-03 22.17.55.png
この記述の中のthisは、イベントの発火元であるFormの情報が入っています。
$(this).attr('action');は、Form情報のパスを取得しています。

messagesコントローラーの#createアクションでメッセージを保存し、respond_toを使用してJSON形式のリクエストに対してのレスポンスを返せるようにしましょう

スクリーンショット 2020-07-03 22.21.45.png
if @message.save
リクエストで送られてきた情報を保存している
respond_to do |format|
format.json
json方式で返している

その他アウトプット

スクリーンショット 2020-07-03 22.25.20.png
クラス名MessageFieldにappend(html)でHTMLを追加している

$(".submit-btn").prop('disabled', false);

送信ボタンを一度押すとリロードしないと押せなくなるが
prop('disabled', false);を送信ボタンクラスに記述する事によりロードせずに投稿ができる様になる

非同期に失敗した場合の処理

スクリーンショット 2020-07-03 22.31.17.png
失敗した場合エラーをアラートで知らせてくれます。
Doneの後に使います

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

Kinx ライブラリ - パーサ・コンビネータ(その1)

Kinx ライブラリ - パーサ・コンビネータ(その1)

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。今回はパーサ・コンビネータです。

前回 JIT ライブラリ を紹介しましたが、最後以下の言葉で締めくくりましたね。

これでパーサ・コンビネータとか実装して組み合わせたらたら、ちょっとした JIT 付き言語処理系が作れますね。

ええ、そこで急遽作りましたよ。パーサ・コンビネータ・ライブラリ。その名も Parsek。Parsec ならぬ Parsek。

インタフェースは Parsimmon を参考にしましたが、実装は全くの独自です。API はこんなに充実してませんが、それなりに使えます。インタフェースを追加するのは簡単なので、追々必要に応じて追加しよう。

長くなりそうなので記事を 2 回に分けようかと思います。

今回はこれを使って、四則演算文法をパースして AST(Abstract Syntax Tree = 抽象構文木)を作るところまでいきましょう。次回、最後には JIT コンパイルして実行するところまでいきます。

パーサ・コンビネータとは

小さい(単純な)パーサーを組み合わせて大きなパーサーを作るためのライブラリ。詳しくは他の記事に譲るとして、以下、サンプルを見ながらわかるようにしてみましょう。

サンプル

今回は趣向を変えて、サンプルを使いながら説明してみます。サンプルは正の整数(自然数)による四則演算です。簡単のために負の数は扱いません(結果が負になることはあり得ます)。

普通はここで BNF とか PEG とかの説明に入るのでしょうが、無視します。サンプルを通してまず動かすところからスタートです。

using Parsek

パーサ・コンビネータ・ライブラリは標準組み込みではないので、using しましょう。また、ライブラリはクラスとして提供されているのでインスタンス化しておきましょう。

using Parsek;

var $ = new Parsek();

何気に $ は変数名として使えます。

小さな(単純な)パーサーとは?

一つずつ例を挙げてみます。

数値をパースするパーサー

まず、正の整数を定義してみましょう。これが一つ目の小さな(単純な)パーサーです。一つ目ですが、いきなり正規表現です。まぁ、それほど難しくないのでわかりやすいでしょう。

ひとつだけ落とし穴なのは、使っているエンジン(=鬼車)が POSIX NFA ではない ので、長くマッチするほうを先に書かないといけません。簡単に言うと、以下の例では "123" はきちんと "123" でマッチしますが、逆(/[0-9]|[1-9][0-9]*/)に書くと最初に書いた [0-9] にマッチして検索をやめてしまうので "1" となって "23" にマッチしません。注意しましょう。

var number = $.regex(/[1-9][0-9]*|[0-9]/);

これでこの number というパーサーは数値(が書かれた文字列)をパースできるようになります。やってみましょう。

実際にパースを行うのは parseAll() メソッドです。parse() というのもありますが、これは途中で終了しても成功するメソッドで、通常は内部で使われます。parseAll() の場合は全て解析し終わって後処理まで実施して結果を返します。

using Parsek;

var $ = new Parsek();
var number = $.regex(/[1-9][0-9]*|[0-9]/);

System.println(number.parseAll("0"));       // => {"position":1,"status":1,"value":"0"}
System.println(number.parseAll("10"));      // => {"position":2,"status":1,"value":"10"}
System.println(number.parseAll("129"));     // => {"position":3,"status":1,"value":"129"}
System.println(number.parseAll("abc"));     // => {"position":0,"status":0,"value":null}
System.println(number.parseAll("0129"));    // => {"position":1,"status":0,"value":null}

復帰値の position はパースした文字列の完了位置で、status が成功・失敗(1 が成功)、value が実際にパースが成功した文字列です。見て分かる通り、失敗すると valuenull です。

しかしよく見ると value は文字列ですね。文字列を解釈しているだけなので当たり前です。ここで、value に対して変換を行うメソッドが .map() です。以下のように変換用の関数を与えます。

using Parsek;

var $ = new Parsek();
var number = $.regex(/[1-9][0-9]*|[0-9]/).map(&(value) => Integer.parseInt(value));

System.println(number.parseAll("129"));     // => {"position":3,"status":1,"value":129}

数値になりましたね。上記の場合、単に値をパススルーしているだけなので、Integer.parseInt を直接渡しても同じです。

var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt);

このほうが簡潔ですね。

四則演算の演算子をパースするパーサー

演算子によって優先度が違うので 2 つに分けます。

  • + または -
  • * または / または %

一文字の or を解釈するのに便利なのが $.oneOf() です。以下のように使います。

var addsub = $.oneOf("+-");
var muldiv = $.oneOf("*/%");

簡単ですねー。早速試してみましょう。

using Parsek;

var $ = new Parsek();
var addsub = $.oneOf("+-");
var muldiv = $.oneOf("*/%");

System.println(addsub.parseAll("+"));       // => {"position":1,"status":1,"value":"+"}
System.println(addsub.parseAll("-"));       // => {"position":1,"status":1,"value":"-"}
System.println(addsub.parseAll("*"));       // => {"position":0,"status":0,"value":null}
System.println(muldiv.parseAll("*"));       // => {"position":1,"status":1,"value":"*"}
System.println(muldiv.parseAll("/"));       // => {"position":1,"status":1,"value":"/"}
System.println(muldiv.parseAll("%"));       // => {"position":1,"status":1,"value":"%"}
System.println(muldiv.parseAll("a"));       // => {"position":0,"status":0,"value":null}

期待通りです。

カッコをパースするパーサー

もう一つ、数値演算には必要なカッコを解釈させましょう。特定の文字列にマッチするパーサーは $.string() を使います。ここでは 1 文字ですが、何文字の文字列でも OK です。

var lbr = $.string("(");
var rbr = $.string(")");

これも試してみるとうまく動きます。$.string() の効果を見るために別の文字列でも試してみましょう。

using Parsek;

var $ = new Parsek();
var lbr = $.string("(");
var rbr = $.string(")");
var hoge = $.string("hoge");

System.println(lbr.parseAll("("));          // => {"position":1,"status":1,"value":"("}
System.println(lbr.parseAll(")"));          // => {"position":0,"status":0,"value":null}
System.println(rbr.parseAll("("));          // => {"position":0,"status":0,"value":null}
System.println(rbr.parseAll(")"));          // => {"position":1,"status":1,"value":")"}
System.println(hoge.parseAll("hoge"));      // => {"position":4,"status":1,"value":"hoge"}
System.println(hoge.parseAll("fuga"));      // => {"position":0,"status":0,"value":null}

正しくマッチしているのが分かります。

組み合わせるとは?(コンビネーター)

これで小さな(単純な)パーサーという道具が揃いました。

var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt);
var addsub = $.oneOf("+-");
var muldiv = $.oneOf("*/%");
var lbr = $.string("(");
var rbr = $.string(")");

これらを組み合わせてみましょう。ここで PEG を出しておきます。BNF でもいいですが、PEG のほうがコンビネーターには合ってますね。文法はこうですよ、というのを示しておかないと何やってるかわからなくなりそうですので。意味は追々触れていきます。

number <- regex(/[1-9][0-9]*|[0-9]/)
addsub <- '+' / '-'
muldiv <- '*' / '/' / '%'
lbr <- '('
rbr <- ')'

expression <- term (addsub term)*
term <- factor (muldiv factor)*
factor <- number / (lbr expression rbr)

PEG の優先度付選択の記号 / と除算の指定 '/' が紛らわしいですが、よく見ると分かります。

トップダウン、ボトムアップどちらもありですが、ここではボトムアップでパーサーを構築していきます。

factor

まずは factor です。

factor <- number / (lbr expression rbr)

factornumberlbr expression rbr か、になります。プログラムにそのまま落とせます。ここで使うのは以下のメソッドです。

  • ここではまだ expression が定義されていないので、遅延評価させるために $.lazy() を使います。$.lazy() を使うと実際に評価されるときにパーサーが作られます。
  • どちらかを選ぶ、というメソッドは $.alt() です。複数のものから最初に成功したパーサーの結果を返します。
  • lbr expression rbr というように複数のものが連続している、ということを表すのが $.seq() です。

さて書いてみましょう。expression は事前に宣言だけしておきます。

var expression;
var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr)));

term

次は term です。

term <- factor (muldiv factor)*

これは、factor の後に (muldiv factor) が 0 回以上続く、という意味です。0 回も許されるので、何も続かない、というのも OK です。muldiv factor といった感じに並べるのはさっきの lbr expression rbr と同じで連続していることを意味します。ここで使うメソッドは以下です。

  • 0 回以上の繰り返し、はパーサーに対して .many() を指定します。

では定義してみましょう。

var term = $.seq(factor, $.seq(muldiv, factor).many());

これで term が定義できました。

expression

最後に expression です。形は term と一緒ですね。

expression <- term (addsub term)*

そのまま書いてみましょう。

expression = $.seq(term, $.seq(addsub, term).many());

これでパーサーが揃いました。試しにパースしてみましょう!

パース

一旦、ソースコードを全部載せてみます。とはいってもそんなにないですね。

using Parsek;

var $ = new Parsek();
var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt);
var addsub = $.oneOf("+-");
var muldiv = $.oneOf("*/%");
var lbr = $.string("(");
var rbr = $.string(")");

var expression;
var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr)));
var term = $.seq(factor, $.seq(muldiv, factor).many());
expression = $.seq(term, $.seq(addsub, term).many());

// parse expression!
System.println(expression.parseAll("1+2*3+2*(14-2)"));
// => {"position":14,"status":1,"value":[[1,{}],[["+",[2,[["*",3]]]],["+",[2,[["*",["(",[[14,{}],[["-",[2,{}]]]],")"]]]]]]]}
System.println(expression.parseAll("1+2*3+2*(14-2-)"));
// => {"position":7,"status":0,"value":null}

最初のは(長いですが)成功したことが分かります。結果を読むのは大変ですが、これは後で整形しましょう。そして、2 つ目は失敗していることが分かります。最後の (14-2-) がどの規則にもマッチしてないからですね。

ではこの結果を整形していきましょう。活躍するのは number で使った .map() です。

カッコの式

まず、$.seq(lbr, expression, rbr) の部分です。$.seq() は値として結果の配列を返します。カッコの式というのは、値としてはカッコは不要で中にある式の結果だけあればいいですね。ということで、以下のように変えます。

var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr).map(&(value) => value[1])));

修正すると結果は次のようになります。

System.println(expression.parseAll("1+2*3+2*(14-2)"));
// => {"position":14,"status":1,"value":[[1,{}],[["+",[2,[["*",3]]]],["+",[2,[["*",[[14,{}],[["-",[2,{}]]]]]]]]]]}

ちょっと短くなりましたね。

term、expression

次に、termexpression です。ここでは、後で解析するために AST(Abstract Syntax Tree = 抽象構文木)の形に整形するようにしましょう。基本的には二項演算子なので、LHS(Left Hand Side = 左辺値)と RHS(Right Hand Side = 右辺値)と演算子(Operator)の組み合わせのオブジェクトを作ります。

ここで、$.seq() をやめて、$.seqMap() を使うように変更します。これは $.seq().map() を一緒にしたようなもので、結果リストを引数として最後の引数に指定した関数にコールバックしてくれる便利なメソッドです。こんな風に使います。

var term = $.seqMap(factor, $.seq(muldiv, factor).many(), &(first, rest) => {
    var expr = first;
    for (var i = 0, l = rest.length(); i < l; ++i) {
        expr = { lhs: expr, op: rest[i][0], rhs: rest[i][1] };
    }
    return expr;
});

firstfactor の結果で、rest$.seq(muldiv, factor).many() の結果です。なので、rest は各要素が [演算子, 右辺値] の形の配列です(空配列の場合もある)。それを AST の形に整形しています。結果、例えば "2 * 3 * 4" みたいなものは以下のように整形されます。

  • コールバック時
    • まず、first2
    • rest[['*', 3], ['*', 4]]
  • expr2 が入る
  • ループに入り、expr{ lhs: 2, op: '*', rhs: 3 } になる。
  • もう一つ要素があるので、expr{ lhs: { lhs: 2, op: '*', rhs: 3 }, op: '*', rhs: 4 } になる。

左側の枝が伸びていく形の AST になります(これを左結合という)。今回の演算子は全て左結合です。

expression も一緒なので、同じように書きましょう。中身は全く同じですなので関数化して使いまわしましょう。

function makeAST(first, rest) {
    var expr = first;
    for (var i = 0, l = rest.length(); i < l; ++i) {
        expr = { lhs: expr, op: rest[i][0], rhs: rest[i][1] };
    }
    return expr;
}
var term = $.seqMap(factor, $.seq(muldiv, factor).many(), makeAST);
expression = $.seqMap(term, $.seq(addsub, term).many(), makeAST);

すっきりしました。

では、プログラム一式です。たったこれだけの定義で四則演算を(演算子の優先順位も考慮された形で)パースできてしまいます。素晴らしいですね!

using Parsek;

function makeAST(first, rest) {
    var expr = first;
    for (var i = 0, l = rest.length(); i < l; ++i) {
        expr = { lhs: expr, op: rest[i][0], rhs: rest[i][1] };
    }
    return expr;
}

var $ = new Parsek();
var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt);
var addsub = $.oneOf("+-");
var muldiv = $.oneOf("*/%");
var lbr = $.string("(");
var rbr = $.string(")");

var expression;
var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr).map(&(value) => value[1])));
var term = $.seqMap(factor, $.seq(muldiv, factor).many(), makeAST);
expression = $.seqMap(term, $.seq(addsub, term).many(), makeAST);

// test
System.println(expression.parseAll("1+2*3+2*(14-2)").value.toJsonString(true));

結果はこうなります。うまくいってますね!

"lhs": {
    "lhs": 1,
    "op": "+",
    "rhs": {
        "lhs": 2,
        "op": "*",
        "rhs": 3
    }
},
"op": "+",
"rhs": {
    "lhs": 2,
    "op": "*",
    "rhs": {
        "lhs": 14,
        "op": "-",
        "rhs": 2
    }
}

おわりに

さて、目的の AST ができました。次回、これを解釈して実行させます。せっかく作った JIT ライブラリも使いますよ!

ではまた次回!

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

ScrollReveal.jsでええ感じのパララックス効果を実装する

スクロールすると画像がふわって出てくる感じのを実装する

簡単にパララックス効果を実装できる便利ライブラリをcdnで以下のようにheadタグに記述
<script src="https://unpkg.com/scrollreveal"></script>

次にhtmlを編集。marginはスクロールするスペースを与えるため

index.html
    <div class="animation" style="margin-top: 1000px;">
        <img src="./images/image1.jpg">
    </div>

次にjs

app.js
$(function () {
    ScrollReveal().reveal('.animation', {
        delay: 500,
        duration: 2000,
    });
});

おしまい。

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

Javascriptでモーダルウィンドウを生成する

JavaScriptを使ったモーダルウィンドウについての解説です。
実装例として、ボタンをクリックすることでモーダルウィンドウを呼び出す処理のサンプルを記載します。

HTML

ボタンをひとつ用意します。
要素を取得する為、id="modal-btn"とします。

<button id="modal-btn">ボタン</button>

CSS

生成するモーダルに付与するCSSを設定します。
中央配置にする為にleft: 50%top: 50%transform: translate(-50%, -50%)を設定しています。

.modal {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.inner {
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.9);
  border: solid 3px #dfe4ea;
  border-radius: 10px;
}

Javascript

はじめに作成したHTMLのボタンをクリックした時の処理を記述します。
クリックを起点としたイベントを設定し、showModalメソッドを発火させます。

document.getElementById('modal-btn').addEventListener('click', showModal);

showModalメソッドの詳細を記述します。

function showModal() {
    // モーダルウィンドウと中身の要素を生成・クラスを付与
    const modalElement = document.createElement('div');
    modalElement.classList.add('modal');
    const innerElement = document.createElement('div');
    innerElement.classList.add('inner');

   // モーダルウィンドウに表示する要素を記述
    innerElement.innerHTML = `
        <p>モーダルウィンドウ</p>
    `;

    // モーダルウィンドウに中身を配置
    modalElement.appendChild(innerElement);
    document.body.appendChild(modalElement);

    // 中身をクリックしたらモーダルウィンドウを閉じる
    innerElement.addEventListener('click', () => {
        closeModal(modalElement);
    });
}

モーダルを閉じる処理を用意します。

function closeModal(modalElement) {
    document.body.removeChild(modalElement);
}

これでシンプルなモーダルウィンドウが完成しました。

モーダルウィンドウの使いどころ

モーダルウィンドウは、Webサイトの様々な箇所で使われています。
画面を覆うように表示されるため、ユーザーへの警告や確認の為に使われることが多い機能です。

  • ポップアップ広告
  • 警告メッセージ
  • エラーメッセージ
  • ロード中

モーダルウィンドウを使ったポップアップ広告などはユーザーから嫌われる傾向にある為、使用には注意が必要です。

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

【JavaScript 】Node.jsとは

はじめに

Node.jsとはサーバーサイドのjavascriptのプラットフォーム(実行環境)である。
javascriptは本来クライアントサイドで動作するプログラミング言語だが、Node.jsを使用することでサーバーサイドでもjavascriptが利用できるようになる。

公式サイト、またはOSのパッケージ管理ツールを使用してインストールすることでNode.jsが使用できる。
公式サイト
色々なパッケージ管理ツールでのインストール

クライアントサイドとサーバーサイド

? WEBページをブラウザで閲覧する場合の仕組み

1. ブラウザ(クライアント)でURLを指定、サーバーにリクエスト
2. リクエストを受けたサーバーはページを表示するために
   必要なhtmlやそれに関連するCSSやjavascriptをブラウザ(クライアント)に返す
3. クライアント側でそれらを受け取ることでWEBページが閲覧できるようになる

クライアントサイド
クライアントサイドの言語とは、クライアント(WEB上であればブラウザのこと)からサーバーにリクエストして得られた結果をブラウザで処理(表示)する際に使われる言語のこと。
javascriptがクライアントサイドの言語として挙げられる。

サーバーサイド
サーバーサイドの言語とは、クライアントに結果を渡すためにサーバー内で処理を行う言語のこと。
PHP、Ruby、Pythonなどの言語がサーバーサイドで使用される。

npm

npmとはNode.jsのパッケージを管理するツールで、Node.jsをインストールすることで自動的に使えるようになる。
ここでいうパッケージとは、あらかじめ用意されているNode.jsの便利な機能をまとめたもののこと。
npmで様々なパッケージのインストールやアップデートをコマンドラインから行うことができる。
npmでインストールされたパッケージはnode_modulesディレクトリにインストールされ、package.jsonファイルで管理ができる。

npmのインストールの種類

npmでパッケージをインストールするにはグローバルインストールローカルインストールの2種類の方法がある。

グローバルインストール
npmをインストールした場所にパッケージがインストールされる。全てのプロジェクト(フォルダ)でインストールしたパッケージが使用できるようになる。

↓下記コマンドを打ち込むことでグローバルにインストール

npm install -g パッケージ名

ローカルインストール
任意のプロジェクト(フォルダ)内にだけパーケージをインストールする。インストールしたプロジェクト内でしかパッケージが使用できない。

↓下記コマンドを打ち込むことでローカルにインストール

npm install パッケージ名

npm コマンド色々

npm install は npm i と省略して記述することもできる

* 作成したものを動かすために必要なパッケージをインストール
(package.json の dependenciesに追加される)
npm i -S

* 開発に必要なパッケージをインストール
(package.json の devDependenciesに追加される)
npm i -D

* パッケージのインストールリスト
npm list

* パッケージをアンインストール
npm rm

* node_modulesフォルダを削除
rm -rf node_modules
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Javascript]まちがえながらも感覚的に実装できるまで昇華していく[後編]

目的

  • 関数の実装を繰り返し実施し、実装までの思考法を身につける。
  • 関数の概念を頭で理解するだけでなく、感覚的に操作できるまで昇華する。
  • 英語でテクニカルな内容を理解する。

今回やること

lodashのライブラリを実装しながら学んだことをアウトプットしていきます。
if文は三項演算子で対応するように意図する。

_.lastIndexOf(array, value, [fromIndex=array.length-1])

第3引数のインデックスから逆方向に検索し、値と一致したインデックス番号を返す。なければ-1。

_.lastIndexOf([1, 2, 1, 2], 2);
// => 3

// Search from the `fromIndex`.
_.lastIndexOf([1, 2, 1, 2], 2, 2);
// => 1

実装

特に問題なし。

lastIndexOf = (array, value, fromIndex = array.length - 1) => {
    for (let i = fromIndex; 0 <= i; i--) {
        if (array[i] === value) {
            return i
        }
    }
    return -1
}

_.last(array)

配列の最後の値を取り出して返す。

_.last([1, 2, 3]);
// => 3

実装

特に問題なし

last=(array=[])=>{
    return array[array.length-1];
}

_.flatten(array)

渡した配列を一つずつなくしてくれる。

_.flatten([1, [2, [3, [4]], 5]]);
// => [1, 2, [3, [4]], 5]

学び

階層になっている時は、頭の中ではJSONを見るような気分になる。
(=階層を意識すること)

const num = [1, [2, [3, [4]], 5]];
  num[0]// =>1
   num[1] // =>[2, [3, [4]], 5]

  [2, 
  [3, [4]], 5]

実装

配列の中を取り出して配列があるかが重要。
再帰と似ていて、まずは、親子みて兄弟は親子見終わって見る。

function flatten(array) {
    const flattendedArray = [];
    for (let i = 0; i < array.length; i++) {
        const value = array[i]
        if (Array.isArray(value)) {
            flattendedArray.push(...value);
        } else {
            flattendedArray.push(value);
        }
    }
    return flattendedArray;
} 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フリマアプリのカテゴリー実装のエラー解決

前提・実現したいこと

プログラミングスクールのチーム開発でフリマアプリを製作中です。
カテゴリー機能実装でエラーが出たので、それをどのように解決したかを記録することにしました。

発生している問題・エラー(YouTube動画)

エラー画面をYouTubeにアップしました。
IMAGE ALT TEXT HERE
QiitaにはYouTubeの埋め込みはできないみたいなので、画像をクリックして動画に飛んで見てください。

FURIMAカテゴリ実装エラー解決記事1.png

親カテゴリーを選択すると子カテゴリーが出てきて、子カテゴリーを選択すると孫カテゴリーが出てくる設定になっています。

問題なのは、上記の画像の状態からもう一度親カテゴリーを選び直すと、また下に子カテゴリーが出てきてしまうことです。

FURIMAカテゴリ実装エラー画面2.png
親カテゴリーを選び直したら、下の画面のようになって欲しいのです。

FURIMAカテゴリ実装エラー解決記事3.png

エラー画面のソースコード

category.js
(省略)
$(window).on('load',function(){
  if(document.URL.match('new')) {
    $('#parent_category').on('change', function(){
      var parentCategory = $(this).val();  
        if (parentCategory != "---"){
          $.ajax({ 
            url: 'get_category_children', 
            type: 'GET',              
            data: { parent_id: parentCategory },  
            dataType: 'json' 
            }) 
            .done(function(children){ 
              $('#children_wrapper').remove();  
              $('#grandchildren_wrapper').remove(); 
              var insertHTML = ''; 
              children.forEach(function(child){ 
                insertHTML += appendOption(child); 
              }); 
              appendChidrenBox(insertHTML); 
             }) 
             .fail(function(){ 
               alert('カテゴリー取得に失敗しました'); 
             }) 
         } else { 
           $('#children_wrapper').remove();  
           $('#grandchildren_wrapper').remove(); 
         } 
       }); 
       $('.category-section__pulldown').on('change', '#child_category', function(){ 
         var childId = $('#child_category option:selected').data('category');  
         if (childId != "---"){  
           $.ajax({ 
             url: 'get_category_grandchildren', 
             type: 'GET', 
             data: { child_id: childId },  
             dataType: 'json' 
           }) 
           .done(function(grandchildren){ 
             if (grandchildren.length != 0) { 
               $('#grandchildren_wrapper').remove();  
               var insertHTML = ''; 
               grandchildren.forEach(function(grandchild){ 
                 insertHTML += appendOption(grandchild); 
               }); 
               appendGrandchidrenBox(insertHTML); 
              } 
            }) 
            .fail(function(){ 
              alert('カテゴリー取得に失敗しました'); 
            }) 
          } else { 
            $('#grandchildren_wrapper').remove();  
          } 
        }); 
(省略)

解決後のソースコード

category.js
$(window).on('load',function(){
    if(document.URL.match('new')) {
      $('#parent_category').on('change', function(){
        var parentCategory = $(this).val(); 
        if ($('div').hasClass('category-select-child')){ 
          $('.category-select-child').remove();
          $('.category-select-grandchild').remove();
          $.ajax({
            url: 'get_category_children',
            type: 'GET',
            data: { parent_id: parentCategory }, 
            dataType: 'json'
          })
          .done(function(children){
            var insertHTML = '';
            children.forEach(function(child){
              insertHTML += appendOption(child);
            });
            appendChidrenBox(insertHTML);
          })
          .fail(function(){
            alert('カテゴリー取得に失敗しました');
          })
        } else {
          $.ajax({
            url: 'get_category_children',
            type: 'GET',
            data: { parent_id: parentCategory }, 
            dataType: 'json'
          })
          .done(function(children){
            var insertHTML = '';
            children.forEach(function(child){
              insertHTML += appendOption(child);
            });
            appendChidrenBox(insertHTML);
          })
          .fail(function(){
            alert('カテゴリー取得に失敗しました');
          })
        }
      });

解決方法

以下のように条件分岐の設定を変えました。

エラーの場合
var parentCategory = $(this).val();  
  //親カテゴリーのidが入っている場合=親カテゴリーの情報が入った場合
  if (parentCategory != "---"){
   //ajaxで子カテゴリーの情報を受け取りjson形式で返す
   //子カテゴリーを追加
  } else {
    //(省略)ここの記述間違えてて何の意味もなかった
  }

親カテゴリーが選択されて情報が入っているかいないかで条件分岐されていたところを、
子カテゴリーがあるかないかで条件分岐することにしました。

解決後の場合
//子カテゴリーのクラスがある場合
if ($('div').hasClass('category-select-child')){
  //子カテゴリーを消す
  $('.category-select-child').remove();
  //ajaxの処理
  //新たに子カテゴリーを追加
} else {
//子カテゴリーのクラスがない場合
  //単純に子カテゴリーを追加
}

.hasClassメソッドは、そのクラスがあるかないかを判定して、trueかfalseで返してくれます。

子カテゴリーがあれば、まず既存の子カテゴリーをremoveメソッドで消してから新たに子カテゴリーを追加し、
子カテゴリーがなければ、単純に今まで通り子カテゴリーを追加するだけにしました。

解決後の挙動(YouTube動画)

解決後の挙動の動画をYouTubeにアップしました。
IMAGE ALT TEXT HERE
※QiitaにはYouTubeの埋め込みはできないみたいなので、画像をクリックして動画に飛んで見てください。

補足情報

ruby 2.5.1
Rails 5.2.4.3

参考記事

【jQuery入門】hasClass()でクラスの存在有無を調べる方法!

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

だから僕はVanilla JSを辞めた

だから僕はVanilla JSを辞めた

Vanilla JSとは、普通の何のフレームワークも使わない純粋なJavaScriptのことです。
Pure JSと呼んだりもしますが、あえてこの表記としています。
私が、フレームワークに手を出して、Vanilla JSを辞めた理由を、私の場合の例として述べます。
私の場合がTypeScirptとVue.jsの導入だったのでその話をしますが、これらの推奨が目的ではなく、新たなものへ挑戦する勇気を持ちなさいという記事です。

TL;DR

  • 純粋なJavaScriptでなんでもできるからと、なんでもしていた私がフレームワークを導入するまで
  • フレームワークや、その他新しいことの食わず嫌いはなるべくやめたい

JavaScriptだけで記述する

WEB開発はコンパイラ不要、環境も選ばない、JavaScriptはなんだか緩いと、手を出しやすい分野だと思います。
私はなんでも純粋な状態で書くことに、何と無くこだわりを持っていました。
JavaScriptだけで書いて、jQueryなどには見向きもしない、そんな日々が続いていました。
その理由としては、新たなものを覚えるほどまだJavaScriptに精通していない、学習コストが高そう、無くてもかける、無駄な通信や変数汚染を避けたい、などでした。

TypeScriptとの出会い

しかしそんなある日、何気なく書いた次の一文が動かないことに頭を悩ませることになります。

document.getElementsByTagName("a").forEach((e) => {
  e.onclick = () => location.href = e.href
  e.removeAttribute("href")
})

内容は、ページ中のaタグのリンクを消去し、代わりにonclickでページ遷移を行うものです。
これは、PWAでaタグによるリンクを行うと、PWAを抜けてブラウザが起動してしまうというものを回避するために書いたものでしたが、うまく動作しません。
この理由は、document.getElementsByTagNameの戻り値はHTMLCollectionであってArrayでないことです。

今まで考えてもいませんでしたが、JavaScriptにも型が存在したのでした。
上記ではHTMLCollectionというのはObject型であり、そこにforEachメソッドが定義されていないため、動作しないのです。
どうにもJavaScriptばかり書いてると、型の考えをしなくなってきます。今でこそlet/constによる記法が標準となってまいりましたが、古い本などではvarを用いるものも見ます。なんだって再代入できちゃうものだから、動的型付けなんて型がないみたいなものだろうと考えてしまいます。

そして思い出したキーワード、それがTypeScriptです。
型を意識しないでめちゃめちゃなことできるからJSは便利なのに、なんでそこに型付けるのか、非常に疑問でしたが、これ使ってたらこんなエラーに数時間溶かすことはなくなるのではないかと、思ったわけです。

簡単な例

次のようなコードを書いたことがあるでしょうか

console.log(`${num} is ${(num % 2) ? "odd" : "even"}`)

上記は、数字の入った変数numによって、それが偶数か奇数かをコンソールに表示するという動作をします。
なぜでしょう。それは0falthyで、それ以外の数字はtruthyだからですね。

JavaScriptでは暗黙の型変換が結構活躍します。条件式の中では勝手Booleanに変換される上記の例もそうですが、ほかにも

const num = document.getElementById("age").value - 0

などとすれば、入力された数値をString型ではなくNumber型として利用できます。
これは、文字列に算術演算子を適用するとき、文字列を数値へ暗黙的に変換を行うためで、実際には数値へ変換できないような文字列だとしたらNaNが代入されることになります。

一見便利な暗黙の型変換ですが、変数に入っている方がわからなければ、先ほど挙げたHTMLCollectionのような、無いメソッドを呼び出してしまうようなミスをしてしまうことが多くなってきます。

TypeScriptでは、変数に代入できる型や、メソッドの戻り値の型などはあらかじめ決めておかなければなりません。このため、上記のコードはコンパイルが通りません。算術演算子の左オペランドには文字列は使えないからです。(文字列の結合の演算子としての+は使用できますが、これは算術演算子ではありません)

これは逆に、変数の型は常にわかっているということです。
なんらかのコードエディタを用いている場合、変数の型がわかっているために、メソッドなども書いている途中でその有無や戻り値の型まで把握できます。

最近はNull安全などといったことも目にしますし、ECMAScript2020ではOptional Chaning演算子?.や、Null合体演算子??も追加されます。nullの扱いは面倒ですからね。
一方でTypeScriptではそもそも、その変数がundefinednullとなり得るかどうか知ることができます。
なり得る場合は、先述のような演算子や、条件分岐などを必要としますが、それを教えてくれるというわけです。便利ですね。

簡単な例2

次のコードの動作はどうなるでしょう。

const num = 5
window.localStorage.setItem("isEven", !(num % 2))

const isEven = window.localStorage.getItem("isEven")
console.log(`${isEven ? "even" : "odd"}`)

当然5は奇数ですからsetItemされるのは!(5 % 2) = !(1) = falseです。
それがisEvenにローカルストレージ経由で代入され、三項演算子で"odd"と評価されるはず、と思いますよね。

残念ながら、num5であろうとなんであろうと、コンソールの表示は必ず"even"になります。
ローカルストレージには文字列しか保存できないんですね。
このため、setItemの時点でfalse"false"に変換され、truthyとなります。
今回の場合、setItemされるのは"true" | "false"と表せます。これはどちらもtruthyですから、常に"even"が表示されます。
型変換されるとわかっていても、必ずJSON.stringifyしてからセットし、読みだした後にJSON.parseするようにしないといけませんね。
一方、TypeScriptでは、Boolean型はString型じゃないよって教えてくれます。また、読み出し後もそれがString型であることを教えてくれるので、このようなミスは防げるかと思います。

シチュエーションとしては、チェックボックスやスイッチの状態を保存したいときなんかあると思います。
自分もやらかしてしまいそうだと思いませんか?

なぜかundefinedって画面に出ちゃうことがある人は検討してみると良いでしょう。そもそも画面に表示するところにundefinedとなり得る変数を置かなければ良いんですから。

型変換を自前で行う必要がある場合や、変数の型を明記する必要がある場合など、コードの記述量は増えてしまうことはあるかもしれませんが、その分バグが減ります。

今まで環境構築や学習コストなどを恐れて導入しなかった昔の自分を諭したいです。

Vue.jsとの出会い

そしてある日、Vue.jsというものを見かけます。
どうやらアプリ開発に便利なフレームワークだそうです。
フレームワークはとはなんや、と思いましたが、流行ってたので乗ることにしました。(コロナも流行ってますが乗らないでください)

とりあえず適当にアプリを開発して、Vuetifyというマテリアルデザインのアプリ開発が簡単にできるものも見つけ、使用しているうちにこう感じましたわけです。
「なるほど。今までは車輪の再発明をしていたわけか。」

あるいはjQueryなんかも使っていればもっと早くアプリの開発ができたのではないかとも、今になっては思います。もう今となっては使いませんが。

先人が自分より高度な知識で作ったものなんですから、そりゃ私がそれに勝るアプリを作れないでしょう。
それはもう、ArrayにはArray.prototype.reverseがあるのに、自前でfor文で逆に詰めなおすようなことをしているようなものだったわけです。時間の無駄だし、なんなら性能も劣る場合があります(ソートなんかそうですね)。

だから僕は

今までの私は間違っていました。
使用できるものは使った方が良いです。なんかnullになったり、予期しない文字列きたり、対応するのめんどくさいですね。デザインなんかコーディングと同時にするには難しいですし、CSSはいつも思ったのと違う動作するんですもん。

そして私は今になって、昔の開発環境を、あるジョーク記事からとってこう呼んでいます。

Vanilla JSと。

それは、純粋なJavaScriptを用いるというフレームワークです。
初学者なんかは構いませんが、ある程度の規模になってくると注意深い開発が必要になり、他人と開発するときなんかにもそのメソッドが何を返す可能性があるのかを明確にしたいときが出てきたりすると思います。
私の場合、TypeScript+Vue.js+Vuetifyというのを採用して、開発の効率と品質が上がり、食わず嫌いで導入を渋っていたことを後悔しました。

だから僕はVanilla JSを辞めた。

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

【Nuxt.js】Nuxt文法編:v-bind

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

前置き

Nuxt.js Vue.js を使う上で必須の知識です!
これができるとコンポーネントを活かして
表現の幅を広げることができます?

例えば!
親によってクラスの有無を切り替えて
色分けをする、といったことができます?

基本的な使い方

v-bind:{ 属性名 }="{ 変数 }"

タグの属性を変数にしたい時に使います。
aタグのhref属性をv-bindしてみます。

飛びたいリンク先
http://localhost:3000/

index.vue
<template>
 <div class="page">
   <a v-bind:href="url">リンク</a>
 </div>
</template>

<script>
export default {
 data () {
   return {
     url: '/',
   }
 },
}
</script>

? この記事はWP専用です
  続きはWPでご覧ください?
https://wp.me/pc9NHC-nh

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

【javascript】ロックとキーをシミュレートする

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

以下のコードはロックとキーのシステムを構築している。

function lock(secret){
  const key = Symbol('key')
  return {
    key,unlock(keyUserd){
      if(keyUserd==key){
        return secret;
      }else {
        return '*'.repeat(secret.length || secret.toString().length)
      }
    }
  }
}

const {key,unlock}=lock(42)

console.log(unlock())//**
console.log(unlock(Symbol('key')))//**
console.log(unlock('key'))//**
console.log(unlock(key))//42

正しいキーならsecretを取得でき、正しくないならキーをマスクしている。

コードの解説

このコードを読む上でわからないことがいくつかあった。

  1. return{key,unlock(...)}とはどういうことか
  2. unlock()で実行できるのはなぜか

return {key,unlock(...)}とはどういうことか

return {}はオブジェクトを返しているということ。そしてオブジェクトには簡易表記という書き方ができる。

const obj = {
  name: name,
  method: function() {}
}

//簡易表記
const obj = {
  name,
  method() {}
}

プロパティ名(name)と変数名が同じ場合、一度指定すればいいだけになる。
{name:name}と書いていたのが{name}で済む。
またメソッドに関しては無名関数に限り{method:function(){}}{method()}と書ける。

つまりlock関数を書き換えるとこういうこと

function lock(secret){
  const key = Symbol('key')
  return {
    key:key,
    unlock:function(keyUserd){
      if(keyUserd==key){
        return secret;
      }else {
        return '*'.repeat(secret.length || secret.toString().length)
      }
    }
  }
}

そしてconst {key,unlock}=lock(42)で変数keyにSymbol('key')が代入され、変数unlockには無名関数が代入されている。

unlock()で実行できるのはなぜか

これは勘違いであった。

その原因となったコードがこちら

const ffaa={
  at(n){
    if(n<=2)return 1;
    return this.at(n-1)+this.at(n-2);
  }
}

const {at}=ffaa;
const ff={at}
const aa={ff:at}

ffaa.at(7)//13
at(7)//エラー
ff.at(7)//13
aa.ff(7)//エラー

これだけを見ると、無名関数を分割した際、単純に変数名()では実行でいなものと思ってしまった。
確かに関数を名前で参照出来ないこともあるが、それは関数内のメンバーを参照するときである。

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

第四回 JavaScriptの非同期処理について

前提

以降の説明では下記の非同期関数を使用して説明を実施します。
※使用したソースは全て GitHub に上げてあります

file.js
function readFile(fileName, success) {
  setTimeout(() => {
    if (typeof(success) === 'function') {
      success(fileName)
    }
  }, 500)
}
function readFileAsync(fileName) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(fileName), 500)
  })
}

プログラムの常識

コードは上から順に実行される


sync.js
const hoge = readFile('hoge.txt')
const fuga = readFile('fuga.txt')
const foo = readFile('foo.txt')
const bar = readFile('bar.txt')
console.log(hoge + fuga + foo + bar)

動かない


なんで?

  • 非同期処理だから
  • ファイルの読み込みが終わる前に console.log() が走っちゃう

なんで非同期なの?

  • 同期的な処理だと、IOアクセスの待ち時間中に何もできなくなる
  • DOMを操作するときはなにかと非同期で動かしたいことが多い(Ajaxのレスポンスを待っている間にプログレスバーを表示させるとか)

ES6以前の非同期処理

  • コールバック地獄

コールバック地獄

async.js
readFile('hoge.txt', function (hoge) {
  readFile('fuga.txt', function (fuga) {
    readFile('foo.txt', function (foo) {
      readFile('bar.txt', function (bar) {
        console.log(hoge + fuga + foo + bar)
      })
    })
  })
})

  • 深いネスト
  • エラーをまとめて受け取れない
  • function ,function , function ...

ES6 (ECMAScript 2015)以後の世界


Promise


Promiseとは

Promiseは非同期処理を抽象化したオブジェクトとそれを操作する仕組みです。
使いこなすことで、以下のような効果が見込めます。

  • コールバック地獄から解放される
  • エラー処理をうまく記述できる
  • 一連の非同期処理を関数化して再利用しやすくできる

書き方

howto.js
const promise = function(str) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(str), 500)
  })
}

promise('foo')
  .then(
    (val) => {
      console.log(val)
      return promise('bar')
    })
  .then((val) => console.log(val))
  .catch((err) => console.log(err))

解説

Promiseオブジェクト

  • Promiseオブジェクトを返す関数を書く
  • Promiseオブジェクトの中に非同期に行いたい処理を書く
  • 非同期処理が終わったらresolve()を呼び出す
  • 非同期処理が失敗したらreject()を呼び出す

解説

Promiseオブジェクトを使う

  • 非同期処理を実行したあとに実行したい処理をthen節の中に書く
  • 実行結果は引数(val)で受け取る
  • 非同期処理が失敗したらcatch節で受け取れる

いいところ

  • ネストが深くならない
  • 各処理で起こったエラー(例外)をcatch節でまとめて補足できる

これでコールバックともおさらばだぜ!!


promise.js
let hoge, fuga, foo, bar
readFileAsync('hoge.txt')
  .then((result) => {
    hoge = result
    return readFileAsync('fuga.txt')
  })
  .then((result) => {
    fuga = result 
    return readFileAsync('foo.txt')
  })
  .then((result) => {
    foo = result
    return readFileAsync('bar.txt')
  })
  .then((result) => { 
    bar = result
    console.log(hoge + fuga + foo + bar)
  })

うーん・・・

このままでもできるけど・・・


  • 確かにネストは浅くなったが長い
  • 外の変数に退避しないと一個前の結果を参照できない
  • then,then,then...

もっと同期的に書きたい

理想のコード.js
const hoge = readFileAsync('hoge.txt')
const fuga = readFileAsync('fuga.txt')
const foo = readFileAsync('foo.txt')
const bar = readFileAsync('bar.txt')
console.log(hoge + fuga + foo + bar)

そして ES8(ECMAScript 2017)へ・・・


async/awaitの登場


await.js
async function readFiles(){
  const hoge = await readFileAsync('hoge.txt')
  const fuga = await readFileAsync('fuga.txt') 
  const foo = await readFileAsync('foo.txt') 
  const bar = await readFileAsync('bar.txt')
  console.log(hoge + fuga + foo + foo)
}

readFiles()

解説

  • 同期的に処理したい部分を async function で囲む
  • 同期的に処理したい非同期処理関数の手前に await をつける
  • Promiseの処理が終わるまで待ってくれるようになる

ちなみに・・・

  • async functionもPromiseを返すので、以下のようなコードは非同期に実行されます
await1.js
async function readFiles(){
  const hoge = await readFileAsync('hoge.txt')
  const fuga = await readFileAsync('fuga.txt') 
  const foo = await readFileAsync('foo.txt') 
  const bar = await readFileAsync('bar.txt')
  console.log(hoge + fuga + foo + foo)
}

readFiles()

console.log('readFiles()はまだ終わらない・・・')
  • ちゃんとやるなら以下のような感じ
await2.js
async function readFiles() {
  const hoge = await readFileAsync('hoge.txt')
  const fuga = await readFileAsync('fuga.txt') 
  const foo = await readFileAsync('foo.txt') 
  const bar = await readFileAsync('bar.txt')
  console.log(hoge + fuga + foo + foo)
}

readFiles().then(() => console.log('readFiles()は終わった!!'))

めでたしめでたし


ちょっとまって


直列処理はいいけど並列処理は?


Promise.all()


Promise.all()

  • 複数の非同期処理が完了した後にthen節を呼び出せる

promise_all.js
const hoge = readFileAsync('hoge.txt')
const fuga = readFileAsync('fuga.txt')
const foo = readFileAsync('foo.txt')
const bar = readFileAsync('bar.txt')

Promise.all([hoge, fuga, foo, bar])
  .then((values) => console.log(values[0] + values[1] + values[2] + values[3]))

awaitとの合わせ技も可能

promise_all1.js
async function readFiles() {
  const values = await Promise.all([
    readFileAsync('hoge.txt'),
    readFileAsync('fuga.txt'),
    readFileAsync('foo.txt'),
    readFileAsync('bar.txt'),
  ])
  console.log(values[0] + values[1] + values[2] + values[3])
}

readFiles()

まとめ

  • async/awaitは神
  • Promise.allとかも使って、非同期にすべきところは非同期にしよう
  • ちなみに新しい規格なのでIE非対応。使いたいときはトランスコンパイルしましょう

補足資料

ES6,ES8

ES6〜ES10(es2015〜es2019)まとめ

トランスパイルとは

トランスコンパイラ(他にトランスパイラ、ソース・トゥ・ソースコンパイラ、などとも)は、あるプログラミング言語で書かれたプログラムのソースコードを入力として受け取り、別のプログラミング言語の同等のコードを目的コードとして生成する、ある種のコンパイラである
-- Wikipediaより

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

[JS] もう少し厳密に URL からファイル名等を取得する正規表現

よく見かける URL の正規表現に比べて、もう少し厳密にパースする正規表現の紹介になります。

正規表現なので他の言語等でも使用できると思いますが、ここでは JavaScript で動くコードを載せます。

1. RFC 3986 に書かれている URL の正規表現

WEB 上で調べると様々なひとたちが URL の正規表現を書いていると思いますが、URL の定義が書かれている RFC 3986 に既に (おそらく) 厳密な正規表現が書かれています。

自身でヘタに正規表現を書くよりもこちらを流用したほうが安全です。

以下、引用・意訳。

^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
 12            3  4          5       6  7        8 9

2 行目は、マッチ文字列のインデックスを分かりやすくするための数です。
例えば、以下のようにマッチします。

http://www.ics.uci.edu/pub/ietf/uri/#Related

$1 = http:
$2 = http
$3 = //www.ics.uci.edu
$4 = www.ics.uci.edu
$5 = /pub/ietf/uri/
$6 = <undefined>
$7 = <undefined>
$8 = #Related
$9 = Related

参考「RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax
Appendix B. Parsing a URI Reference with a Regular Expression

2. 例: ディレクトリとファイル名とクエリ文字列を取得

2.1. 一般的によく使われる JavaScript のコードの問題点

lastIndexOf() でファイル名やクエリ文字列を分離したり、split() でディレクトリやファイル名を分離するものが多いですが、厳密にはクエリ文字列そのものに /? を含めることができるため、理論上、正しく動作しない可能性があります。

const url = /* 'URL 文字列' */;
const query = url.slice(url.lastIndexOf('?') + 1);
const fileName = url.slice(0, url.lastIndexOf('?')).slice(url.lastIndexOf('/') + 1);

例:https://example.com/dir1/dir2/index.php?callback=https://example.com/dir3/dir4/index2.php?param

query === 'params'
fileName === `index2.php`

2.2. (おそらく) 厳密な正規表現

不要な括弧 ( ... ) を取り除いたりキャプチャしないように (?: ... ) に変更して、必要な部分だけをキャプチャします。

また、ファイル名にマッチする部分 5 ([^?#]*)(?:([^?#]*/)([^/?#]*))? に書き換え、(クエリ文字列が取り除かれた) パス文字列の最後のスラッシュから後ろをキャプチャします。

ファイル名を含めるには必ずスラッシュ / が 1 つ以上あるはずで、スラッシュがない場合にはファイル名が空になるはずなので、正しく動作するはずです….。

^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
 12            3  4          5       6  7        8 9

^(?:[^:/?#]+:)?(?://[^/?#]*)?([^?#]*)(\?[^#]*)?(?:#.*)?
                             1       2

^(?:[^:/?#]+:)?(?://[^/?#]*)?(?:([^?#]*/)([^/?#]*))?(\?[^#]*)?(?:#.*)?
                                1        2          3

※ここではクエリ文字列を区切り文字の ? ごとキャプチャしています。パスを再び組み立てる必要がある場合などは ? を残した方が便利です。逆に、POST 送信する目的などでは ? を除去した方が使いやすいため、状況によって使い分けてください (両方キャプチャすることもできます) 。

2.3. JavaScript でのコード

const url = /* 'URL 文字列' */;  
const matchedFileName = url.match(/^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?(?:([^?#]*\/)([^\/?#]*))?(\?[^#]*)?(?:#.*)?$/) ?? [];
const [, dir, fileName, query] = matchedFileName.map(match => match ?? '');

例:https://example.com/dir1/dir2/index.html?params#section1

dir === '/dir1/dir2/'
fileName === `index.html`
query === '?params'

例:https://example.com/dir1/dir2/index.php?callback=https://example.com/dir3/dir4/index2.php?param

dir === '/dir1/dir2/'
fileName === `index.php`
query === '?callback=https://example.com/dir3/dir4/index2.php?param'

3. おまけ: 拡張子も分離する

拡張子は拡張子で厳密に取り出そうとすると罠が多いため、以下のページを参考にさせていただきました。

^(.+?)(\.[^.]+)?$
 1    2

参考「ファイル名から拡張子とそうでない部分を分ける - Qiita」(※ Ruby の正規表現のため、少し記述が異なります)
参考「Railsの正規表現でよく使われる \A \z って何?? - Qiita

const url = /* 'URL 文字列' */;  

const matchedFileName = url.match(/^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?(?:([^?#]*\/)([^\/?#]*))?(\?[^#]*)?(?:#.*)?$/) ?? [];
const [, dir, fileName, query] = matchedFileName.map(match => match ?? '');

const matchedExt = fileName.match(/^(.+?)(\.[^.]+)?$/) ?? [];
const [, name, ext] = matchedExt .map(match => match ?? '');

例:https://example.com/dir1/dir2/index.html?params#section1

name === 'index'
ext === `.html`
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】漫画の進捗管理ツール作ってみた

01.png
02.png
03.png

あと何コマ?
コマ数で漫画の進捗管理するツールを作りました。
コマ数を入力するとコマが出てきて、
終わったコマをクリックすると塗りつぶされて完了状態になります。
1コマ辺りの作業時間を入力すると、残りの作業時間がわかります。
1コマの作業時間×残りコマ数で残りの作業時間が算出される訳ですね。

何故作ろうと思ったのか

ページ単位で管理するツールは既にあるが、
コマ単位で管理するものはなかったため。
漫画制作のモチベーションを維持するためにこういうツールがほしかった。
(コマ単位で管理しないとモチベが保たない)
Vue初心者が悶絶しながら作ったものですが、この記事が他の勉強中の方の参考になればと思います。

ソース全文

panels.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>あと何コマ?</title>
  <!-- Bootstrap -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
    integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
  <!-- fontawesome -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css"
    integrity="sha256-UzFD2WYH2U1dQpKDjjZK72VtPeWP50NoJjd26rnAdUI=" crossorigin="anonymous" />
  <link rel="stylesheet" href="panels.css">
</head>
<body>
  <header class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4">
    <nav class="mt-2 my-md-0 mr-md-3">
      <a class="px-2 text-white" href="#paneldiv">あと何コマ?</a>
      <a class="px-2 text-white" href="#timediv">あと何分?</a>
    </nav>
  </header>
  <div class="jumbotron jumbotron-fluid">
    <div class="container captionText">
      <p>コマ数で進捗管理するツールです。残りコマ数を入力して、入力完了ボタンを押してください。<br>
        1コマ辺りの作業時間を入力すると、残りの作業時間がわかります。
      </p>
    </div>
  </div>
  <div id="app" class="mb-5">
    <div class="container main py-4 mt-sm-3">
      <article class="text-center pt-3 pb-4" id="paneldiv">
        <div class="alertArea text-center mb-3">
          <strong v-show="alertShow">
            コマ数を入力してください
          </strong>
        </div>
        <h3 v-if="!resultShow">
          あと<input type="number" v-model.number="remainedPanelsInput" min="0" class="panelInput">コマ?
          <button type="button" class="ml-2 btn page-link text-light d-inline-block btn-purple" @click="resultShowFunc"
            v-if="!resultShow">入力完了</button>
        </h3>
        <h3 v-else><input type="number" v-model.number="remainedPanelsInput" min="0" class="panelInput">コマ
          <button type="button" class="ml-2 btn page-link text-light d-inline-block btn-purple" @click="resultReset"
            v-if="resultShow">リセット</button>
        </h3>
        <div v-show="resultShow">
          全{{ remainedPanelsInput }}コマ-
          済み<input type="number" v-model.number="filledPanels" v-bind:max="remainedPanelsInput" min="0">コマ
          =
          あと<span class="resultText">{{ remainedPanelsNumber }}</span>コマ
          <p class="text-muted pt-3">終わったコマをクリックすると、塗りつぶされて完了状態になります。完了状態のコマをクリックすると未完の状態に戻ります。</p>
        </div>
        <section class="row pricing-header px-3 py-3 pt-md-3 pb-md-1 mx-auto text-center" v-show="resultShow">
          <div class="panel" v-for="n in remainedPanelsNumber" @click="panelFinished">
            <div class="panelInner">{{ n }}</div>
          </div>
          <div class="panel filled" v-for="n in filledPanels" @click="panelReturn"></div>
        </section>
      </article>

      <article class="text-center" id="timediv" v-show="resultShow">
        <h3>あと何分?</h3>
        <div>
          1コマ辺りの作業時間<input class="inputPerPanel" type="number" v-model.number="
          perPanel" min="0">分×残り{{ remainedPanelsNumber }}コマ=
          あと<span class="resultText">{{ remainingTime }}</span>分
          ({{ remainingHour }}時間)
        </div>
      </article>
    </div>
    <!-- ツイートボタン -->
    <div class="contact text-center">
      <a href="https://twitter.com/share" class="twitter-share-button" data-url="https://mitaru.github.io/panels/"
        data-text="進捗どうですか?あと何コマ?" data-size="large" data-hashtags="あと何コマ">
        Tweet
      </a>
    </div>
  </div>
  <footer class="my-1 pt-5 text-muted text-center text-small">
    <ul class="list-inline">
      <li class="list-inline-item">
        <a href="https://twitter.com/SakaiMitaru">
          <i class="fab fa-twitter-square mr-1"></i>SakaiMitaru
        </a>
      </li>
      <li class="list-inline-item"><a href="https://github.com/mitaru/panels.git">
          <i class="fab fa-github"></i>
        </a>
      </li>
    </ul>
  </footer>
  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
    integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
    crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js"
    integrity="sha384-1CmrxMRARb6aLqgBO7yyAxTOQE2AKb9GfXnEo760AUcUmFx3ibVJJAzGytlQcNXd"
    crossorigin="anonymous"></script>
  <!-- vue.js -->
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="panels.js"></script>
</body>
</html>
panels.css
html {
  font-size: 14px;
}

@media (min-width: 768px) {
  html {
    font-size: 16px;
  }
}

body {
  color: rgb(31, 45, 65);
  background: #F7F7FC;
}

header {
  background: #37384E;
  color: #fff;
}

.jumbotron {
  margin: 0;
  background: #37384E;
  color: #fff;
  border-radius: 0 0 40% 40%;
}

.captionText p {
  margin-bottom: 50px;
}

.text-muted {
  color: #9e9fb4 !important;
  font-size: 14px;
  margin: 0;
}

.main {
  max-width: 2000px;
  background: #F7F7FC;
}

.alertArea {
  height: 20px;
}

.alertArea strong {
  background: rgba(255, 255, 255, 0.6) !important;
  padding: 2px 5px;
  border-radius: 4px;
  color: #37384E;
}

.panelInput {
  min-width: 10vw;
}

.btn-purple {
  color: #fff;
  background: #16C995;
  border: 0;
}

.btn-purple:hover {
  background: #0f926d;
}

.pricing-header {
  max-width: 1000px;
}

section {
  justify-content: center;
  align-items: center;
}

input {
  width: 50px;
  border: 0;
  background: #fff;
  border-radius: 4px;
  padding: 3px;
  margin: 2px;
  color: #766DF4;
  font-weight: bold;
  text-align: center;
}

.resultText {
  background: rgba(118, 109, 244, 0.08) !important;
  color: #766df4 !important;
  font-size: 20px;
  padding: 0 10px;
  border-radius: 4px;
  font-weight: bold;
}

.panel {
  width: 150px;
  height: 100px;
  background: #fff;
  border: 5px solid #333;
  margin: 4px;
  text-align: center;
  line-height: 100px;
  cursor: pointer;
  user-select: none
}

.filled {
  background-color: #fff;
  background-image: radial-gradient(#16C995 14%, transparent 17%), radial-gradient(#16C995 14%, transparent 17%);
  background-position: 0 0, 4px 4px;
  background-size: 8px 8px;
}

footer {
  background: #F7F7FC;
  clear: both;
}

a .fa-github {
  font-size: 30px;
  color: #333;
}

a .fa-github:hover {
  opacity: 0.8;
}

@media screen and (max-width: 480px) {
  .panel {
    width: 100px;
    height: 70px;
    line-height: 70px;
  }
}
panels.js
(function () {
  'use strict';

  new Vue({
    el: '#app',
    data: {
      remainedPanelsInput: 0,
      filledPanels: 0,
      perPanel: 0,
      resultShow: false,
      alertShow: false,
    },
    watch: {
      remainedPanelsInput: {
        handler: function () {
          localStorage.setItem('remainedPanelsInput', JSON.stringify(this.remainedPanelsInput));
        },
        deep: true
      },
      filledPanels: {
        handler: function () {
          localStorage.setItem('filledPanels', JSON.stringify(this.filledPanels));
        },
        deep: true
      },
      perPanel: {
        handler: function () {
          localStorage.setItem('perPanel', JSON.stringify(this.perPanel));
        },
        deep: true
      },
    },
    methods: {
      resultShowFunc: function () {
        if (this.remainedPanelsInput === 0) {
          this.alertShow = true;
        } else {
          this.resultShow = true;
          this.alertShow = false;
        }
      },
      panelFinished: function () {
        this.filledPanels++;
      },
      panelReturn: function () {
        this.filledPanels--;
      },
      resultReset: function () {
        this.remainedPanelsInput = 0;
        this.filledPanels = 0;
        this.perPanel = 0;
        this.resultShow = false;
      },
    },
    computed: {
      remainedPanelsNumber: function () {
        return this.remainedPanelsInput - this.filledPanels;
      },
      remainingTime: function () {
        return this.remainedPanelsNumber * this.perPanel;
      },
      remainingHour: function () {
        return Math.round((this.remainingTime / 60) * 10) / 10;
      },
    },
    mounted: function () {
      this.remainedPanelsInput = JSON.parse(localStorage.getItem('remainedPanelsInput')) || 0;
      this.filledPanels = JSON.parse(localStorage.getItem('filledPanels')) || 0;
      this.perPanel = JSON.parse(localStorage.getItem('perPanel')) || 0;
      if (this.remainedPanelsInput > 0) {
        this.resultShow = true
      }
    },
  })
  // twitter投稿
  !function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');
})();

Bootstrap4のこちらの実例をもとに作りました。ほぼ原型残ってないです。
何故わざわざテンプレートをもとに作るかと言うと、レスポンシブ対応が楽だからですね。
10.png
全力で先人に頼っていくスタイル。

Vue部分

panels.js
  new Vue({
    el: '#app',
    data: {
      remainedPanelsInput: 0,
      filledPanels: 0,
      perPanel: 0,
      resultShow: false,
      alertShow: false,
    },
// 中略
    computed: {
      remainedPanelsNumber: function () {
        return this.remainedPanelsInput - this.filledPanels;
      },
      remainingTime: function () {
        return this.remainedPanelsNumber * this.perPanel;
      },
      remainingHour: function () {
        return Math.round((this.remainingTime / 60) * 10) / 10;
      },
    },

02.png
remainedPanelsInput(全○コマの数)から filledPanels(完了したコマ数)を引いて
remainedPanelsNumber(残りコマ数)を出しています。
あと何分?の部分はperPanel(1コマ辺りの作業時間)と
remainedPanelsNumber(残りコマ数)を掛けて算出しています。
また分単位だけでなく時間単位の表記もあった方が親切だと思ったので
remainingHourで計算しました。
Math.round((this.remainingTime / 60) * 10) / 10;と書くことで、
小数点第二位で切り捨てて表示することができます。

panels.html
<h3 v-if="!resultShow">
  あと<input type="number" v-model.number="remainedPanelsInput" min="0" class="panelInput">コマ?
  <button type="button" class="ml-2 btn page-link text-light d-inline-block btn-purple" @click="resultShowFunc"
    v-if="!resultShow">入力完了</button>
</h3>
<h3 v-else><input type="number" v-model.number="remainedPanelsInput" min="0" class="panelInput">コマ
  <button type="button" class="ml-2 btn page-link text-light d-inline-block btn-purple" @click="resultReset"
    v-if="resultShow">リセット</button>
</h3>
panels.js
methods: {
  resultShowFunc: function () {
    if (this.remainedPanelsInput === 0) {
      this.alertShow = true;
    } else {
      this.resultShow = true;
      this.alertShow = false;
    }
  },

こちらはあと何コマ?部分のコードです。
image.png
image.png
if (this.remainedPanelsInput === 0)
あと何コマ?の入力欄が0の場合、入力完了ボタンを押下コマ数を入力してください」とアラートが表示されます。
image.png
1以上の数字が入力されている場合は結果が表示されます。
このような表示の分岐にv-showv-ifは大変便利です。

panels.html
<div class="panel" v-for="n in remainedPanelsNumber" @click="panelFinished">
  <div class="panelInner">{{ n }}</div>
</div>
<div class="panel filled" v-for="n in filledPanels" @click="panelReturn"></div>
panels.js
methods: {
  // 中略
  panelFinished: function () {
    this.filledPanels++;
  },
  panelReturn: function () {
    this.filledPanels--;
  },

image.png
image.png
こちらはコマ部分です。
白いコマはremainedPanelsNumber(残りコマ数)分、
ドットのコマはfilledPanels(完了したコマ数)分表示されます。
v-for="n in remainedPanelsNumber"と書けば
remainedPanelsNumberの数だけコマを複製してくれます。楽ちんです。
jsで作ろうとしたらコマの中にコマ数を表示するのも大変そうですが、
Vueなら{{ n }}と書くだけですみます。おお助かる助かる。
またpanelFinishedpanelReturnのクリックイベントで
パネルの完了状態を変更しています。

panels.js
watch: {
  remainedPanelsInput: {
    handler: function () {
      localStorage.setItem('remainedPanelsInput', JSON.stringify(this.remainedPanelsInput));
    },
    deep: true
  },
  filledPanels: {
    handler: function () {
      localStorage.setItem('filledPanels', JSON.stringify(this.filledPanels));
    },
    deep: true
  },
  perPanel: {
    handler: function () {
      localStorage.setItem('perPanel', JSON.stringify(this.perPanel));
    },
    deep: true
  },
},
// 中略
mounted: function () {
  this.remainedPanelsInput = JSON.parse(localStorage.getItem('remainedPanelsInput')) || 0;
  this.filledPanels = JSON.parse(localStorage.getItem('filledPanels')) || 0;
  this.perPanel = JSON.parse(localStorage.getItem('perPanel')) || 0;
  if (this.remainedPanelsInput > 0) {
    this.resultShow = true
  }
},

ローカルストレージでremainedPanelsInputfilledPanels
perPanelの数を保存しています。
監視している変数の変更をトリガーにして勝手に働いてくれる
監視プロパティくんは便利やでホンマ。
途中保存がうまく行かなかったんですが、
mounted部分をnew Vue内で一番最後に配置したら
ちゃんと保存されるようになりました。どうして?(無知)

デザイン部分

Bootstrap5のとあるテーマを大いに参考にさせていただきました。
見た目もできるだけ可愛くしたかったんです。
image.png

panels.css
.jumbotron {
  margin: 0;
  background: #37384E;
  color: #fff;
  border-radius: 0 0 40% 40%;
}

border-radiusだけでdivの下部分を丸くできるものなんですね。
今回調べて初めて知りました。他にも色んな表現ができるみたいです。
歪んだ円まで作れるなんてすごい!
参考:今さら聞けない!? CSSのborder-radiusで様々な角丸に挑戦!

image.png

panels.css
.filled {
  background-color: #fff;
  background-image: radial-gradient(#16C995 14%, transparent 17%), radial-gradient(#16C995 14%, transparent 17%);
  background-position: 0 0, 4px 4px;
  background-size: 8px 8px;
}

なんとradial-gradientを使えば、CSSだけで漫画のトーンみたいなドットの背景が作れます。
CSSでドット柄(水玉模様)を作成 - ホームページのパーツ作成で好きなドットを作ろう!
他にもradial-gradientでストライプやチェック柄まで作れるみたいです。実際スゴイ!
参考:CSSグラデーションで作った背景パターンのサンプル

感想

  • 比較的思った通りに作れた
  • 見た目もいい感じになったと思う

課題・問題点

  • ページ単位の管理機能も作りたかったがややこしすぎてヤコになった
  • 今までの知識の延長線上だなと思うのでもっとレベルアップしたい
  • BootstrapVue使おうとして挫折した

ここでも全文載せてますが、GitHubでもコードを公開しております。
アドバイスいただけたら嬉しいです。
https://github.com/mitaru/panels

次は何を作ろうかなー。ちょっとネタ切れしてきました。

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

【Rails】タイマー機能

はじめに

Railsでタイマー機能を実装したくて、調べてもなかなか欲しい情報が見つからず:expressionless:

友人から「javascriptで実装できるよ!」と教えてもらったら、あるわ♪あるわ♪情報が:sparkles:

無知過ぎる自分の戒め&【Rails タイマー】で検索した際、ヒットするよう記事に残します:bow_tone1:

どんな機能?

時間を指定し、時間になったらアラートがでるという機能になります。

例えば、3秒と入力し、3秒後にアラートがでる。

こんな感じです(Gyazoなのでタイミングによっては見れなくなります:bow_tone1:)
↓↓↓↓
https://gyazo.com/89773746aac0c2a1640d48e6a536190b

実装

view.html.haml
.timer__input
    %form{name: "timer"}
      %input{type: "text", value: ""}>/
      分
      %input{type: "text", value: ""}>/
      秒
      %br/
      %input{onclick: "cntStart()", type: "button", value: "スタート"}/
      %input{onclick: "cntStop()", type: "button", value: "ストップ"}/
javascript.js
//タイマー処理
var timer1;

//カウントダウン関数を1000ミリ秒ごとに呼び出す関数
function cntStart() {
  document.timer.elements[2].disabled=true;
  timer1 = setInterval("countDown()",1000);
}

// タイマー停止関数
function cntStop() {
  document.timer.elements[2].disabled=false;
  crearInterval(timer1);
}

// カウントダウン関数
function countDown() {
  var min=document.timer.elements[0].value;
  var sec=document.timer.elements[1].value;

  if((min=="")&&(sec=="")) {
    alert("時間を設定してください!");
    reSet();
  }
  else {
    if(min=="")min=0;
    min=parseInt(min);

    if(sec=="")sec=0;
    sec=parseInt(sec);

    tmWrite(min*60+sec-1);
  }
}

// 残り時間を書き出す関数
function tmWrite(int) {
  int=parseInt(int);

  if(int <= 0) {
    reSet();
    alert("時間です!");
  }
  else {
    //残り分すうはintを60で割って切り捨てる
    document.timer.elements[0].value=Math.floor(int/60);
    //残り秒数はintを60で割った余り
    document.timer.elements[1].value=int % 60;
  }
}

// フォームを初期状態に戻す(リセット)関数
function reSet() {
  document.timer.elements[0].value="0";
  document.timer.elements[1].value="0";
  document.timer.elements[2].disabled=false;
  clearInterval(timer1);
}

※上記はCSSはございません:bow_tone1:
(CSSを実装する前に記事にしてしまったためです:sweat_smile:
皆様のデザイン力で素敵なビューを作成してください:sparkles:

感想

javascriptは偉大なり:sun_with_face:
というのが正直な感想です 笑
まだまだ勉強中の身ではありますが、少しずつ知識をつけていきたいですね:relieved:

参考

JavaScript入門 カウントダウンタイマー

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

FullCalendarをそこそこカスタマイズしてみる。

前書き

FullCalendarを結構使ったので、メモメモ。
Fullには使い切ってないけどね。
結構いろいろできるのね。まだまだよくわかんないけど。

やること

表示したいカレンダーの内容は以下。

  • 祝日(日本の)がわかるようにする(なんの日かは表示しなくていい。休みだ!とわかればいい)
  • Google Calendarから予定をひっぱってきて表示する
  • 月カレンダーと、月ごとの予定リストを切り替えて表示できる
  • 大きいスクリーンサイズは月カレンダー、小さいスクリーンサイズは予定リストをデフォルト表示とする
  • 予定リスト表示の場合、予定の場所もリストに表示
  • 予定をタップしたら、Google Calendarにリンク、ではなく、ポップアップ詳細を表示。(この記載からは詳細を割愛)

使うもの

  • FullClaendar v5.0.1
  • jQuery v3.5.1
  • FontAwsome v5.4.2
  • Sass(scss)

ReactやVueのようなフレームワーク、ライブラリ無し。
<script>内に記載。なので、FullClaendarのライブラリは全部入っている状態です。(日本語以外のファイルは入れてないけど)

※作ってる環境にjQuery入っているので、記載しましたが、脱jQuery方向で書いてあったります。
ふとめんどくさくなって、使ったりしてるので、リストアップに残してあります。

さて、設定をしよう

カレンダーのキャンバスを準備

<div id="calendar"></div>

表示するカレンダーの設定(FullCalendarのオプション設定)

今回は
- 日本語化
- カレンダーは月曜はじまり

という設定を入れようと思います。

  document.addEventListener('DOMContentLoaded', function() {

    var calendar = new FullCalendar.Calendar(calendarEl, {
      locale: 'ja',
      height: 'auto',
      firstDay: 1,
      headerToolbar: {
        left: "dayGridMonth,listMonth",
        center: "title",
        right: "today prev,next"
      },
      buttonText: {
        today: '今月',
        month: '',
        list: 'リスト'
      },
    });
    //キャンバスにレンダリング
    calendar.render();
  });

Google Calendar からAPIを使って日本の祝日&予定をもってくる

Google Calenadr APIの設定は割愛しますが、ブラウザ用のAPIを作って、キーを入力。
(もちろん、下記はダミーです。)

日本の祝日として公開されているカレンダーと、実際の予定(イベント)を、eventSourcesに設定します。

  • 祝日の方にはイベントと分ける識別子として、クラスも付与してみます。
  • 今回は認証使わないので、イベントの方のカレンダーもGoogle Clendar側で公開設定するのを忘れずに。
(※ここまでに書いたソースは省略 以下このスタイルで記載します。)
    var calendar = new FullCalendar.Calendar(calendarEl, {
      googleCalendarApiKey: 'xxxxxxxxdummmmmmyyyyyyyyyxxxxxxxx',
      eventSources: [
        {
          googleCalendarId: 'ja.japanese#holiday@group.v.calendar.google.com',
          className: 'event_holiday'
        },
        {
          googleCalendarId: 'hoge-na-calendar@group.calendar.google.com',
        }
      ]
    });

タイトル部分とかの日付表記フォーマットを指定する

ドキュメントはこちら https://fullcalendar.io/docs/date-formatting なのですが、
わかりにくくないですか?わたしだけですか。すみません。
各項目ごとに、年月日とか時間をプロパティとして設定値渡すそうです。

    var calendar = new FullCalendar.Calendar(calendarEl, {
      views: {
        dayGridMonth: {
          titleFormat: { year: 'numeric', month: 'numeric' },
        },
        listMonth: {
          titleFormat: { year: 'numeric', month: 'numeric' },
          listDayFormat: { month: 'numeric', day: 'numeric', weekday: 'narrow' },
          listDaySideFormat: false
        }
      },
    });

曜日にあわせて日にち部分に色をつける

各曜日のclassが付与されているので、cssいじればOKです。

日付部分以外に影響与えないように書いたら、こんなのになりましたが、
もっといい方法があったのかも。

// 当月以外の日(月カレンダーに一緒に表示される前後の月)
.fc-day-other {
  .fc-daygrid-day-top, .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion, .fc-list-day-cushion {
    color: $d-grey;
  }
}

// 土曜日
.fc-day-sat {
  .fc-daygrid-day-top, .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion, .fc-list-day-cushion {
    color: $d-blue;
  }
}

// 日曜日
.fc-day-sun{
  .fc-daygrid-day-top, .fc-scrollgrid-sync-inner .fc-col-header-cell-cushion, .fc-list-day-cushion {
    color: $d-red;
  }
}

祝日にあわせて日にち部分に色をつける

過去に↓なことをやってみました。(Vue ver.ですが。)
https://qiita.com/niever66/items/bef24d1f23e9075eb09d

理由としては、「祝日のことは祝日データ読み込んだところに書けばいいんじゃない?」
と思ったからなのですが。。
リスト表示の場合、予定がありきで日付部分が作られるので、うまくいかなかったのです。

※うまくいかなかった中身は↓です。
「HTMLCollectionのlengthってなになのでしょう?https://teratail.com/questions/274367」
日付部分のDOMをいじりたくても、レンダリング前の状態なゆえに、触れません。

ということで、書き換えです。eventDidMountを使います。
ちなみに、

  • カレンダー表示の場合は、日付部分に色をつける
  • 予定リスト表示の場合は、日付部分に色をつける&曜日の表示の横に[祝]の字を追加

します。

※見直しながら思ったのですが、予定に全日が入ってこない前提だったので、予定リストのつくりが甘いです。

    var calendar = new FullCalendar.Calendar(calendarEl, {
      eventDidMount: function(e) {
        let el = e.el;
        //普通のイベントとわけて考えるため、条件分岐。
        if ( el.classList.contains('event_holiday') ) {
          if ( e.view.type == "dayGridMonth" ) { //カレンダー(月)表示の場合
          //イベントが表示される場所の親をたどって各日の枠にたどり着いたらclassを授けよう
            el.closest('.fc-daygrid-day').classList.add('is_holiday');
          } else if ( e.view.type == "listMonth" ) { //予定リストの場合
          //祝日の兄要素(というか、前要素)にclassを授けよう
            el.previousSibling.classList.add('is_holiday');
       /*
            で、innerText変えたらいいんじゃない?と思ったのだけど、
       innerText変えるとinnerHTMLもHTML要素なくなって
       innerTextと同じ内容になってしまうのですが、、(なぜ?)
            ということで、文字列継ぎはぎします。
            */
            let t = el.previousSibling.innerText;
            let h = el.previousSibling.innerHTML.split(t);
            t = t.slice(0, -1) + '・祝' + t.slice(-1);
            el.previousSibling.innerHTML = h[0] + t + h[1];

       /*
            このままだと、祝日だけど、予定ないわ~、暇人だわ、ワシ。
            が、丸見えになるので、イベントが無い祝日の日付表示部分を削除。
       祝日イベントの次の行が予定でなく、日付だったら削除です。
            */
            if (el.nextSibling.classList.contains('fc-list-day')) {
              el.previousSibling.remove();
            }
            //祝日イベントも削除
       el.remove()
          }
        }
      }
    });

カレンダー表示の場合、↑だと祝日イベントが表示されてしまいます。
jsの中で非表示処理したらよかったのだけど、めんどくさくなったので、cssで処理。

.fc-daygrid-event {
  &.event_holiday {
    display: none;
  }
}

リストに項目を追加

予定リストの表示には、予定の場所も記載するのでした。
ついでに、アイコン表示も追加します。

ドキュメント https://fullcalendar.io/docs/event-render-hooks

eventContent っていうのがあったのですが、触ってみて、よくわからなかったので、
eventDidMountに追記して対応します。

    var calendar = new FullCalendar.Calendar(calendarEl, {
      eventDidMount: function(e) {
        if ( el.classList.contains('event_holiday') ) {
          // ~~ 略(祝日の表記設定) ~~
        } else {
          if ( e.view.type == "listMonth" ) {
            // 予定の時間には「時計」アイコンを追加
            let t = el.querySelector('.fc-list-event-time');
            t.insertAdjacentHTML('afterbegin','<i class="far fa-clock">');
            // 位置情報を取得して、他の要素とあわせたHTML要素として出力
            //「地図のピン」アイコンを追加しておく
            let location = e.event._def.extendedProps.location;
            if ( location !== undefined ) {
              let cell = document.createElement('td');
              cell.classList.add('fc-list-event-location')
              cell.innerHTML = location;
              cell.insertAdjacentHTML('afterbegin','<i class="fas fa-map-marker-alt">');
              el.appendChild(cell);
            }
          }
        }
      },
    });

スクリーンサイズに合わせて表示形式を変更する

読み込み時のスクリーンサイズに合わせてデフォルトの設定を変えます。

  document.addEventListener('DOMContentLoaded', function() {
    let calInitialSettings;

    //他の設定追加したくなったときように、objectにしてみた。
    //768pxで分岐させてみました。
    if ( window.innerWidth < 768 ) {
      calInitialSettings = {initialView: 'listMonth'};
    } else {
      calInitialSettings = {initialView: 'dayGridMonth'};
    }

    var calendar = new FullCalendar.Calendar(calendarEl, {
      initialView: calInitialSettings.initialView,
    });
  });

スクリーンサイズが変わったときにも変更を加えます。
(やってみたけど、ユーザーにとって迷惑じゃね?)

    var calendar = new FullCalendar.Calendar(calendarEl, {
      windowResize: function() {
        if ( window.innerWidth < 768 ) {
          calendar.changeView('listMonth');
        } else {
          calendar.changeView('dayGridMonth');
        }
      },
    });

カレンダーの日付部分がうざいです

locale: 'ja'を入れいていると、月カレンダーの日付部分にいちいちが付きます。うざいです。
Formatterの設定系にどうしてもこの項目の設定がみつかりません。
めちゃくちゃ悩みました。
が、結果としてめちゃくちゃあっさりできました。

    var calendar = new FullCalendar.Calendar(calendarEl, {
      dayCellContent: function(e) {
        e.dayNumberText = e.dayNumberText.replace('', '');
      },
    });

カレンダー・リスト表示のフッターにもページャー追加

カレンダーだけなら気にならなかったんですけどね。リスト表示すると、下までスクロールして「次の月見るには上に戻るんかい!」感ハンパなかったので、ボタンを追加です。

    var calendar = new FullCalendar.Calendar(calendarEl, {
      footerToolbar: {
        right: "prev,next"
      },
    });

ただ、これだと、スクロール位置はそのままでリスト・カレンダーだけ送られるので、
リスト・カレンダーの上部までスクロールも一緒に行ってほしいものです。

なので、カスタムボタンを追加しましょう。

    const calendarEl = document.getElementById('calendar');
    let calendarEl_posT = $(calendarEl).offset().top - 18; //18は上部ゆとり感にテキトウ。

    var calendar = new FullCalendar.Calendar(calendarEl, {
      customButtons: {
        nextWithScroll: {
          icon : 'fa-chevron-right',
          click: function(e) {
            calendar.next();
            $(window).scrollTop(calendarEl_posT); //なるべく使ってなかった急にjQuery!
          }
        },
        prevWithScroll: {
          icon : 'fa-chevron-left',
          click: function(e) {
            calendar.prev();
            $(window).scrollTop(calendarEl_posT);
          }
        }
      },
      footerToolbar: {
        right: "prevWithScroll,nextWithScroll"
      },
    });

ボタンアイコンについては ドキュメント https://fullcalendar.io/docs/buttonIcons
themeSystemの方もbootstrapの方も、(わたしの環境のせい?)で記載してもアイコン表示されなかったので、
css側で追記します。(割愛)

イベントをクリック

イベントをクリックしたら、放っておくとGoogle Calendarに画面遷移します。
遠慮願いたいです。

ということで、ポップアップを作りますが。。割愛します。

  document.addEventListener('DOMContentLoaded', function() {

    var calendar = new FullCalendar.Calendar(calendarEl, {
      eventClick: function(info) {
        //カレンダーへのリンクはさせません。
        info.jsEvent.preventDefault();
        hogehoge(info);
      }
    });

    function hogehoge(info) {
      //なんかやりたいこと
    }

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

vue-cliでvue createしたらnpm run serveに失敗した。

環境

PC - Windows 10 Home
node.js - 12.16.3
npm - 6.14.4

vueプロジェクト作成

まずはグローバルにvue-cliをインストール

> npm install -g @vue/cli

インストールを確認

> vue --version
@vue/cli 4.4.6

適当に作ったディレクトリでプロジェクト作成
今回はvueprojectという名前で進めます。

> vue create .

いろいろ聞かれますが、とりあえずデフォルトで進めます。

  Successfully created project vueproject.
  Get started with the following commands:

 $ npm run serve

と出れば成功。
言われた通り、サーバーを起動します。

> npm run serve

エラー発生

npm ERR! code ELIFECYCLE
npm ERR! vueproject@0.1.0 serve: `vue-cli-service serve`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the vueproject@0.1.0 serve script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\user\AppData\Roaming\npm-cache\_logs\2020-07-03T03_35_30_681Z-debug.log

なるほど。vue-cli-serviceがおかしい?
よく分からん。

解決

ググって発見。
https://teratail.com/questions/219324

プロジェクトディレクトリから下記を削除する
- node_modules
- package-lock.json

その後、モジュールを再インストール

> npm install

完了したら、サーバー起動

> npm run serve
  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.2.107:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

起動確認。

http://localhost:8080/
にアクセスし、下記の画面が表示されたらOK。
FireShot Capture 002 - portfolio - localhost.png

まとめ

別件でも同手順で対処したことあったし、モジュール入れ直しは割とあるあるなのかも。
エラー時にもっといろいろ出る場合は別の原因がありそう。

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

【JavaScript】オブジェクトが未定義だった場合のエラーを回避する方法 ?(クエッションマーク)

こんにちは!
今年も夏が来ましたね〜
エンジニアのみなさま、この時期、夏バテにお気をつけてください...!
リモートワークされている人の中にはエアコンなしの部屋でコーディングされている人もいるんだとか。。。(Macはめちゃくちゃ熱こもりますし、火傷しないか心配です。。)

今回は「オブジェクトが未定義だった場合のエラーを回避する方法」について、調べてみました。
ご指摘やアドバイス等をいただけると、大変助かります。

本記事の対象者

JavaScript初心者〜初中級者

(おさらい)

基本パターン

const obj = {name: 'KumKum'};
console.log(obj.name) // KumKum

基本的なオブジェクトの定義ですね

指定したオブジェクトの中身がnullの場合

const obj = null;
console.log(obj.name) // Uncaught TypeError: Cannot read property 'name' of null

オブジェクトがnullの場合エラーになります

指定したオブジェクトの中身がundefinedの場合

const obj = undefined;
console.log(obj.name) // Cannot read property 'name' of undefined

undefinedの場合も同様ですね

ここから本題

先ほどのエラーを回避するにはどうしたら良いのか。
以下のように?(クエッション) を使うことで、undefinedをセットすることができます。

指定したオブジェクトの中身がnullの場合

const obj = null;
console.log(obj?.name) // undefined

エラーにならず、undefinedが設定されます。

指定したオブジェクトの中身がundefinedの場合

const obj = undefined;
console.log(obj?.name) // undefined

nullと同様、エラーにはなりません。

実際の使用例

const res = getData(); //APIなどの外部からデータを取得
const obj = res?.data; // 取得に失敗した場合、res.dataは存在しない
console.log(obj)

データ取得に成功した場合→値が設定される
データ取得に失敗した場合→undefinedが設定される

まとめ

オブジェクト名?.プロパティ名と指定すると、
オブジェクトがnullまたはundefinedだった場合に、
エラーにならず、undefinedを返す

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

【JavaScript】オブジェクトが未定義だった場合のエラーを回避する方法 ?(クエッション・疑問符)

こんにちは!
今年も夏が来ましたね〜
エンジニアのみなさま、この時期、夏バテにお気をつけてください...!
中には自宅でエアコンなしの部屋でコーディングされている方もいるんだとか。。。(ただでさえ暑いのに、Macとかめちゃくちゃ熱こもりますし、心配です。。)

今回は「オブジェクトが未定義だった場合のエラーを回避する方法」について、調べてみました。
ご指摘やアドバイス等をいただけると、大変助かります。

本記事の対象者

JavaScript初心者〜初中級者

(おさらい)

基本パターン

const obj = {name: 'KumKum'};
console.log(obj.name) // KumKum

基本的なオブジェクトの定義ですね

指定したオブジェクトの中身がnullの場合

const obj = null;
console.log(obj.name) // Uncaught TypeError: Cannot read property 'name' of null

オブジェクトがnullの場合エラーになります

指定したオブジェクトの中身がundefinedの場合

const obj = undefined;
console.log(obj.name) // Cannot read property 'name' of undefined

undefinedの場合も同様ですね

ここから本題

先ほどのエラーを回避するにはどうしたら良いのか。
以下のように?(クエッション) を使うことで、undefinedをセットすることができます。

指定したオブジェクトの中身がnullの場合

const obj = null;
console.log(obj?.name) // undefined

エラーにならず、undefinedが設定されます。

指定したオブジェクトの中身がundefinedの場合

const obj = undefined;
console.log(obj?.name) // undefined

nullと同様、エラーにはなりません。

まとめ

オブジェクト名?.プロパティ名と指定すると、
オブジェクトがnullまたはundefinedだった場合に、
エラーにならず、undefinedを返す

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

Vue Composition API リアクティブ周りの関数一覧

Vue Composition APIのリファレンスを見たらリアクティブ周りのAPIが大量に追加されていたのでまとめてみました。

この記事は以下バージョン時点の情報です。

Vue Composition APIのAPI Reference に掲載されているいくつかのAPIは、まだVue2系で使えるComposition APIのプラグイン(vuejs/composition-api)でサポートされていません。
そのAPIについては :warning: マークを末尾につけています。サポート対応状況についてはこちらの記載をもとにしています。

reactive

引数に渡したオブジェクトのリアクティブなプロキシを返します。
Vue2系のVue.observable()と同等の機能です。ネストされたオブジェクトもリアクティブな値として保持されます。

const obj = reactive({
  name: "Foo",
  address: {
    city: "Bar"
  }
})

obj.address.city = "Huga"
console.log(obj.address.city) // Huga

型定義

function reactive<T extends object>(target: T): UnwrapNestedRefs<T>;

ref

プリミティブな値をrefオブジェクトに変換して返します。refオブジェクトはvalueプロパティでリアクティブな値にアクセスできます。
refの値としてオブジェクトが代入されている場合は、reactiveメソッドによってオブジェクトを深くリアクティブにします。

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

型定義

interface Ref<T = any> {
    [RefSymbol]: true;
    value: T;
}

function ref<T extends object>(value: T): T extends Ref ? T : Ref<UnwrapRef<T>>;
function ref<T>(value: T): Ref<UnwrapRef<T>>;
function ref<T = any>(): Ref<T | undefined>;

computed

コールバック関数を受け取り、戻り値をリアクティブでReadonlyなrefオブジェクトとして返します。
refオブジェクトの値は、コールバック関数内にあるリアクティブな値の変化に応じて再計算されます。
(getterとsetterを持つオブジェクトを渡して書き込み可能なrefオブジェクトを作ることもできます)

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2
count.value++
console.log(plusOne.value) // 3
plusOne.value = 4 // Error

型定義

// read-only
function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>>

// writable
function computed<T>(options: {
  get: () => T
  set: (value: T) => void
}): Ref<T>

readonly:warning:

オブジェクト(ref or reactive or プレーン)を受け取り、読み取り専用のリアクティブなプロキシを返します。
軽量な独自storeの周りの実装で使えそうです。

※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。

const original = reactive({ count: 0 })

const copy = readonly(original)
console.log(copy.count) // 0

original.count++
console.log(copy.count) // 1
console.log(isReactive(copy)) // true
copy.count++ // Error

型定義

function readonly<T extends object>(target: T): Readonly<UnwrapNestedRefs<T>>;

customRef :warning:

依存関係の追跡と更新のトリガーを明示的に制御するカスタマイズされたrefオブジェクトを返します。
限定された用途でリアクティブな値を使いたい時に良さそうです。

※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。

// 代入と参照時に必ずconsole.log()を実行するref
const useLoggerRef = (value: string) => {
  return customRef((track, trigger) => ({
    get() {
      console.log(value)
      track()
      return value
    },
    set(newValue: string) {
      console.log(newValue)
      value = newValue
      trigger()
    }
  }))
}

const foo = useLoggerRef("")

foo.value = "Hoge" // console.log("Hoge")が実行される
foo.value = "Bar" // console.log("Bar")が実行される
const bar = foo.value // console.log("Bar")が実行される

型定義

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}

markRaw

リアクティブ化されない(プロキシされない)オブジェクトを返します。
適宜markRawを使いプロキシ変換をスキップすることで、不変のデータソースを持つ大規模なリストのレンダリングなどでパフォーマンスを向上させることができます。

const foo = markRaw({
  baz: "baz"
})
const bar = reactive(foo)
isReactive(bar) // false reactive関数を実行してもリアクティブにならない

型定義

function markRaw<T extends object>(value: T): T

shallowReactive

浅いリアクティブなオブジェクトを返します。ネストされたオブジェクトはリアクティブ化されません。

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

isReactive(state) // true
isReactive(state.nested) // false

型定義

function shallowReactive<T extends object>(target: T): T;

shallowRef

浅いリアクティブなrefオブジェクトを返します。ネストされたオブジェクトはリアクティブ化されません。

const foo = ref({})
const bar = shallowRef({})

isReactive(foo.value) // true
isReactive(bar.value) // false

型定義

function shallowRef<T>(value: T): T extends Ref ? T : Ref<T>;

shallowReadonly :warning:

浅いreadonlyなオブジェクトを返します。ネストされたオブジェクトはreadonlyになりません。

※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// mutating state's own properties will fail
state.foo++
// ...but works on nested objects
isReadonly(state.nested) // false
state.nested.bar++ // works

型定義

function shallowReadonly<T extends object>(target: T): Readonly<{
    [K in keyof T]: UnwrapNestedRefs<T[K]>;
}>;

toRaw

プロキシされたリアクティブなオブジェクトの生データを返します。

const foo = {}
const reactiveFoo = reactive(foo)

isReactive(toRaw(reactiveFoo)) // false
toRaw(reactiveFoo) === foo // true

型定義

function toRaw<T>(observed: T): T;

unref

引数がrefの場合はref.valueの値を、それ以外だったら引数自体を返します。
val = isRef(val) ? val.value : val の糖衣構文です。

const foo = ref(1)
const bar = 1

unref(foo) // 1
unref(bar) // 1

型定義

function unref<T>(ref: T): T extends Ref<infer V> ? V : T;

toRef

reactiveなオブジェクトの指定のプロパティをrefオブジェクトに変換して返します。
propsの値をリアクティブを切らず、setup関数外のコンポーザブルな関数に渡すときなどに使えます。

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')
isRef(fooRef) // true

型定義

function toRef<T extends object, K extends keyof T>(object: T, key: K): Ref<T[K]>;

toRefs

reactiveなオブジェクトの全てのプロパティをrefオブジェクトに変換したオブジェクトを返します。

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
isRef(stateAsRefs.foo) // true
isRef(stateAsRefs.bar) // true

型定義

type ToRefs<T = any> = {
    [K in keyof T]: Ref<T[K]>;
};
function toRefs<T extends object>(object: T): ToRefs<T>;

isRef

refオブジェクトでラップされているかどうかを判定します。

const foo = ref("foo");
const bar = reactive({
  bar: "bar"
});

isRef(foo) // true
isRef(bar) // false

型定義

isRef<T>(value: any): value is Ref<T>;

isReactive

reactiveかどうか判定します。reactiveで作られたオブジェクトをラップしたreadonlyなオブジェクトの場合にもtrueを返します。

const foo = ref("foo");
const bar = reactive({
  bar: "bar"
});
const baz = readonly(bar);
const hoge = readonly(foo)

isReactive(foo) // false
isReactive(bar) // true
isReactive(baz) // true
isReactive(hoge) // false

型定義

function isReactive(value: unknown): boolean;

isReadonly :warning:

オブジェクトがreadonlyかどうか判定します。

※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。

const foo = ref("foo");
const bar = reactive({
  bar: "bar"
});
const baz = readonly(bar);

isReadonly(foo) // false
isReadonly(bar) // false
isReadonly(baz) // true

型定義

function isReadonly(value: unknown): boolean;

isProxy :warning:

オブジェクトがProxyか判定します。reactiveかreadonlyで作成したオブジェクトが該当します。

※ 2020/07/03現在、vue-composition-apiプラグインではサポートされてません。

const foo = ref("foo");
const bar = reactive({
  bar: "bar"
});
const baz = readonly(bar);

isProxy(foo) // false
isProxy(bar) // true
isProxy(baz) // true

型定義

function isProxy(value: unknown): boolean;

終わりに

以上「Vue Composition APIのリアクティブ関連の関数一覧」でした。思った以上にAPIが多いですね。
業務ではref, reactive, toRef, toRefs, isRefくらいしか使ってなかったです。
今後はどんどん活用していきたいです。

参考

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

PackerとAnsibleを使用してAlibaba Cloud ECSインスタンス上にReact JSアプリケーションイメージを作成する

このガイドでは、Packerを使ってAlibaba Cloud ECSインスタンス上にマシンイメージを構築する方法と、マシンイメージに持たせたいものを書くためのAnsibleについてお話します。

前提条件

このガイドに従うために専門家である必要はありません。必要なのは、Alibaba CloudのアカウントとAccess keyだけです。こちらのリンクをクリックすると、Alibaba Cloud上でのアクセスキーの作成方法が表示されます。

ステップ1:パッカーのインストール

私たちのシステムにpackerをインストールするには、packerの公式インストールページに従うか、パッケージマネージャ、Windows用のchocolateyとmacOS用のhomebrewを使用することができます。パッケージマネージャを使えば、パスに環境変数を追加する手間が省けます。

chocolatey を使って Windows に packer をインストールするには

1、chocolatey パッケージマネージャをインストールします。chocolatey をインストールするには、管理者として cmd を開き、以下のコマンドを貼り付けて chocolatey パッケージマネージャをインストールします。

@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"

2、chocolatey がインストールされていることを確認するには、 choco -v コマンドを実行します。

3、以下のコマンドを実行して、packerをインストールします。

choco install packer

4、packerのインストールを確認するには、次のように実行します。

packer -v

5、Packerのバージョンがインストールされているはずです。
インストールプロセスを示すGif画像

image.png

homebrewを使ってMacOSにpackerをインストールするには

1、homebrewがまだインストールされていない場合は、インストールしてください。macOSにhomebrewをインストールするには、ターミナルを開いて以下のコマンドを貼り付けます。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

2、以下のコマンドを実行して、packerをインストールします。

brew install packer

3、packerのインストールを確認するには、以下を実行します。

packer -v

4、Packerのバージョンがインストールされているはずです。
MacOSへのインストールを示すGif画像
image.png

ステップ2: Reactアプリのマシンイメージを構築する

マシンイメージを構築するには、テンプレートファイルを作成する必要があります。テンプレート ファイルは、構築するイメージを定義するために使用します。テンプレートファイルはJSON形式で、Packerの様々なコンポーネントを設定する異なるキーのセットを持っています。

1、example.jsonという名前のテンプレートファイルを作成し、以下のコードを貼り付けます。

  {
      "variables": {
        "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
        "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
      },
      "builders": [{
        "type": "alicloud-ecs",
        "access_key": "{{user `access_key`}}",
        "secret_key": "{{user `secret_key`}}",
        "region": "us-west-1",
        "image_name": "ReactJS-Application",
        "instance_type": "ecs.t5-lc2m1.nano",
        "source_image": "ubuntu_16_0402_32_20G_alibase_20180409.vhd",
        "io_optimized":"true",
        "image_force_delete":"true",
        "ssh_username": "root",
        "internet_charge_type": "PayByTraffic"
      }],
      "provisioners": [{
        "type": "shell",
        "script": "installAnsible.sh"
       },{
       "type": "ansible",
      "playbook_file": "playbook.yml"
     }]
    }

変数セクションでは、テンプレートファイル内のどこでも使用できる変数を定義し、
env関数を指定して環境変数からaccess_keysecret_keyの値を取得しています。

ビルダーセクションには、特定のビルダーを設定するJSONオブジェクトの配列が含まれています。ビルダーは、マシンを作成し、そのマシンをイメージに変換する役割を担う packer のコンポーネントです。

プロビジョナーセクションでは、組み込みのソフトウェアやサードパーティ製のソフトウェアを使用して、起動後のマシンイメージをインストールしたり設定したりします。このガイドでは、プロビジョナーとして ansibleshell を使います。

2、ALICLOUD_ACCESS_KEYALICLOUD_SECRET_KEYをエクスポートするには、ターミナルで以下のコマンドを実行します。

 export ALICLOUD_SECRET_KEY="YOUR_ALICLOUD_ACCESS_KEY"
   export ALICLOUD_SECRET_KEY="YOUR_ALICLOUD_SECRET_KEY"

3、playbook.ymlという名前の新しいAnsibleファイルを作成し、以下のコードを貼り付けます。example.jsonplaybook.ymlは同じディレクトリまたはフォルダに作成します。

   ---
     - hosts: all
       become: true

       vars:
         NODEJS_VERSION: 8
         domain: "localhost"

       tasks:
       - name: Add gpg key for nodejs
         apt_key:
           url:  "https://deb.nodesource.com/gpgkey/nodesource.gpg.key"
           state: present
       - name: Add nodejs LTS to apt repository
         apt_repository:
           repo: "deb https://deb.nodesource.com/node_{{ NODEJS_VERSION }}.x {{ ansible_distribution_release }} main"
           state: present
           update_cache: yes
       - name: Install nodejs
         apt:
           name: nodejs
           state: present
       - name: Setup React application
         shell:
            cmd: |
              npx create-react-app my-app # Setup our react application
              cd /root/my-app
              npm install -g pm2   # Install Pm2, A nodejs process manager which enables us to run our application in the background process
       - name: Install nginx
         apt:
           name: nginx
           state:  present
           update_cache: yes
       - name: Remove nginx default configuration
         file:
           path: /etc/nginx/sites-enabled/default
           state: absent
       - name: enable reverse proxy # This enables us to use the public IP without passing in the port on our browser address bar
         shell:
           cmd: |
             cat > /etc/nginx/sites-available/my-app <<EOF
             server {
               listen 80;
               server_name {{ domain }};
               location / {
                 proxy_pass 'http://127.0.0.1:3000';
               }
             }
             EOF
       - name: create symlinks for nginx configuration # make sure the nginx configuration files are always the same.
         file:
           src:  /etc/nginx/sites-available/my-app
           dest: /etc/nginx/sites-enabled/my-app
           state: link
         notify:
         - restart nginx
       handlers:
         - name: restart nginx # restart nginx service
           service:
             name: nginx
             state: restarted

4、テンプレートファイルを検証するには、packer validate example.jsonを実行します。これにより、テンプレートファイルに構文エラーなどのエラーがないことを確認します。
5、packer build example.jsonを実行してReactJSイメージをビルドします。

ステップ3: Reactイメージを使ってインスタンスを起動する

先ほど作成した画像を使用してインスタンスを作成するには、以下の手順に従います。

1、アリババクラウドコンソールにログオン
2、ダッシュボードのサイドナビゲーションバーにあるElastic Compute Serviceをクリックします。
3、インスタンスを作成するには、[Create Instance] の説明が記載されたボタン、またはこのリンクをクリックします。
4、基本設定では、以下の操作を行います。

----- 1、課金方法については、「Pay-As-You-Go」をクリックしてください。
----- 2、地域については、ドロップダウンをクリックして、US West 1 (Silicon Valley)を選択してください。
----- 3、インスタンスタイプでは、「エントリーレベル(共有)」をクリックします。
----- 4、画像は「Custom Image」ボタンをクリックします。Custom Imageの下のドロップダウンメニューをクリックして、作成したイメージはReactJS-Applicationを選択します。
----- 5、Next: Networkingボタンをクリックします。

5、2つ目のステップであるネットワーキングについては、以下の手順に従ってください。
-----1、Networkセクションでは、「VPC」ドロップダウンメニューをクリックし、デフォルトのVPCを選択します。
-----2、Security Groupセクションでは、「ポート設定に移動」をクリックして、アプリケーションのポートを設定します。
-----3、Add Security Group Ruleをクリックし、Port Rangeを80に、Authorization Objectsを0.0.0.0.0/0に設定します。これを繰り返しますが、ポート範囲を3000に設定します。
ポート80はHTTPリクエストのためのもので、ポート300`はリアクトアプリケーションが実行されているポートです。
-----4、セキュリティグループを設定したら、次へをクリックします。Next: System Configurations ボタンをクリックします。

6、3番目のステップであるシステム設定については、以下の手順に従ってください。
-----1、詳細設定(インスタンスRAMのロールまたはクラウドイニットに基づく)ドロップダウンをクリックして、以下のコードをユーザーデータフィールドボックスに貼り付けます。

#!/usr/bin/env bash
cd /root/my-app
pm2 start /root/my-app/node_modules/react-scripts/bin/react-scripts.js --name my-app -- start

上記のスクリプトはインスタンスの作成時に実行されるので、アプリケーションを起動するためにインスタンスに SSH 接続する必要はありません。

7、プレビューボタンをクリック
8、利用規約に同意し、「インスタンスの作成」ボタンをクリックします。
9、インスタンスが起動したら、インターネットの IP アドレスをコピーしてブラウザのアドレスバーに貼り付けます。ReactアプリケーションのWebページが表示されるはずです。
インスタンスを起動するプロセスを示すgif画像は以下の通りです。

image.png

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

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

javascriptの要素アクセス諸々

HTML要素のタグ内のドキュメント取得

innerHTML:HTMLタグを含めた文字列
textContent:HTMLタグを除いた文字列

HTML要素のタグ内のドキュメント設定

textContent:HTMLタグがない場合
HTMLタグ含めたい場合はcreateElementやcreateTextNodeでタグ要素を生成して
appendChildで挿入する

HTML要素の親子取得

<ul id="login-users">
<li id="suzuki-dhyi">suzuki</li>
<li id="tanaka-ddha">tanaka</li>
<li id="sato-xydu">sato</li>
</ul>
// ul要素取得
let ul = document.getElementById('login-users');
// 添え字アクセス
let _user = ul.children;
_user[0];
_user[1];

// 子要素をループで回す
Array.from(ul.children).forEach(user => {
console.log(user);
});
// 子要素から親要素を取得
let user1 = document.getElementById('suzuki-dhyi');
let par = user1.parentNode;

// 要素内の検索
let tanaka = ul.querySelector('tanaka-ddha');

// 子要素の末尾に追加:appendChild
// 子要素の最初に追加:insertBefore

// 要素の削除:remove
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webhooksを使ったReactアプリのデプロイとUbuntuでのSlackの統合

このチュートリアルでは、Ubuntuを使用したAlibaba Cloud Elastic Compute Service (ECS)インスタンス上でWebhooksを使用してSlackの通知をトリガーします。

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

前提条件

1、Alibaba Cloud Elastic Compute Service (ECS)を有効化し、有効な支払い方法を確認する必要があります。新規ユーザーの場合は、Alibaba Cloudアカウントで無料アカウントを取得することができます。ECSインスタンスのセットアップ方法がわからない場合は、このチュートリアルまたはクイックスタートガイドを参照してください。ECSインスタンスは、少なくとも2GB RAMと1コアプロセッサを搭載している必要があります。
2、Alibaba Cloudから登録されたドメイン名。すでにAlibaba Cloudまたは他のホストからドメインを登録している場合は、そのドメインネームサーバーレコードを更新することができます。
3、ドメイン名は、あなたのAlibaba Cloud ECSのIPアドレスを指している必要があります。
4、Alibaba CloudのVNCコンソールまたはPCにインストールされているSSHクライアントにアクセスします。
5、サーバーのホスト名を設定し、root権限を持つユーザーを作成します。

Ubuntuのシステムを更新する

パッケージのインストールを進める前に、以下のコマンドを使用してUbuntuシステムをアップデートしてください。このコマンドを実行するには、root ではないユーザーから sudo 権限でログインすることを忘れないでください。このコマンドを実行すると、Is this ok? 'y'と入力してEnterキーを押します。

# sudo apt update && sudo apt upgrade

Nginxサーバーのインストール

nginxサーバーをインストールするためには、以下の手順に従う必要があります。

ステップ1

インストールするには、以下のコマンドを実行します。

# sudo apt-get install nginx

ステップ2

以下のコマンドを実行してNginxサーバーを起動します。

# sudo systemctl start nginx

Nginxサーバーの状態を確認するには、以下のコマンドを実行します。

# sudo systemctl status nginx

インストールを確認するには、アリババクラウドECSのIPアドレスか、IPアドレスを指したドメイン名でアクセスします。私の場合はドメイン名経由でアクセスし、以下の画面が読み込まれました。

Gitのインストール

ローカルマシンだけでなく、サーバーにもgitをインストールする必要があります。Gitをインストールして設定するには、以下の手順に従います。

ステップ1

Gitをインストールするには、コマンドを実行します。

# sudo apt-get install git

ステップ 2 (オプション)

以下のコマンドを実行して Git を設定します。名前と有効なメールアドレスを指定してコミットメッセージに正しい情報が含まれるようにします。

# git config --global user.name "Aareez"
# git config --global user.email "xyz@example.com"

Node.js のインストール

Node.jsは、ブラウザの外でJSコードを実行するクロスプラットフォーム・オープンソースのJSランタイム環境です。Node.jsのバージョンはUbuntuのデフォルトリポジトリにあります。PPAを使用してNode.jsをインストールするには、以下の手順に従ってください。

以下のコマンドを実行して、お好みのバージョンのスクリプトを取得します。

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

では、以下のコマンドでスクリプトを実行します。

# sudo bash nodesource_setup.sh

インストールするには、コマンドを実行します。

# sudo apt-get install -y nodejs

これで無事にNode.jsのインストールが完了しました。

gccとg++をインストール

また、ネイティブアドオンを構築するための開発ツールも必要になるかもしれません。それらをインストールするには、コマンドを実行します。

# sudo apt-get install gcc g++

Yarnのインストール

Yarn パッケージマネージャをインストールするには、以下のコマンドを実行します。

 # curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
 #  echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
# sudo apt-get update && sudo apt-get install yarn

npmを動作させるためには、build-essentialをインストールする必要があります。インストールするには以下のコマンドを実行してください。

# sudo apt-get install build-essential

create-react-appを使ってReactアプリを作成する

まず、create-react-appでWebhookをテストするためのアプリケーションを構築します。この後、GitHubのリポジトリを作成し、そのリポジトリにプロジェクトのコードをプッシュする必要があります。

create-react-appのノードモジュールをグローバルリポジトリに追加するには、以下のコマンドを実行してシェルから利用できるようにする必要があります。

# sudo npm install -g create-react-app

aareez-projectという名前のプロジェクトを作成するには、以下のコマンドを実行します。

# sudo create-react-app aareez-project

以下のコマンドで作成したプロジェクトディレクトリに移動します。

# cd aareez-project

aareez-projectディレクトリにあるgitでリポジトリを初期化します。そのためには、以下のコマンドを実行します。

# sudo git init

ここで、GitHub の URL と一緒にリモートオリジンを追加します。

# sudo git remote add origin https://github.com/itsaareez1/react-example

プロジェクトディレクトリにあるファイルをステージします。

# sudo git add 

それらをコミットし、以下のコマンドを実行してリポジトリにプッシュします。

# sudo git commit -m "initial commit"
# sudo git push -f origin master

React用ディレクトリの設定とNginxサーバーの設定

ここでは、Githubのリポジトリからreactアプリのクローンを取得する必要があります。

ルートディレクトリに移動します。

# cd ~

これでGithubからファイルをクローンします。

# sudo git clone https://www.github.com/itsaareez1/react-example 

上記のgitリンクを使ってプロジェクトをクローンします。

コマンドでcloneディレクトリに移動します。

# cd react-example

Nginxが動作するようにindex.htmlやJSファイル、CSSファイルなどのページを格納したフォルダを作成するには、yarnのbuildコマンドを実行する必要があります。

# sudo yarn && sudo yarn build

ここで、Nginx経由でファイルにアクセスできるようにするために、シンボリックリンクを作成する必要があります。Nginxはアプリケーションを/var/wwwディレクトリに配置します。 シンボリックリンクの作成は以下のコマンドを実行してください。

# sudo ln -s ~/react-example /var/www/react-example

Nginxがシンボリックリンクを正しく動作させるための権限を付与するには、以下のコマンドを実行します。

# sudo chmod -R 755 /var/www

ここで、利用可能なサイトディレクトリにnginxサーバーブロックを設定します。以下のコマンドを実行すると、ファイルが開きます。

# sudo nano /etc/nginx/sites-available/react

開いたファイルに以下のテキストをコピーし、softpedia.xyzをドメイン名またはECSのIPアドレスに変更して保存してください。

server {
        listen 80;

        root /var/www/react-example/build;
        index index.html index.htm index.nginx-debian.html;

        server_name softpedia.xyz;

        location / {
                try_files $uri /index.html;
        }
}

ここで、利用可能なサイトディレクトリにある react の設定用のシンボリックリンクを、有効になっているサイトディレクトリに作成します。

# sudo ln -s /etc/nginx/sites-available/react /etc/nginx/sites-enabled/react

nginxの構文が正しいかどうかを確認するには、以下のコマンドを実行します。

# sudo nginx -t

設定をロードするには、nginx サーバーを再起動します。

# sudo systemctl restart nginx

すべての設定が正しく行われているかどうかを確認するために、ブラウザでIPアドレスやドメインにアクセスすると、以下のような画面が表示されます。

image.png

Webhooks のインストールと設定

設定可能なエンドポイントを持つ HTTP サーバをフックと呼びます。Webhook サーバは、いくつかの HTTP リクエストを受信したときに、設定可能なルールのセットに従ったカスタマイズ可能なコードを実行します。

ホームに移動する

# cd ~

webhookをダウンロードします。

# sudo wget https://github.com/adnanh/webhook/releases/download/2.6.6/webhook-linux-amd64.tar.gz

ダウンロードしたフォルダを展開します。

# sudo tar -xvf webhook-linux-amd64.tar.gz

バイナリを/usr/local/binに移動して、自分の環境で利用できるようにします。

# sudo mv webhook-linux-amd64/webhook /usr/local/bin

ダウンロードしたフォルダを削除します。

# sudo rm -rf webhook-linux-amd64*

ここで、主にサードパーティ製のアプリケーションを配置するoptフォルダ内に、スクリプトとフックのためのディレクトリを作成します。

# sudo mkdir /opt/scripts

# sudo mkdir /opt/hooks

これらのディレクトリの権限をユーザ名に割り当てます。

# sudo chown -R aareez:aareez /opt/scripts
# sudo chown -R aareez:aareez /opt/hooks

GitHubがHTTPリクエストを送信すると、JSON配列ファイルで定義されたルールに基づいてwebhookが起動されます。ここで、webhook用の設定ファイルを作成する必要があります。そのためには、以下のコマンドを実行します。

# sudo nano /opt/hooks/hooks.json

開いたファイルに以下のテキストをコピーします。

[
  {
    "id": "redeploy-app",
    "execute-command": "/opt/scripts/redeploy.sh",
    "command-working-directory": "/opt/scripts",
    "pass-arguments-to-command":
    [
      {
        "source": "payload",  
        "name": "head_commit.message"
      },
      {
        "source": "payload",
        "name": "pusher.name"
      },
      {
        "source": "payload",
        "name": "head_commit.id"
      }
    ],
    "trigger-rule":
    {
      "and":
      [
        {
          "match":
          {
            "type": "payload-hash-sha1",
            "secret": "654321Ab", 
            "parameter":
            {
              "source": "header",
              "name": "X-Hub-Signature"
            }
          }
        },
        {
          "match":
          {
            "type": "value",
            "value": "refs/heads/master",
            "parameter":
            {
              "source": "payload",
              "name": "ref"
            }
          }
        }
      ]
    }
  }
]

GitHub の通知を設定する

GitHub リポジトリで、master へのコミットが発生した際に HTTP リクエストが発生するように設定するには、以下の手順に従う必要があります。

1、GitHub リポジトリを開き、以下のように設定をクリックします。
image.png

2、この後、Webhooksセクションに移動します。

image.png

3、Webhookの追加ボタンをクリックします。
image.png

4、Payload URL には http://your_server_ip:9000/hooks/redeploy-app と入力してください。softpedia.xyzをECSのIPアドレスまたはドメインに置き換えることを忘れないでください。Content typeには、option application/jsonを選択します。hooks.jsonファイルでは、秘密鍵654321Abを使用していますが、任意のものを使用することができます。秘密鍵には654321Abを使用します。その後、Just the push eventオプションを選択します。アクティブチェックボックスにチェックを入れて、Add webhookボタンをクリックします。

image.png

これで、あなたや誰かが GitHub リポジトリにコミットするたびに、GitHub はコミットイベントに関する情報を含む Payload を含む POST リクエストを送信するようになりました。

リデプロイスクリプトを書く

redeploy.shスクリプトにWebhookを指定しているので、スクリプトを作成する必要があります。以下のコマンドを実行して、スクリプトを書くためのファイルを開きます。

# sudo nano /opt/scripts/redeploy.sh

以下のコードをコピーして、開いたファイルに貼り付けてください。

#!/bin/bash -e

function cleanup {
      echo "Error occoured"
      # !!Placeholder for Slack notification
}
trap cleanup ERR

commit_message=$1 # head_commit.message
pusher_name=$2 # pusher.name
commit_id=$3 # head_commit.id

# !!Placeholder for Slack notification

cd ~/react-example/
sudo git pull origin master
sudo yarn && yarn build

            # !!Placeholder for Slack notification 

以下のコマンドでスクリプトを実行可能な状態にします。

# sudo chmod +x /opt/scripts/redeploy.sh

Nginxは/var/wwwフォルダにreact-exampleを置くように設定されているため、上記のスクリプトを実行するとビルドディレクトリが更新され、新しいファイルがサーバに送られてきます。

## run webhook server to test configuration. Execute the command below for it.
# webhook -hooks /opt/hooks/hooks.json -verbose

すべてが正常に動作すると、以下のような画面が表示されます。

image.png

テストのためには、Webhookを起動したまま、複製セッションを開き、以下のコマンドを実行してください。

# sudo git commit --allow-empty -m "Trigger notification"
# sudo git push origin master

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

image.png

Slackの通知を統合する

Slackの通知を追加するには、redeploy.shを修正してSlackからの通知を送受信するようにします。Slackの設定は以下の手順で行います。

1、左上のドロップダウンメニューをクリックし、「Slackのカスタマイズ」を選択します。
2、次に、アプリの設定に移動します。
3、管理パネルで、「統合を選択」を選択します。
4、Incoming Hooks integrationを検索します。
5、Add Configurationをクリックします。
6、チャンネルを選択または作成します。
7、Add Incoming Webhooks integrationをクリックします。
Slackのウェブフック設定が表示されます。WebhookのURLをメモしておきます。コマンドを使ってredeploy.shスクリプトを開きます。

# sudo nano /opt/scripts/redeploy.sh

前のコードを削除し、以下のコードを追加します。slack_webhook_urlWebhookのURLに置き換えることを忘れないでください。

#!/bin/bash -e

function cleanup {
      echo "Error occoured"
      curl -X POST -H 'Content-type: application/json' --data "{
              \"text\": \"Error occoured while building app with changes from ${pusher_name} (${commit_id} -> ${commit_message})\",
              \"username\": \"buildbot\",
              \"icon_url\": \"https://i.imgur.com/JTq5At3.png\"
      }" your_slack_webhook_url
}
trap cleanup ERR

commit_message=$1 # head_commit.message
pusher_name=$2 # pusher.name
commit_id=$3 # head_commit.id

curl -X POST -H 'Content-type: application/json' --data "{
        \"text\": \"Started building app with changes from ${pusher_name} (${commit_id} -> ${commit_message})\",
        \"username\": \"buildbot\",
        \"icon_url\": \"https://i.imgur.com/JTq5At3.png\"
}" your_slack_webhook_url

cd ~/react-example/
sudo git pull origin master
sudo yarn && sudo yarn build

curl -X POST -H 'Content-type: application/json' --data "{
        \"text\": \"Build and deploy finished with changes from ${pusher_name} (${commit_id} -> ${commit_message})\",
        \"username\": \"buildbot\",
        \"icon_url\": \"https://i.imgur.com/JTq5At3.png\"
}" your_slack_webhook_url

テストは、Webhookを起動した状態で、セッションを複製して以下のコマンドを実行してください。

# sudo git commit --allow-empty -m "Trigger notification"
# sudo git push origin master

これで完了です! Slackチャンネルでは、開始・終了したアプリケーションのビルドに関する通知やメッセージを受け取ることができます。

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

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

なぜサーバーサイド言語と合わせて、JabvaScriptを理解しておく必要があるのか。

PHPは通信を行わないと変化が起こらない。サーバーサイド。
例:現在時刻は、リロードした時にだけわかる。
JavaScriptは、通信がなくても変化が起きる。クライアントサイド。
例:現在時刻は、毎秒変わる。

javascriptのデメリットは、データの保存ができない。その時その端末のみのアクション、データ、記録。

Ajax(非同期通信)=JavaScript

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

JavaScript: EventTargetでイベントを送受信する方法

この投稿ではJavaScriptのEventTargetを使って、ブラウザ上でイベントを送信したり受信したりする方法を紹介します。

サーバサイドでNode.jsを使っていると、ビルトインライブラリにeventsがあり、EventEmitterクラスでイベントの送受信ができて便利ですが、これはNode.js固有のものなのでブラウザでは使えません。代わりに、クライアントサイドではEventTargetを使うことで、EventEmitterのようなことができます。

EventTargetは多くのブラウザが対応

EventTargetは、多くのブラウザが対応しており、特に外部ライブラリのインストールを必要としません。

CleanShot 2020-07-03 at 10.01.46@2x.png

EventTargetの基本的な使い方

まず、EventTargetnewします。

const events = new EventTarget()

イベントを受信するイベントリスナーの登録は、addEventListenerメソッドでします:

const fooListener = ({ type, detail }) => console.log({ type, detail })
events.addEventListener('foo', fooListener)

イベントリスナーの第一引数のオブジェクトにはいろいろな情報が入ってきますが、typeプロパティとdetailプロパティは重要です。typeプロパティにはイベント名が、detailプロパティには送信されたイベントのカスタムデータが入ってきます。

イベントを送信するには、dispatchEventメソッドを使います:

events.dispatchEvent(new CustomEvent('foo', { detail: 'message1' }))

dispatchEventの第一引数は、CustomEventです。CustomEventコンストラクタには第一引数にイベント名、第二引数のdetailプロパティに一緒に送りたいカスタムデータを入れます。上の例ではdetailに文字列を入れていますが、配列やオブジェクトでも大丈夫です。

以上の基本的な使い方を踏まえて、イベントの送受信をする簡単なサンプルコードが次です:

// EventTargetの作成
const events = new EventTarget()

// イベントリスナーの登録
const fooListener = ({ type, detail }) => console.log({ type, detail })
events.addEventListener('foo', fooListener)

// イベントの送信
events.dispatchEvent(new CustomEvent('foo', { detail: 'message1' }))
events.dispatchEvent(new CustomEvent('foo', { detail: 'message2' }))
events.dispatchEvent(new CustomEvent('foo', { detail: 'message3' }))

これを実行すると、3つのイベントが送信され、イベントリスナーはその3イベントを受信します。Chromeでの実行結果は次のようになります:

CleanShot 2020-07-03 at 10.12.22@2x.png

以上が、EventTargetの基本的な使い方です。

関連ページ

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

JSですべての子孫要素に処理する

js
let parentNodeList = document.querySelectorAll('#parent *');
changeAllDescendants(parentNodeList);

function changeAllDescendants(nodeList) {
  for (let elm of nodeList) {
    console.log(elm); //やりたい処理
  }
}

もしくは

js
let parentElm = document.querySelector('#parent');
changeAllDescendants(parentElm);

function changeAllDescendants(parent) {
  console.log(parent); //やりたい処理
  if (!parent.hasChildNodes) {
    return
  }
  // 次の子へ
  for (let child of parent.childNodes) {
    changeAllDescendants(child);
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【javascript】一言で、 Javascript 他ファイルの呼び出し おまけ付き

【ゴール】

他jsファイルを呼び出し、読み込む

【メリット】

■javascript 理解度向上
■コードの可読性向上 整理出来るので

【開発環境】

■ Mac OS catalina

【コード】

以下2点処理必要

①importを使用

*読み込ませたいファイルに
*ファイルの一番最初に記述
’ ’内はファイル名

hoge.js
import HogeHoge form './hogehoge';

class Hoge{
.
.
.
}

②exportを使用

*呼び出したいファイルに記述
*ファイルの末尾に記述
*default後はクラス名

hogehoge.js
class HogeHoge{
.
.
.
}
export default HogeHoge;

【番外編】定数、変数の呼び出し

基本的には同じ

import

hoge.js
import content form './hogehoge';

class Hoge{
.
.
.
}

export

hogehoge.js
class HogeHoge{

const content = "HogeHoge";
.
.
}
export default content;

ちなみに
import: 輸入 持ち込む
export: 輸出、書き出す

という意味です。

以上

【合わせて読みたい】

■ 【javascript】 テンプレートリテラル とは 一言で。
https://qiita.com/tanaka-yu3/items/9b07bd9fc4126291be28

■ 【Javascript】 メソッド まとめ 基礎基本コード メモ
https://qiita.com/tanaka-yu3/items/2438798d159fa402b1d5

■ 【Javascript】JS 変数 定数 違い  一言でまとめました
https://qiita.com/tanaka-yu3/items/51b8b0630a1e4e2d52c8

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

【JavaScript】Base64変換ライブラリで速度対決

こんにちわ。
皆さんもBase64は使われているかと思います。
バイナリを文字列で表現できるのは何かと便利ですよね。

しかしJavaScriptには、こうサクっと1発でバイナリ配列とBase64文字列を変換できる実装が用意されていないので、何かしらの手を加える必要があります。

今回は、バイナリとBase64の相互変換を行える3つの方法について、どれが1番速いかを検証してみようと思います。

方法1, 標準実装

ライブラリを使わず、これらのブラウザ標準実装を組み合わせて変換します。

TextEncoder/TextDecoderは、文字列とUint8Arrayを相互変換してくれるAPIです。
そしてatob/btoaは、お馴染みBase64のブラウザ標準実装です。

この2つを組み合わせることにより、以下の流れでバイナリとBase64を変換できます。

Uint8Array <=> BinaryString <=> Base64

BinaryStringとは0x00~0xFFまでの文字(制御文字含)で構成される文字列です。

バイナリを型付き配列やBlobとして扱うのが難しい時によく用いられる表現です。

そしてatob/btoaは、ちょうど0x00~0xFFまでの文字を扱えます。
逆にこれしか扱えないので、Unicodeを扱う際によく問題となりますが...

TextEncoder/TextDecoder APIはクラスとして提供されるので、Base64向けの拡張クラスを作ってみました。

// Base64 => Uint8Array
class Base64Encoder extends TextEncoder{
    constructor(){
        super();
    }

    encode(text){
        return super.encode(atob(text));
    }
}

// Uint8Array => Base64
class Base64Decoder extends TextDecoder{
    constructor(){
        super();
    }

    decode(buffer){
        return btoa(super.decode(buffer));
    }
}

Node.jsはどうするんだって...?
最近話題 のDenoさんが何とかしてくれるでしょう...知らんけど。

方法2, base64-js

週間14,700,000ダウンロードの実績があるライブラリです。
恐らくこれが1番メジャーでしょうか。

メソッドは3つのみ、シンプルで使いやすいです。

Base64とUint8Arrayを相互変換できます。

方法3, js-base64

週間6,150,000ダウンロードの実績があるライブラリです。
2番手といったところでしょうか。

base64-jsと比べてライブラリ容量は大きめですが、メンテナンスは割と最近までされているようです。

こちらも、Base64とUint8Arrayを相互変換できます。

測定

以下のPCで測定を行いました。

  • CORE2Quad Q9550
  • DDR2-1066 4GB
  • Windows7 32bit
  • Google Chrome 83

部屋に転がってたPCなので、マシンスペックについては目を瞑ってもらいましょう...

元となるデータは、小さいと一瞬で終わってしまい比較にならないと予想したので、64MBのバイト配列でエンコード/デコードを行います。
これだけ大きいと、コールスタックやヒープなどのメモリ性能もついでに見れるかと思います。

// 64MB
const test = new Uint8Array(1024 * 1024 * 64);

// 1. 標準実装
new Base64Encoder().encode(new Base64Decoder().decode(test));

// 2. base64-js
base64js.toByteArray(base64js.fromByteArray(test));

// 3. js-base64
Base64.toUint8Array(Base64.fromUint8Array(test));

結果

  1. 標準実装 ... 1240 ms
  2. base64-js ... 2870 ms
  3. js-base64 ... ぴえん?(5秒程の後にスタックオーバーフロー)

という結果になりました。

まじかよ!
ライブラリ使うより普通に標準実装を組み合わせた方が速いじゃん!

js-base64に至っては完走すら出来ませんでした。

ブラウザでBase64を扱うなら、標準実装もしくはbase64-jsを使うのが良さそうです。

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

条件付きレンダリングって?

条件付きレンダリングてなだろう?

プログラミングにおいて複数の反応を用意して、一定の入力によって様々な出力へと変化するいわば制御構文というものがあります。これは一般的にはif文を用いることで可能になるんですが私が今勉強しているVue.jsで条件付きレンダリングにおいてはv-ifv-elseというものを使って表現するらしい!
まぁ簡潔に言えば条件付きレンダリングとは制御構文であると言えますね!

百聞は一見に如かず!

とりあえず何かしらこのv-ifとやらを使ってみるのが一番わかりやすいと思うのでサンプルプログラムを作ってみましょう。

sample.html
<div id="app">
    <p v-if="flag" class="ok">
      This is correct display!
    </p>
    <p v-else class="ng">
      uncorrect display!!!!!!!
    </p>
  </div>
  <button onClick="doAction();">
    Click
  </button>
  <script>
    var data = {
      flag: true
    }
    var app = new Vue({
      el: '#app',
      data: data
    });

    function doAction() {
      data.flag = !data.flag;
    }
  </script>

こんな感じのことを書いといて、webで開くとこんな感じです...
スクリーンショット 2020-07-03 6.23.19.png
サーセン!背景のこと全く考えてなくて画像の境界線が分からなくなってるけど気にしないでくれると助かります
スクリーンショット 2020-07-03 6.23.35.png
プログラム自体は至って簡単なものです!
flagに格納されている値がtrueであればv-ifの方の表示がされます。ボタンを押すとdoActionのメソッドが動き、!data.flagによりdataのなかのflagの値の否定、つまりfalseへ変化しdata.flagへ格納します。したがって今度は値がfalseのためv-elseの方の表示がされます。
書いて見ればなんてことないただのif文でしたな〜

言葉に騙されないで!

参考書とかで勉強してると時々「は⁉︎何これ!初めて聞いたはこの機能!」ということが多々あると思います。でも実際に書いて実行してみると意外と今回のようになんてことない初歩中の初歩だったりする。なんか難しい言葉が出てきたな〜とか思ったらとにかくプログラムを実際に書いてみる!これに限ることを学んだ今日この頃...
この記事ほとんど書く必要なかった〜〜〜

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