20190522のJavaScriptに関する記事は22件です。

[JavaScript]pushメソッドの返り値は新しい配列の要素数

とあるfor文を書いていて、配列の要素を返したい時に、arr.push(i)をreturnしたらうまく返せなかったのでめも。

arr = []

for(let i = 10; i >= 0; i--) {
  arr.push(i)
}

console.log(arr)
// => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

console.log(arr.push(11))
// => 12

上記のように、ES6でpushメソッドを使用した時の返り値は、新しい配列の要素ではなくて、新しい配列の要素数を返す。

参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/push

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

Nightwatch.js検証

Nightwatch.jsを試す機会があったため、ドキュメントではわからない、実際に使ってみて気づいた点をまとめている。

開発環境構築

  • Getting Started - Installationに従えば、インストールは問題なくできる。
  • Windows環境ではChrome Driverを使ってテスト実行すると以下のエラーが出力された。
  An error occurred while trying to start ChromeDriver: cannot resolve path: "node_modules/.bin/chromedriver".
  • これはChrome Driverのパス設定の問題なのでnightwatch.jsonを変更すればよい。(参考
  "server_path": "node_modules/chromedriver/lib/chromedriver/chromedriver.exe"

テスト実装

  • テストランナーにはMocha、アサーションにはchaiが使えるため、取っつきやすい。
  • テストコードの構造化には、Page Objectsパターンや、事前・事後処理を定義するGlobal Hooksが使える。また、テストファイルのグループ化(Test Groups)もできる。
  • TypeScriptを使うこともできるが、公式にサポートされてはいないので使いにくいところがある。
    • Nightwatch.jsはテストファイルの拡張子がjs前提になっているため、ts-nodeでは実行できず、tscでトランスパイルしたものを読み込む形にする必要がある。
    • 型定義(@types/nightwatch)の最新版は0.9.12と少し古い。(Nightwatch.jsの最新安定版は1.0.19)
    • Page Objectはオブジェクトとして定義するため、型(EnhancedPageObject)に合わせられない。
    • NightwatchBrowserオブジェクトから参照できるPage Objectの型もEnhancedPageObject型になるため自分で定義したメソッドを使うためには、キャストが必要になる。
  • async/awaitはv1.1(beta)以降で使えるとのことだが、v1.1.8時点ではPromiseを返すメソッドと返さないメソッドが混じっている。また、Page Objectはasync/await未対応。
  browser.url('http://www.google.com')) // Promise
  browser.click('input[name=btnK]') // 同期的
  browser.pause(500) // Promise
  browser.waitForElementVisible('input[name=btnK]') // 同期的
  • Visual Studio Codeでのデバッグ実行はnodeコマンドにinspectオプションを指定すれば簡単にできる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FlutterでWebアプリが書けるようになった

https://9to5google.com/2019/05/07/flutter-apps-web-desktop-more/
↑が原文です。変なところあればご指摘いただけるとありがたいです。

Flutter doubles down on ‘write once, run anywhere’ apps w/ web, desktop, more

おおよそ全ての開発者は、モバイルアプリとウェブアプリ・デスクトップアプリ間で統一をもたせる、あるいはそれを維持することの難しさはよく知るところだろう。FlutterはiOSアプリとAndroidアプリの間のギャップを埋めるフレームワークとして知られているが、この度その領域をWebまで広げ、「一度書けばどこでも動く(write once, run anyware)」の夢へと更に近づいた。

これまでFlutterは、iOS/Android関係なく同じ見た目・振る舞いをするモバイルアプリを作るためのGoogleのフレームワークとして知られていた。昨年行われたFlutterの最初のイベントで、GoogleはFlutterをモバイルアプリ領域を飛び越えてWebまで飛躍するためのHummingbirdプロジェクトを公開した。

Google I/O 2019では、Googleは「FLutterはモバイルアプリだけ」という概念を取り払おうとしている。GoogleはFlutterを”portable UI toolkit”として企業や開発者がプラットフォームごとにアプリを作るのではなく、アプリを1度作るだけで全てのプラットフォームで動作することを可能にしようとしている。そのために、Webアプリやデスクトップアプリ、そしてRaspberry Piのような組み込み系のプラットフォームでも動作することを目標にしている。

本日のイベント前に、私は小規模なFlutter製Webアプリを自分のマシンにインストールしてもらう機会を頂いた。そのアプリを触ってみて私が最初に気づいたことは、動作速度の速さである。これは、Dartで書かれたFlutterがJavascriptのようなWeb言語にコンパイルされているから実現できることである。下記画像のように、ブラウザ上でネイティブアプリのように読み込まれる。

image.png

Flutter製Webアプリの印象的なところはもう一つある。それは、Hummingbirdデモであったスライドパズルのごとくアニメーションがヌルヌル動くことである。4K Lenovo Yoga Chromebookやthe Pixel 3で動かしてみても、動作が遅くなることはなかった。

デスクトップアプリの観点から見ても、Flutterの成長速度は著しいものである。Flutter製アプリはChromeOSのサポートの甲斐あってChromebookでもすでによく動作するが、キーボードやマウスのサポートはここ数ヶ月で少し落ち込んでいる。Windows、Mac、Linuxなどのコンピュータで動作させることが優先されているため、これらのサポートは先に持ち越されているのだ。

ゆくゆくは、GoogleはFlutterがおよそ全てのデバイスで動作させることを目標にしている。一つのFlutter製アプリが複数のプラットフォームで動作するのだ。”scenarios including home, automotive and beyond.”

Googleはあらゆるアプリ開発技術の中でFlutterを一番にしようとしている。あなたの作りたいアプリがAndroid向けであれ、iOSであれ、ChromeOS、Windows、Web、IoTその他なにであろうと関係ないのである。これは驚異的なことだ。

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

光の速さでデプロイ ~ Vue + Firebase hosting ことはじめ ~

概要

こんにちは、都内でフロントエンドエンジニアをやっていますかめぽんです。最近は、Next.js + Firebaseで個人プロダクトを作っているんですが、Firebaseが便利すぎてお米が3杯食べられるくらいに感動しています。
何と言っても、スピード感が出せるのは非常に大きいなと感じています。特に、デプロイまでは本当にあっという間で、ものの数分でデプロイができます。
今回、Vue-cliとfirebase hostingを使ってプロジェクトの立ち上げからデプロイまでを、ササッと体験してもらえればと思います。

ターゲット

  • 光の速さでデプロイしたい人
  • Firebase実は使ってない人
  • Vue好きな人

メリット

  • SPAを光の速さでデプロイ出来る
  • firebase hostingが出来るようになる
  • Vue-cli触れる

本題

開発の流れ

今回の開発の流れは以下のように進めていきます。
事前に、firebase-toolsとVue-cliをグローバルインストールしておいてください。

  • vue-cliでプロジェクトの立ち上げ
  • firebaseプロジェクトの作成
  • プロジェクト内でfirebaseの設定
  • デプロイ

vue-cliでプロジェクトの立ち上げ

なにはともあれ、Vue-cliでプロジェクトを立ち上げましょう!以下コマンドにて、プロジェクトが一撃で作れます。

vue create [プロジェクト名]

すると、色々質問されると思うのですが任意で進めてみてください。
今回はManually select featuresを選択し、好きな設定でカスタマイズしていきます。
スペースキーで選択・非選択、エンターで決定できます。
スクリーンショット 2019-05-22 20.55.42.png

設定はまだまだ続きます、テストやLinterの設定などをお好みで選択していってください。
スクリーンショット 2019-05-22 20.57.10.png

最後までいき、以下のような画面が出たらプロジェクト作成完了です!

スクリーンショット 2019-05-22 21.00.45.png

firebaseプロジェクトの作成

次にFirebaseの設定です。https://console.firebase.google.com/u/0/ こちらのサイトにアクセスしてみてください。

すると以下のような画面が出るので、赤枠の部分を押してFirebaseプロジェクトを新規作成してみてください。

スクリーンショット 2019-05-22 22.19.34.png

プロジェクトの名前を打ち込み、、、
スクリーンショット 2019-05-22 22.19.53.png

プロジェクト作成を押すだけで、、、
スクリーンショット 2019-05-22 22.20.11.png

プロジェクトの作成が完了しました。
スクリーンショット 2019-05-22 21.25.00.png

プロジェクト内でfirebaseの設定

はじめに、プロジェクト内でfirebaseを使えるようにするために以下コマンドを売ってみてください。(事前にfirebase-toolsをグローバルインストールしておいてください)

firebase init

すると、こんな感じでターミナルが燃えていると思います。firebaseがもついろんな機能がありますが、今回はHostingを選んでください。
スクリーンショット 2019-05-22 21.20.13.png

そしたら、あらかじめ作っておいたプロジェクトが表示されるのでそれを選んでください。
スクリーンショット 2019-05-22 23.07.07.png

そして、firebaseの設定に関する質問を進めていくと以下のような画面が出るので、ここまでくればfirebaseプロジェクトとVue-cliで作ったプロジェクトをつなぎこむことが出来ました。

次に、Vue-cli側でアプリのデプロイをします。その前にfirebase.jsonの設定を少し変えます。Vue-cliのビルドしたファイルとfirebaseで読むときの設定を変えにいきます。npm run buildでVueの資材を吐き出せるのですが、その吐き出したファイルをホスティングするためにその対象ディレクトリを指定します。

{
  "hosting": {
    "public": "dist", //publicからdistへ
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }
}

設定は以上になります!

デプロイ

そして、ここからデプロイに移っていくわけなんですが、その前にVue-cliの資材をビルドします。以下コマンドを打てみてください。

npm run build

すると、dist配下にファイルが流れ込んできたのがわかると思います。これらが、ホスティングする対象ファイルたちです。

スクリーンショット 2019-05-22 23.25.49.png

次に、以下魔法のコマンドを打つとホスティングされます。

firebase deploy

この画面が出たらデプロイが完了です!なんて簡単なんでしょうか。
スクリーンショット 2019-05-22 21.30.16.png

そして、ホスティングのURLにアクセスしてみて、吐き出した画面が表示されれば成功です!

スクリーンショット 2019-05-22 23.33.55.png

まとめ

いかがだったでしょうか?非常に簡単でスピーディにデプロイが出来たと思います。実案件でも良いですが、爆速でプロトタイプを作りたいときなどにも非常におすすめなのでジャンジャン使っていきましょう!それでは!

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

【デモ環境あり】GASを使って掲示板を作ってみた話

はじめに

今回もGsuite(google)のサービスのみで掲示板アプリを作ってみました。
これを作ろうと思ったきっかけとしては、
Gsuiteのサービス一覧に組織全体に知らせるための掲示板のようなアプリケーションってないなー。
ないなら勉強がてら作ってみるかーと思ったのがきっかけです。

イメージフロー

スクリーンショット 2019-05-22 22.02.48.png

①:掲示板に新規に投稿したり、投稿している掲示板に対して返信を実行

②:返信の投稿の際に返信元の投稿者にメールを送信

③:掲示板の投稿を確認を行います。記事を閲覧したユーザは自動で既読情報を更新

デモ環境

今回、自分の個人googleアカウントでデモ環境を作って公開しようと思います。
デモなので本来のアプリケーションと違う部分や制限機能を設けています。

名前や、記事に内容は全てサンプル用に用意したものになります。
アプリケーションのアクセス方法は匿名アクセスにしています。

デモ環境はこちら

アプリケーション解説

これからはアプリケーションの解説を行なっていきます。

スプレッドシート

使用しているスプレッドシートは2つです。

  • 投稿内容保持するスプレッドシート(データ)
  • シートIDやファルダIDを保持するスプレッドシート(マスタ)

データプレッドシート

カラムは以下のようにしています。
スクリーンショット 2019-05-22 22.31.11.png

id 親id 削除フラグ タイムスタンプ ...

新規投稿内容も返信内容も一つのスプレッドシートで保持しています。
「親id」の部分で親子関係を管理しています。

マスタスプレッドシート

スクリーンショット 2019-05-22 22.24.35.png

UPDATA_FLG

データスプレッドシートのデータがスプレッドシートのセル制限を超えないかを時間のトリガー処理で行なっています。
もし、セル制限を超えそうな場合は、スプレッドシートのデータを直近投稿10件以外を別のシートに退避させます。

処理実行中時の画面
スクリーンショット 2019-05-22 22.27.06.png

そのトリガー処理時はアプリケーションにアクセスできないようにするためのフラグになります。

SNS_ACCESS_KEY

仮にトリガー処理でバックアップ処理を実行した場合、投稿記事の一意キーが変わってしまします。
そのバックアップ処理が変わったことを判断するための判断として使用するキーになります。

アプリケーションアクセス時にクライアント側で保持し、実際にデータシートを更新時にキーが一致するかを判断しています。

SNS_BORAD_DATA , SNS_FILE

データのスプレッドシートIDと添付ファイルを保存するフォルダIDを設定しています。

web画面

ライブラリはVue.js
フレームワークはvuetify.js

この二つを利用して作成しました。

投稿一覧画面

スクリーンショット 2019-05-22 22.39.18.png

アプリケーションアクセス時、初めに表示する画面です。
自動で、直近の掲示板数件の投稿を取得します。
「さらに読み込む」をクリックすることでさらに数件読み込む仕様になっています。

複数の検索ワードから検索が可能になっています。
表示するのを数件ずつ表示する関係上、リアルタイム検索は出来ないのが残念です。

「新規投稿」ボタンから新規投稿が可能で、「リロード」ボタンで記事の再読み込みを行います。

一覧をクリックすることで詳細内容が表示されます。

新規投稿

スクリーンショット 2019-05-22 22.43.46.png

特に説明することはないかと思います。
添付ファイルはGASのアップロード制限?で50Mbまでにしています。

※デモ環境では新規投稿できないです。

詳細画面

スクリーンショット 2019-05-22 22.45.55.png

枠内に表示されている、文章が掲示板の全本文になります。

投稿者本人の場合、左上に「ゴミ箱」ボタンが表示され、削除することが可能です。
※デモ環境では投稿者の名前は「デモ投稿太郎」になります。

下に表示しているメッセージは掲示板に対してコメントをした内容を表示しています。

「リプライ」ボタンで返信画面の表示
「目」ボタンで閲覧者の確認(デモ環境では仮の人しか入っていません)
「リンク」ボタン、「添付ファイル」ボタンは投稿者が添付した場合、クリックし表示されます。

返信画面

スクリーンショット 2019-05-22 22.50.24.png

こちらも特に説明することはないと思います。
メール送信のチェックボックスでメール送信の有無を選択できます。

※返信投稿は実際に、スプレッドシートに書き込まないので自由に試してください。
返信も返信者の投稿のみ削除が可能です。
スクリーンショット 2019-05-22 22.55.41.png

以上で説明を終わります。

最後に

Gsuiteのサービスで組織全体に周知する掲示板が出来てくれたらもっと便利になるきがするんですけどねー。
あんまり需要がないのでしょうか?

それと、一部スマホで操作すると思い通りの操作をしてくれないのでそこは改良する必要があると思っています。

なにかご質問や、指摘等がありましたらよろしくお願いします。

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

[Nuxt]firebaseの認証で無限にユーザー情報を取得できないとき by @penguin_fuyuno

結論

chromeの設定が悪い!!

嘘だろと思わるかもしれないが実際に起きたトラブルです

現象

ログインボタンを押す

googleLogin: function() {
        console.log("googleLogin");
        firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider());

で リダイレクト 認証して リダイレクトさせる

次にログイン状態の判定

firebase.auth().onAuthStateChanged(user => {
        console.log(user);
        if (user) {
          console.log("true");
          this.isLogin = true;
          this.userData = user;
        } else {
          console.log("false");
          this.isLogin = false;
          this.userData = null;
        }
      })

そして ログイン状態を判定させて ユーザー情報を入れる

が!!!!!

ユーザー情報が入らない!!!!

なんで?????

原因と因果

chrome://settings/content/cookies

スクリーンショット 2019-05-22 21.06.40.png

ここの上から三番目が原因

ここがオンになっていると 認証機能が正しく動作しない

This browser is not supported or 3rd party cookies and data may be disabled.

https://github.com/firebase/firebase-js-sdk/issues/865

https://support.cloudhq.net/how-to-enable-3rd-party-cookies-in-google-chrome-browser/

発見方法

すべては safariでlocalhost をつかったらうまく動作した!!
そこから物語ははじまった

chromeだとだめ

裏付けはこれでできます

googleLogin: function() {
        console.log("googleLogin");
        // firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider());
// ここを入れ替える
        firebase.auth().getRedirectResult().then(function(authData) {
          console.log(authData);
        }).catch(function(error) {
          console.log(error);
        });
      },

で コンソールで確認をする

すると

This browser is not supported or 3rd party cookies

があーだーこーだって言われる

これがさきほどの結論と解決方法につながるのである

感想

一日半かかりました
パンダになるくらい悩みました
コードが原因ではなかったです
今日もなんとか生きてます
これを読んでもらってあなたは10分で解決できることを願います
最後まで読んでくれてありがとうございます(。・ω・。)/

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

[Nuxt]firebaseのgoogle Auth無限にユーザー情報を取得できないとき by @penguin_fuyuno

結論

chromeの設定が悪い!!

嘘だろと思わるかもしれないが実際に起きたトラブルです

現象

ログインボタンを押す

googleLogin: function() {
        console.log("googleLogin");
        firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider());

で リダイレクト 認証して リダイレクトさせる

次にログイン状態の判定

firebase.auth().onAuthStateChanged(user => {
        console.log(user);
        if (user) {
          console.log("true");
          this.isLogin = true;
          this.userData = user;
        } else {
          console.log("false");
          this.isLogin = false;
          this.userData = null;
        }
      })

そして ログイン状態を判定させて ユーザー情報を入れる

が!!!!!

ユーザー情報が入らない!!!!

なんで?????

原因と因果

chrome://settings/content/cookies

スクリーンショット 2019-05-22 21.06.40.png

ここの上から三番目が原因

ここがオンになっていると 認証機能が正しく動作しない

This browser is not supported or 3rd party cookies and data may be disabled.

https://github.com/firebase/firebase-js-sdk/issues/865

https://support.cloudhq.net/how-to-enable-3rd-party-cookies-in-google-chrome-browser/

発見方法

すべては safariでlocalhost をつかったらうまく動作した!!
そこから物語ははじまった

chromeだとだめ

裏付けはこれでできます

googleLogin: function() {
        console.log("googleLogin");
        // firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider());
// ここを入れ替える
        firebase.auth().getRedirectResult().then(function(authData) {
          console.log(authData);
        }).catch(function(error) {
          console.log(error);
        });
      },

で コンソールで確認をする

すると

This browser is not supported or 3rd party cookies

があーだーこーだって言われる

これがさきほどの結論と解決方法につながるのである

感想

一日半かかりました
パンダになるくらい悩みました
コードが原因ではなかったです
今日もなんとか生きてます
これを読んでもらってあなたは10分で解決できることを願います
最後まで読んでくれてありがとうございます(。・ω・。)/

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

【Javascript】多段階アンドゥの実装覚書

きっかけ

お世話になっている施設でアンケート集計のお仕事がきた
Excelは良いソフトだがプルダウンメニュー選択がクソで大嫌いです
そこでJavascriptを使って集計用のアプリ(?)を作成してみました

アンドゥ?

個人的には一段階だけでも十分だと思ったのですが意見として多段階アンドゥの実装をと出たので頑張って作ってみました

コード

スパゲッティすぎて説明もできないので全文のっけます

html

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="workSheet.css">
  <title>アンドゥの実装</title>
</head>
<body>
  <div class="table">
    <!-- 区切り文字・開始番号 -->
    <form id="punctuationAndStartNum" class="tableRow">
      <span class="tableCell textAlignRight">セルの区切りを選択:</span>
      <span class="tableCell">
        <select id="punctuation">
          <option value="   ">タブ区切り</option>
          <option value=",">カンマ区切り</option>
          <option value=" ">半角空白区切り</option>
        </select>
        <span class="textAlignRight">開始番号:</span>
        <input id="startNum" type="text" size="3" value="1" onchange="startN();">
      </span>
    </form>
    <!-- 区切り文字・開始番号 end -->
    <!-- 性別 -->
    <form class="tableRow">
      <span class="tableCell textAlignRight">性別を選択:</span>
      <select id="sex" class="tableCell" autofocus>
        <option value="男"></option>
        <option value="女"></option>
        <option value="記載無し">記載無し</option>
        <option value="質問項目無し">質問項目無し</option>
      </select>
    </form>
    <!-- 性別 end -->
    <!-- 年代 -->
    <form class="tableRow">
      <span class="tableCell textAlignRight">年代を選択:</span>
      <select id="age" class="tableCell">
        <option value="20代">20代</option>
        <option value="30代">30代</option>
        <option value="40代">40代</option>
        <option value="50代">50代</option>
        <option value="60代">60代</option>
        <option value="70代〜">70代〜</option>
        <option value="記載無し">記載無し</option>
        <option value="質問項目無し">質問項目無し</option>
      </select>
    </form>
    <!-- 年代 end -->
    <!-- 知ったきっかけ -->
    <form class="tableRow">
      <span class="tableCell textAlignRight">知ったきっかけ:</span>
      <select id="trigger" class="tableCell">
        <option value="公式ウェブサイト">公式ウェブサイト</option>
        <option value="Facebook">Facebook</option>
        <option value="twitter">twitter</option>
        <option value="知人からの紹介">知人からの紹介</option>
        <option value="メール">メール</option>
        <option value="ブログ">ブログ</option>
        <option value="お知らせハガキ">お知らせハガキ</option>
        <option value="その他">その他</option>
        <option value="記載無し">記載無し</option>
        <option value="質問項目無し">質問項目無し</option>
      </select>
    </form>
    <!-- 知ったきっかけ end -->
    <!-- その他の理由 -->
    <form class="tableRow">
      <span class="tableCell textAlignRight">その他の理由:</span>
      <input id="other" class="tableCell" type="text" size="50" autocomplete="off" placeholder="その他の理由を記入してください。">
      <input type="text" size="50" autocomplete="off" placeholder="見えないハス" style="display: none;"><!-- 改行入力無効化の為 -->
    </form>
    <!-- その他の理由 end -->
    <!-- 問い合わせ内容 -->
    <form class="tableRow">
      <span class="tableCell textAlignRight vaTop">問い合わせ内容:</span>
      <textarea id="inquiry" class="tableCell" name="name" rows="8" cols="50" placeholder="問い合わせ内容を記入してください。"></textarea>
    </form>
    <!-- 問い合わせ内容 end -->
    <!-- 備考欄 -->
    <form class="tableRow">
      <span class="tableCell textAlignRight vaTop">備考欄:</span>
      <textarea id="nb" class="tableCell" name="name" rows="3" cols="50" placeholder="備考を記入してください。"></textarea>
    </form>
    <!-- 備考欄 end -->
    <!-- 入力・取消・コピー・削除ボタン -->
    <form class="tableRow">
      <span class="tableCell textAlignRight"></span>
      <div class="tableCell">
      <input class="outputButton" type="button" value="入力" onclick="main();">
      <input class="outputButton" type="button" value="取消" onclick="oneStepBack();">
      <span> </span>
      <input class="outputButton" type="button" value="Copy" onclick="copyTextToClipboard(fullText);">
      <input class="outputButton" type="button" value="全削除" onclick="removeText();">
    </div>
    </form>
    <!-- 入力・取消・コピー・削除ボタン end -->
    <!-- 出力 -->
    <div class="tableRow">
      <span class="tableCell textAlignRight vaTop">確認用テーブル:</span>
      <div id="makedTable" class="tableCell">
        <table>
          <tr>
            <th>番号</th>
            <th>性別</th>
            <th>年代</th>
            <th>きっかけ</th>
            <th>その他理由</th>
            <th>問い合わせ内容</th>
            <th>備考</th>
          </tr>
        </table>
      </div>
    </div>
    <form class="tableRow">
      <span class="tableCell textAlignRight vaTop">出力内容:<br>
      <input id="hide" class="outputButton" type="button" value="表示" onclick="expression();"></span>
      <textarea id="output" class="tableCell" style="display: none;" rows="3" cols="50" placeholder="フォーマットに合わせて出力されます。"></textarea>
    </form>
    <div class="tableRow">
      <span class="tableCell textAlignRight">xlsxファイル:</span>
      <input id="dl-xlsx" class="tableCell outputButton" type="button" value="Download XLSX">
    </div>
    <div class="tableRow" style="display: none;"> <!-- 目隠し -->
      <span class="tableCell textAlignRight vaTop">出力用テーブル:</span>
      <div id="excelTable" class="tableCell">
        <table>
          <tr>
            <th>番号</th>
            <th>性別</th>
            <th>年代</th>
            <th>きっかけ</th>
            <th>その他理由</th>
            <th>問い合わせ内容</th>
            <th>備考</th>
          </tr>
        </table>
      </div>
    </div>
    <!-- 出力 end -->
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.9.10/xlsx.full.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js"></script>
  <script src="export-xlsx.js"></script>
  <script src="workSheet.js"></script>
</body>
</html>

CSS

もったいぶってcss

workSheet.css
@charset "UTF-8";

* {
  margin: 5px;
  font-size: 15px;
}

span {
  white-space: nowrap;
}

input, textarea {
  border: solid 1px gray;
  border-radius: 2px;
  background: #eee;
}

table, tr {
  margin: 0;
  padding: 0;
}

th, td {
  min-width: 30px;
  max-width: 170px;
  margin: 0;
  padding: 5px;
  border: solid 1px gray;
  text-align: center;
}

#startNum {
  text-align: center;
}

.table {
  display: table;
}

.tableRow {
  display: table-row;
}

.tableCell {
  display: table-cell;
  box-sizing: border-box;
  /* border: solid 1px green; */
}

.textAlignRight {
  text-align: right;
  font-weight: bold;
}

.vaTop {
  padding-top: 7px;
  vertical-align: top;
}

.outputButton {
  height: 35px;
  padding: 5px;
  border: solid 1px black;
  border-radius: 5px;
  font-size: 15px;
}

js

やっとjsです

workSheet.js
const output = document.getElementById('output');
const punctuationAndStartNum = document.getElementById('punctuationAndStartNum');
const makedTable = document.getElementById('makedTable');
const makedExcelTable = document.getElementById('excelTable');
const startNum = document.getElementById('startNum');
var count = 0;
var excelNum = 0;
var initialValue = 0;
var oneStepBackNum = 0;
var fullText = '';
var oneStepBackText = []; // 取消用
var oneStepBackTable = []; // 取消用
var oneStepBackExcel = []; // 取消用
const tag = '<tr><th>番号</th><th>性別</th><th>年代</th><th>きっかけ</th><th>その他理由</th><th>問い合わせ内容</th><th>備考</th></tr>'
var tableText = ['<table>' + tag, '', '</table>'];
var excelTable = ['<table  class="table-to-export" data-sheet-name="testExcel">' + tag, '', '</table>']

function main() {
  const punctuationId = document.getElementById('punctuation');
  const punctuation = punctuationId.value;
  var joined = '';
  var joinedExcel = '';

  const sex = document.getElementById('sex');
  const sexValue = sex.value;

  const age = document.getElementById('age');
  const ageValue = age.value;

  const trigger = document.getElementById('trigger').value;

  const other = document.getElementById('other');
  var otherValue = other.value.replace(/ /g, ' '); // 半角スペースを全角にする。

  const inquiry = document.getElementById('inquiry');
  var inquiryValue = inquiry.value.replace(/ /g, ' '); // 半角スペースを全角にする。
  var inquiryValueDQ = ('"' + inquiryValue + '"');

  const nb = document.getElementById('nb');
  var nbValue = nb.value.replace(/ /g, ' '); // 半角スペースを全角にする。
  var nbValueDQ = ('"' + nbValue + '"');

  if(trigger === 'その他' && otherValue === '') {
    alert('その他の理由を入力してください');
    other.focus();
    return;
  } else if(trigger != 'その他' && otherValue != '') {
    alert('"知ったきっかけ"または\n"その他の理由"をみなおしてください');
    other.focus();
    return;
  }

  otherValue = tabCheck(otherValue);
  inquiryValueDQ = tabCheck(inquiryValueDQ);
  nbValueDQ = tabCheck(nbValueDQ);
  inquiryValue = tabCheck(inquiryValue);
  nbValue = tabCheck(nbValue);

  if(otherValue === false || inquiryValueDQ === false || nbValueDQ === false) {
    return;
  }

  joined = sexValue + punctuation + ageValue + punctuation + trigger + punctuation + otherValue + punctuation + inquiryValueDQ + punctuation + nbValueDQ;
  joinedExcel = sexValue + punctuation + ageValue + punctuation + trigger + punctuation + otherValue + punctuation + inquiryValue + punctuation + nbValue;

  count += 1;
  oneStepBackExcel.push(excelTable[1]); // 取消用
  oneStepBackTable.push(tableText[1]); // 取り消し用
  tableText[1] = tableMake(joinedExcel, punctuation) + tableText[1];
  excelTable[1] += tableMake(joinedExcel, punctuation);
  makedTable.innerHTML = tableText.join('');
  makedExcelTable.innerHTML = excelTable.join('');

  oneStepBackText.push(fullText); // 取り消し用
  console.log(oneStepBackText);
  fullText += (joined + '\n');

  output.value = fullText;
  const line = countLine(fullText);
  output.style.height = (line * 1.5) + 'em';

  sex.focus();
  other.value = '';
  inquiry.value = '';
  nb.value = '';

  punctuationAndStartNum.style.display = 'none';

  oneStepBackNum = 1;
} // main end

// 最初の番号を取得し-1する
function startN() {
  excelNum = startNum.value - 1;
  initialValue = startNum.value - 1;
}

// 取消!
function oneStepBack() {
  if(fullText != oneStepBackText) {
    count -= 1;
    tableText[1] = oneStepBackTable[count];
    excelTable[1] = oneStepBackExcel[count];
    fullText = oneStepBackText[count];
    oneStepBackTable.pop();
    oneStepBackExcel.pop();
    oneStepBackText.pop();
    makedTable.innerHTML = tableText.join('');
    makedExcelTable.innerHTML = excelTable.join('');
    output.value = fullText;
  } else if(count === initialValue && initialValue === oneStepBackNum) {
    alert('まだ何もしてません');
    sex.focus();
  } else if(fullText === '') {
    alert('取り消しはもうできません');
    sex.focus();
  }
}

// タブが入力されてたら
function tabCheck(str) {
  var tab = str.indexOf('   ');
  if(tab != -1){
    str = str.replace(/ /g, ' ');
    if(confirm('タブは全角空白に置き換えます')) {
      return str;
    } else {
      alert('入力は取り消されました');
      return false;
    }
  }
  return str;
}

// テーブル作ります
function tableMake(str, punctuation) {
  str = str.split(punctuation);
  for(var i = 0; i < str.length; i++) {
    if(i === 0) {
      str[i] = '<tr><td>' + (excelNum + count) + '</td><td>' + str[i] + '</td>';
    } else if(i === (str.length - 1)) {
      str[i] = '<td>' + str[i] + '</tr></td>';
    } else {
      str[i] = '<td>' + str[i] + '</td>';
    }
  }
  str = str.join('');
  return str;
}

// コピーボタン
function copyTextToClipboard(textVal){
  if(textVal != '') {
    // テキストエリアを用意する
    var copyFrom = document.createElement("textarea");
    // テキストエリアへ値をセット
    copyFrom.textContent = textVal;

    // bodyタグの要素を取得
    var bodyElm = document.getElementsByTagName("body")[0];
    // 子要素にテキストエリアを配置
    bodyElm.appendChild(copyFrom);

    // テキストエリアの値を選択
    copyFrom.select();
    // コピーコマンド発行
    var retVal = document.execCommand('copy');
    // 追加テキストエリアを削除
    bodyElm.removeChild(copyFrom);
    // 処理結果を返却
    return retVal;
  } else {
    alert('結果が出力されていません');
  }
}

// 全削除
function removeText() {
  if(window.confirm('リセットしますか?\n取り消しはできません。')) {
    location.reload();
  }
}

// 出力内容表示・非表示
function expression() {
  const hide = document.getElementById('hide');
  if(hide.value === '表示') {
    output.style.display = 'inline';
    hide.value = '隠す';
  } else {
    output.style.display = 'none';
    hide.value = '表示';
  }
}

// 出力の行数カウント
function countLine(str) {
  num = str.match(/\r\n|\n/g);
  if(num != null){
    line = num.length + 3;
  } else {
    line = 1;
  }
  return line;
}

※Excelファイルの出力には下記リンクからjsファイルを作ってください
以上

最後に

二次元配列でもっとスマートにできそうな気がしますが今回はここまでとしておきます

コピペ用のテキストもクリップボードに吐き出せますし
Excelファイルも作れます

コピーはここから拝借しました
JavaScript でテキストをクリップボードへコピーする方法

Excelファイルの出力はこちらから
HTMLのTableをExcelに出力するJavaScript - Qiita

レイアウト等のセンスのなさはご勘弁を…

追記

アップした後にいじってみたらundoよりも根本的なミスを…
訂正しました

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

バックエンドエンジニアがReact(JS)+Gin(Golang)でwebサービスを作ってみた

webサービスを作ってみたのでその紹介と、作ってみた感想です。特定の技術に関する説明をする記事ではありません。

どんなサービスか

色の名前とそれに紐付くRGB値等の定義はHTMLJIS慣用色名などがありますが、仮に「赤」という色が定義上#ff0000だとしても、多くの人は#ff0101を見たときも「赤」だと認識すると思います。本来の定義からどれくらい外れていたらその色と認識しなくなるかは、人によって違いそうです。

これについて自分以外の人がどのように認識しているか知りたいと思いました。その方法としては、「赤」という色の名前とともに#ff0000に近い色を複数表示して、「赤」だと思うものを選択する、という作業を複数人にしてもらうことで、表示されたそれぞれの色についてどの割合でそれを「赤」だと思う人がいるか知ることができそうです。

この情報を収集してうまく表現できれば「これは赤では?」「うーん、違う気がする」といったシチュエーションに対して「ではどの割合でそう思う人がいるか調べてみよう?」という解決策をもらたすことができますよね?

これを実現することを目標にしたのが今回紹介するcolor-consensusというサービスになります。

以下が使い方の説明です。

  • まずアクセスすると以下のような画面が出てきます。右側のナビゲーションでGreenを選んでますから、画面中央のマス目からGreenだと思う色を選んでほしいという画面です。クリックやドラッグで選択できます。

  • 入力した情報をSubmitするにはログインが必要です。非ログイン状態でSubmitを押したり右上のそれっぽいボタンを押すとサインアップかログインができるモーダルが開きます。ユーザがログインIDとパスワードを指定する方式ではないです。ユーザ情報を入力してSubmitするとサービスから一方的にログインIDを発行してSet-Cookieします。そのログインIDは今まで右上に表示されていたサインアップ/ログインボタンの部分に表示され、クリックでクリップボードにコピーできます。

  • 色を選んだりログインしたりしたくない場合は灰色のGo to statistics →ボタンから統計情報を表示する画面に遷移できます。

  • 色を選んでSubmit →を押しても同様に統計情報を表示する画面に遷移します。その画面では中央上のフィルターに沿って選択された色が白い線で囲われます。何人の人がSubmit →したかもVote Countとして表示されています。

  • フィルターをいじることで人の属性を限定して結果を表示できます。現状日本人の僕しかSubmitしていないのでJapanしかないですね。

作ってみたサービスはこちらです。 → https://color-consensus.herokuapp.com/

ソースコードも公開しています。 → https://github.com/konohiroaki/color-consensus

コードの規模はテストを含めて7000行くらい。フロントエンドとバックエンドの割合はバックエンドの方が半分より少し多いくらいです。

開発期間は3~4ヶ月ほどで、なるべく毎日触るようにしながら、焦らず少しずつ進めていました。

技術スタック

仕事では Java8, Spring Framework, Cloud Foundary あたりを使ったwebのバックエンド開発をメインでしています。今回の開発を通してjQueryあたりで止まっているフロントの知識をアップデートしたいと思いました。

フロントエンド

Webpack, Babel, Javascript, React, Redux, bootstrap, など
- Webpack以外の選択肢もあるようでしたが、Webpackという名前をよく聞くのでそれを選ぶことにしました。
- VueとReactは悩みましたがReactのドキュメントを読み始めたらするする読めたのでそのままそれを使うことにしました。

バックエンド

Golang, Gin framework, MongoDB, など
- 仕事で使っている技術そのままでもできましたが、バックエンド開発でも新しいことを学ぶために最近流行りのGolangを使うことにしてみました。
- webフレームワークはGolang用のものでGithub上で一番Starのあるものを選びました。

プラットフォーム

Heroku
- ここで時間を使いたくなかったというのがVPSを借りるなどしなかった理由です。コードを書くことに集中したかったです。あとHerokuの一番下のプランだと無料なのも嬉しいです。

開発の流れ

だいたい以下のような流れで開発をしました。メモしながら開発していたわけではなく記憶を頼りに書いているので結構雑ですが。

準備
開発
  • 最初はReactの公式ドキュメントを読みながらreactのアプリを動くようにしました。最初の時点ではReduxは使っていません。
  • 同じ頃にバックエンドにも簡単なHTTPのエンドポイントを作ってみました。
  • 画面主導で開発を行い、表示する色を選択するための画面右側の部分 → ユーザ入力部分 → 統計情報表示部分 の順に作っていき、必要に応じてバックエンドのAPIも実装していきました。
    • ざっくりこのような部品を作るというのは初めの頃に決めていましたが、細かい部分は開発しながら気付いたときにTODOとしてコード中にメモを残して、頃合いを見て実装していきました。
  • 統計情報表示部分の開発をする頃にstatspropsだけでデータを引きずり回すのが苦しくなってきたので、Redux in Motionで使い方を覚えて導入しました。データを一元管理できるようになって格段に楽になったので、もっと早くやればよかったです。
  • 統計情報表示部分をデモグラフィックでフィルターできるようにしてみました。この機能でサインアップ時に入力した情報が使われています。
  • gomocktestifyを使ってテスト皆無だったバックエンドにテストを書きまくりました。
  • ようやくtravis CIを入れました。Githubにpushしたら自動でテストが流れてデプロイするようになりました。ここまで待たずに最初にやっても良かったように思います。
  • ここまで色の名前を言語(Japanese, Englishなど)に紐づけていましたが、言語というより色カテゴリ(HTML Color, JIS慣用色名など)という上位互換みたいな概念を使ったほうがいいと思ったので、ごっそり書き換えました。

サービスの課題

とりあえず動くものの、課題はまだまだあります。

サービスの外から見えてる部分の課題

  • ユーザが選択できる色の候補が微妙です。特にグレーっぽい色だと使い物になりません。つらい。
  • UI上で色候補を選ぶ作業のUXがだいぶ低いように感じます。僕でもだるいのに、誰がやるのって思います。かなり目が疲れます。
    • 一色ずつ表示してyes/noで答えてもらうみたいな感じのUIがいいかもしれません。ユーザのyes/noに応じて表示する色を賢く変えていくみたいにできれば結構良さそうな気がします。もう少しアイディアが具体的にできたらごっそり書き換えてみたいですね。うまくできれば上下の課題も同時に解決できそうです。
  • PCのGoogle Chromeでしか動作確認をしていません。
    • スマホで色を選択する部分が動かないことは確認しました?
  • Herokuの無料プランなので30分アクセスがないと次のアクセスを処理するのに数秒かかります。

サービスの中身の課題

  • ユーザが選択した色を全てDBに持っていますが、おそらく選んだ色より近い色(どう判断できるかは要定義ですね)は選んだとみなしていい気がします。そういう意味では保存してるデータが冗長な感じです。
  • Reactが賢いのでXSSなどはなさそうですが、悪意のあるポストへの対策が基本的にありません。適当なデータをpostしまくればちゃんとした情報を埋もれさせてしまえます。
  • 統計情報表示画面ではその色に対してSubmitされている情報を全て取得してUIを描画していますが、データ量が増えたらこれではダメそうです。フィルターを変更するごとに通信してサマリー情報を取れるようにしても良さそうです。
  • Reactのコンポーネント設計ではロジックを記述するWrapperコンポーネントとHTMLを出力するコンポーネントを分けるのがお作法のようです。今はそれらを適用したときに比べてメンテナンス性が低い状態だと思います。
  • Reduxのモジュールにはある程度テストを書いてますが、Reactコンポーネントのテストは書けていません。つまり目でチェックしてる?
    • 見た目の部分は他と比べて変化の多い部分でもあり、全部きっちりテストを書いたら逆にメンテナンスが大変そうです。やり方要検討ですね。

というわけで改善の余地はまだまだありすぎますね。

全体を通しての感想

  • Golangが流行っている割にあまり書きやすい感じはしませんでした。機能が少なくて古い言語な印象です。今回必要と思う部分がなくてGoroutineを使わなかったことがGolangを使うメリットを大きく減らしてしまったのかなと思います。
    • Goroutineは動作原理も独特のようですが、並行処理のコード上の表現がJavaで使えるFuture objectを用いたアプローチとかなり異なるので、それがコードの設計にどのように影響するのか見てみたいと思っています。
  • UI上の典型的な表現がreact-toastifyなどのパッケージで大体解決できるという体験が新鮮でした。色んなパッケージを試すのが楽しいです。
  • フロントエンドはバックエンドに比べてデバッグのためのツールが充実してるように感じました。redux-devtools-extensionとかスゴイ。
  • フロントエンドはJavascriptで開発しましたが、Typescriptや全然別の言語からもトランスパイルできる事を途中で知りました。次は静的型付けできる言語を選択してみたいです。
  • 開発中コードを追加するたびにちょくちょくリファクタリングをしていました。おかげで忘れたコードもわりとすぐ理解を取り戻せるという状態で進められました。結構忘れるので読みやすさは生命線です。
  • 散々言われてますが、やはり公式ドキュメントとか解説本とかを読むのがツールの理解に対して近道だと感じました。ツール名でググったときはツールの公式サイトが一番上に出てきてほしいです。
  • 可能な限り毎日触るようにしていたおかげで、モチベーションが上がったときだけやるのではなく、毎日コンスタントに何かをやるということが自分にとって自然になりました。これが一番の収穫だったように思います。

以上、作ったものの紹介と、作ってみた感想でした。

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

関数の記述を簡略化できる、アロー関数(=>) by「JavaScript コードレシピ集」

0. 前提

「JavaScriptコードレシピ集(技術評論社)」の下記を参照しています。

Chap1 JavaScriptの基礎

  • 012 アロー関数(=>)で関数を定義したい

1. アロー関数とは?

// Syntax
(引数) => | 処理内容 |        // 関数を定義する

関数を定義する方法は、function宣言の他にアロー関数というものもあります。アロー関数のメリットはふたつあります。

  1. 関数を簡略化して記述可能
  2. thisを束縛できる (「JavaScript コードレシピ」P.44から引用)

2. アロー関数で関数を定義

JavaScript
// 関数の定義
const calcSum = (a, b, c) => {
  const result = a + b + c;
  return result;
};
JavaScript
// 関数の実行(functionと同じ)
calcSum(1, 2, 3);

3. アロー関数の省略記法

  1. 引数が1つのときは「カッコ()」を省略できる
    (引数が0個か、2個以上のときは省略不可)
JavaScript
// 関数の宣言
const myFunction = (a) => {
  return a + 2;
};

// 関数の宣言 | カッコを省略
const myFunction2 = a => {
  return a + 2;
};
  1. アロー関数内の処理が1行のとき、{}とreturnを省略できる
JavaScript
// 関数の宣言
const myFunction3 = (a) => {
  return a + 2;
}

// 関数の宣言 | {}とreturnを省略
const myFunction3 = (a) => a + 2;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「プリミティブ型/オブジェクト型」データ型の意味を知る (JavaScriptコードレシピ)

0. 前提

「JavaScriptコードレシピ集(技術評論社)」の下記を参照、データ型についてまとめました。

Chap4 データについて深く知る

  • 074 データの型について知りたい
  • 076 データの型を調べたい

1. データの型について知る

データ型 意味
プリミティブ型(基本型) データそのもの
オブジェクト型(複合型) データを参照するデータ

JavaScriptで取り扱う数値、文字列、真偽値、オブジェクトといった値はすべて「型」(データ型)という区切りで分けられます。その違いは、「データそのもの」か「データを参照するデータ」かです。プリミティブ型(基本型)は、数値、文字列などの「データそのもの」です。プリミティブ型はさらに6種類に分類されます。
(「JavaScriptコードレシピ集」P.174)

1.1 プリミティブ型

プリミティブ型 意味 データの例
Boolean 真偽値の型 true,false
String 文字列の型 '鈴木'、'田中'
Number 数値の型 1,30
Undefined 値が未定であることを示す型 undefined
Null 値が存在しないことを示す型 null
Symbal シンボルの型 Symbol()
// データ100を「参照」
const num = 100;
// データ'鈴木'を「参照」
const str = '鈴木';

1.2 オブジェクト型

オブジェクト型 意味 データの例
Object オブジェクトの型 プリミティブ型以外のすべて(Array、Object、Data等)
// [配列]
// データ1,2,3を参照するデータ
const arr = [1, 2, 3];
// [連想配列]
// キーageでデータ18を、キーnameでデータ'鈴木'を参照するデータ
const obj = {
  age: 18,
  name: '鈴木'
};
// [配列]
// 3つのオブジェクト(Object)を参照するデータ
// 各オブジェクトはさらに数値や文字列データを参照
const arr = [
  {id: 10, name:'鈴木'},
  {id: 20, name:'田中'},
  {id: 30, name:'田中'}
]

2. データの型を調べる

Syntax

typeof    //値のデータ型を調べる
データ型 typeofの結果 データの例
Undefined undefined undefined
Null object null
Boolean boolean true,false
String string '松本','浜田'
Symbol symbol Symbol()
Number number 1,10,20
Object(関数を除く) object [1,2,3],{id:10,name:’浜田’}
関数 function function(){},class MyClass{}

2.1 データ型の出力(console.log(typeof 値))

console.log(typeof true); // 'boolean'

console.log(typeof 10);   // 'number'

console.log(typeof '鈴木'); // 'string'

console.log(typeof null);  // 'object'

console.log(typeof undefined); // 'undefined'

console.log(typeof Symbol())   // 'Symbol'

console.log(typeof [1,2,3]);   // 'object'

console.log(typeof {id: 10, name:'田中'});  // 'object'

console.log(
 typeof function() {
     console.log('test');
  }
); // 'function'

console.log(typeof class MyClass {}); // 'function'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript基礎memo①

JavaScript基本構造

JavaScriptとは

JavaScriptとは
JavaScriptはWebに特化したプログラミング言語の1つで、動的なWebページの制作などに用いられます。
JavaScriptはクライアントサイドでもサーバーサイドでもどちらでも利用可能なプログラミング言語です。
JavaScriptはオブジェクト指向を使っている。
JavaScriptのコードはHTMLファイルに直接記述するか、別ファイルに記述してHTMLファイルから読み込んで実行します。

基本の書き方

  1. HTML内にScriptタグで埋め込み
<body>
 <script>
    'use strict';
    console.log('Hello world');
 </script>
</body>

このuse strictによって厳密なエラーチェックが行われる。
また、console log('')でコンソールに表示することができる。
今回は"Hello World"と表示される。

扱えるデータ型

基本データ型

1. 文字列(string):'hello' "hello"
2. 数値(number):5 5.5 -5 -5.5
3.undefined:undefined(定義されていない)
4. null:null(値自体がない)
5. 真偽値(Boolean):true false
    論理記号:①>と<
            ②<=と>=
            ③===と!===
※console.log(typeof 5)でnumberと出力される。
 このようにtypeofでデータ型を調べることができる。

オブジェクト型

6.object: {a:3,b:5}

注意点

let x = [1,2];
let y = x;
x[0] = 5;
console.log(x);
console.log(y);

上記の出力はxは[5,2]yも[5,2]となる。
これはobject型の変数のコピー(y)は元の変数を
参照しているだけであるからだ。

演算子

//コード1
console.log('5' * 3);
//出力15

//コード2
console.log('5' - '3');
//出力2

//コード3
console.log('5' + 3);
//出力53

//コード4
console.log(parseInt('5',10)+3);
//出力8

コード3のように+の時だけ文字列の結合として判断される。
文字列型をINT型に変換したいときは
コード4の通りparseInt('文字列',進数指定)を用いてできる。
このとき、文字列の中に数字になりえない文字列が来てしまうと
出力がNaNとなる(NaN→Not a Number)。

if

if(条件式){
    処理
}else if(条件B){
    処理
}else{
    処理
}

もしくは

条件式?trueの処理:falseの処理;

for

for(let i = 1;i<=10;i++){
    console.log(`${i}hello`);
}

以上のようにfor文を書くことができる。
console.log(${i}hello);の${i}は変数の埋め込み時に使う。

continue break

for(let i = 1; i<= 10;i++){
    if(i % 3 === 0){
        continue;//スキップ
    }
    console.log(i);
}
for(let i = 1; i<= 10;i++){
    if(i % 3 === 0){
        break;//プログラム終了
    }
    console.log(i);
}

関数

関数宣言

function show(name='taro') {
  console.log(`${name}`)
}

上記のように関数を書く。引数では上記のように初期値を決めることができる。

function sum(a,b,c) {
  return a+b+c;
}
const total =sum(1,2,3) + sum(2,3,4);
console.log(total);

関数式

const sum = function(a,b,c) {
  return a+b+c;
};

アロー関数

const sum = (a, b, c) => {
   return a + b + c;
};

配列の操作

const a = [1,5,10];

//add
a.unshift(100);//先頭に挿入
a.splice(2,0,6,7);//2番目の値を消さずに(0)6と7を挿入する。
a.push(200,300);//末尾に挿入
//remove
a.shift();//先頭から削除
a.splice(1,2);//1番目から2つ削除する。
a.pop();//末尾に削除

//抽出
const removed = a.splice(1,2);
console.log(removed);//削除した値を出力

forEach

const a = [1,5,10];

a.forEach((item,index)=>{
    console.log(`${index}:${item}`);
});

map filter

map

const a  = [1,2,3,4];

const b = a.map(item =>{
    return item * 2;
});
console.log(b);

配列のそれぞれに対して処理を行う。

filter

{
    const a =[1,4,3,5,8];

    const b = a.filter(item=> item % 2 === 0);//偶数だけ返す。
    console.log(b);
}

真偽値を返す関数を与えてtrueだった時の要素だけ返してくれるメソッド。

オブジェクト

const player ={
    name: 'tanaka',
    score: 32,
}
console.log(player.name);
console.log(player['name']);
player.email= 'tanaka.@example.com'
delete player.score;

このようにどのキーにどの値が入っているかを明示できる。
また、オブジェクト内のプロパティの追加や削除もできる。

クラス

クラスから作られたオブジェクトをインスタンスと呼ぶ。

{
class Player{//親クラス
    constructor(name,score){ //メソッド
        this.name = name;
        this.score = score;
    }

    showInfo(){
        console.log(`name:${this.name} score:${this.score}`);
    }

    static showVersion(){
        console.log('Player class ver 1.0');
    }

}

class SoccerPlayer extends Player {//子クラス
    constructor(name,score,number){
        super(name,score);
        this.number = number;
    }
    kick(){
        console.log('Gooooooaaal!!');
    }
}

//PlayerClass
const taguchi = new Player('taghci',32);

taguchi.showInfo();

Player.showVersion();//staticなメソッドはインスタンスを作らずにクラス名に直接メソッドをつけて呼び出せる。
//SoccerPlayerClass
const tsubasa = new SoccerPlayer('tsubasa',99,10);

tsubasa.kick();
console(tsubasa.number);
tsubasa.showInfo();
}

クラスにはオブジェクト生成に必要な初期化処理が
あり、constructorメソッドで初期化される。
上記のようにthis.nameは「このオブジェクトのname」と
いう意味で、右辺のnameは引数で与えられた値。

オブジェクトの操作

{
    const o = {
        a:1,
        b:2,
    };
    //オブジェクトの配列化
    console.log(Object.keys(o));//['a','b']
    console.log(Object.values(o));//[1,2]
    console.log(Object.entries(o));//[['a',1],['b',2]]

    //ここで配列に変換しているのでforEachが使える。
    Object.keys(o).forEach(key=>{
        console.log(`${key}:${o[key]}`);
    });

}

スプレッド演算子

配列でもオブジェクトでも使える。
スプレッド演算子(...)を使うと値の参照ではなく
コピーを作ることができる。

配列

{
    const a = [10,20];
    const b = [1,2, ...a];
    console.log(b);//出力[1,2,10,20]
}
const a =[10,20];
const sum =(a,b)=>a+b;
console.log(sum(...a));//出力30

オブジェクト

const o1 = {a:1};
const o2 = {...o1, b:2};
console.log(o2);//出力{a:1,b:2}

分割代入

配列

const numbers = [1,2,3,8];
const[a,b,...rest] = numbers;
console.log(a);//出力1
console.log(b);//出力2
console.log(rest);//出力[3,8]

オブジェクト

const player = {
    name:'taguchi',
    score: 55,
    hp:33,
    mp:22,
};
const {name,score,...points} = player;
console.log(name);//出力taguchi
console.log(score);//出力55
console.log(points);//出力[33,22]

文字列を操作する

{
    const str = 'hello';

    console.log(str.length);//5
    console.log(str.substring(2,4));//ll
    console.log(str[1]);//e
}

※詳しくはMDNで

Mathオブジェクト

{
    console.log(Math.PI);
    console.log(Math.random());//0 ... 0.99999

    //1~6を作る
    console.log(Math.floor(Math.random()*6 +1));

}

Math.floor()はカッコ内の数値の小数点以下の切り捨てを行う。

Dateオブジェクト

const d = new Date();//引数を書く場合は年と月が必須。

  console.log(d);

  console.log(d.getFullYear());
  console.log(d.getMonth()); // 0 - 11
  console.log(d.getDate());
  console.log(d.getDay()); // 0 - 6 曜日
  console.log(d.getHours());
  console.log(d.getMinutes());
  console.log(d.getSeconds());
  console.log(d.getMilliseconds());

  console.log(d.getTime()); // UTC 1970/01/01 00:00:00

操作

const d = new Date(2018,11);//2018/12/01 00:00:00
d.setHours(10,20,30);//2018/12/01/10:20:30
d.setDate(32);//32日などないが、2019/01/01 10:20:30
d.setDate(d.getDate() + 3);
console.log(d);

  const d1 = new Date(2018, 11, 1);
  const d2 = new Date(2018, 11, 10);
  console.log((d2 - d1) / (24 * 60 * 60 * 1000));

alert confirm

・windowにalertを表示。

alert('hello');

・確認を求める。「ok->true」「キャンセル->false」

confirm('Are you sure?');

setInterval繰り返し処理

Windowに備わったタイマー機能
setInterval(関数、ミリ秒);

{
    let i=0 ;
    const showTime = ()=>{
        console.log(new Date());
        i++;
        if(i>2){
            clearInterval(timerId);
        }
    };

    let timerId = setInterval(showTime,1000);
    //一秒に1回showTime関数を呼び出す。

}

setTimeout

指定した時間の後に1回だけ処理を実行するためのメソッド。
関数が長い処理になった場合でも処理が重なって負荷がかかることがない。

{

    const showTime = ()=>{
        console.log(new Date());

    };

   setTimeout(showTime,1000);

}

例外処理

エラーが起きても処理が止まらず、指定したメッセージを呼び出す。

const a = '5';

try{
    //toUpperCase()は文字列を大文字にする。ただし数値や真偽値は無理。エラーが起きる
    console.log(a.toUpperCase());
}catch (e) {
  console.log(e.message);
  //エラーオブジェクトeにはmessageプロパティが備わっており、エラーメッセージを出力してくれる。
}

console.log('finish');

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

Magic Leap MagicScript Landscape Application. UiProgressBar

Prepare
Magic Leap One
https://www.magicleap.com/magic-leap-one

mlsdk v.0.20.0
https://creator.magicleap.com/downloads/lumin-sdk/overview

magic-script-cli v2.0.1
https://www.npmjs.com/package/magic-script-cli

magic-script-polyfills v2.2.0
https://www.npmjs.com/package/magic-script-polyfills

Create Project

magic-script init my-prog org.magicscript.prog "Progress Bar"
cd my-prog

Code

Change app.js

import { LandscapeApp, ui } from 'lumin';
const { 
  UiLinearLayout, 
  UiProgressBar, 
  UiButton,
  Alignment} = ui;

export class App extends LandscapeApp {

  init() {
    this._prism = null;
    return 0;
  }

  onAppStart () {
    let value = 0;
    let isProgress = false;
    const prism = this.requestNewPrism([0.5, 0.5, 0.5]);
    const layout = UiLinearLayout.Create(prism);
    layout.setAlignment(Alignment.CENTER_CENTER);

    const button = UiButton.Create(prism, 'Progress', 0, 0.1);
    button.onActivateSub(function (uiEventData) {
        value = 0;
        isProgress = true;
        progress.setValue(0);
    });

    const progress = 
        UiProgressBar.Create(prism, 0.4, 0.01);
    progress.setMinMax(0, 100);
    progress.setProgressColor(
      [1.0, 1.0, 0.0, 0.5],
      [1.0, 0.0, 0.0, 1.0]
    );
    progress.setValue(0);

    prism.onUpdate = function(delta)
    {
        if (isProgress)
        {
            if (value < 100)
            {
                value++;
                progress.setValue(value);
            }
            else
            {
                isProgress = false;
            }
        }
        return true;
    };

    layout.addItem(progress
      , [0.01, 0.01, 0.01, 0.01]
      , Alignment.TOP_CENTER);
    layout.addItem(button
        , [0.01, 0.02, 0.01, 0.01]
        , Alignment.CENTER_CENTER);
    prism.getRootNode().addChild(layout);
    this._prism = prism;
  }

  updateLoop(delta) {
     if (this._prism.onUpdate) 
     {
        this._prism.onUpdate(delta);
     }
     return true;
  }
}

Build

magic-script build -i

Run

magic-script run --port=10000

Reference
UiProgressBar(Magic Script API Doc)
https://docs.magicscript.org/lumin.ui.UiProgressBar.html

Progress Bar (UiProgressBar)(Guide C++)
https://creator.magicleap.com/learn/guides/luminrt-uiprogressbar

magicscript
https://www.magicscript.org/

Thanks!

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

TypeScript 3.4のincrementalフラグを試す

はじめに

TypeScript 3.4のリリースノートの最初で説明されているのは「Faster subsequent builds」です。 --incremental というフラグが導入されました。

incrementalフラグとは

これを指定すると、tsconfig.tsbuildinfoというファイルが作成されます。(ファイル名の変更は可能)
このファイルにはコンパイルしたファイルのバージョン(SHA256ハッシュ?)が記録されていて、変更があった分だけをコンパイルし直すようです。結果的にコンパイル時間が短縮されることが期待されます。

noEmitでは効かない

タイプチェックだけをする場合は--noEmitフラグをつけることがありますが、この場合は出力ファイルが生成されないため、tsconfig.tsbuildinfoも生成されません。結果、高速化は効きませんでした。--tsBuildInfoFileを指定しても変わらないようです。

速度計測

簡単ですが、何もファイルに変更を加えず、とあるプロジェクトでコンパイル時間を測ってみました。

$ time npx tsc --project .     
npx tsc --project .  14.89s user 0.71s system 135% cpu 11.519 total
$ time npx tsc --project .
npx tsc --project .  3.11s user 0.22s system 157% cpu 2.123 total

1回目が15秒弱で、2回目が3秒ちょっとでした。

--noEmitしても13秒ほどかかるので、コンパイル結果が不要でも出力しちゃった方が早いというジレンマ。

おわりに

--incrementalをtscに書いたら、

error TS6064: Option 'incremental' can only be specified in 'tsconfig.json' file.

と怒られました。そういうものなのでしょうか。

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

Magic Leap MagicScript Landscape Application. VideoNode

Prepare
Magic Leap One
https://www.magicleap.com/magic-leap-one

mlsdk v.0.20.0
https://creator.magicleap.com/downloads/lumin-sdk/overview

magic-script-cli v2.0.1
https://www.npmjs.com/package/magic-script-cli

magic-script-polyfills v2.2.0
https://www.npmjs.com/package/magic-script-polyfills

Create Project

magic-script init my-video org.magicscript.video "Video"
cd my-video

Code

Change app.js

import { 
    LandscapeApp
  , ui
  , VideoEventType
  , VideoEventData} from 'lumin';
const {
    UiLinearLayout
  , UiButton
  , Alignment } = ui;

export class App extends LandscapeApp {

  init() {
    this._prism = null;
    return 0;
  }

  onAppStart () {
    const prism = this.requestNewPrism([1.0, 1.2, 1.0]);
    const layout = UiLinearLayout.Create(prism);
    layout.setAlignment(Alignment.CENTER_CENTER);

    const video = prism.createVideoNode();
    video.setLooping(1);
    const button = UiButton.Create(prism, 'PLAY!', 0, 0.1);
    button.setEnabled(false);
    button.onActivateSub(function (uiEventData) {
        if (!video.isPlaying())
        {
            video.start();
            button.setText("STOP");
        }
        else
        {
            video.pause();
            button.setText("PLAY");
        }
    });

    prism.onEvent = function(event)
    { 
        if (event instanceof VideoEventData)
        {
            if (event.getVideoEventType() === VideoEventType.kPrepared)
            {
                button.setEnabled(true);
            }
        }
        return true;
    }

    // https://mixkit.co/
    // https://mixkit.co/videos/city/56/
    video.setVideoUri("https://assets.mixkit.co/videos/56/56-720.mp4");

    layout.addItem(video
      , [0.01, 0.01, 0.01, 0.01]
      , Alignment.TOP_CENTER);
    layout.addItem(button
      , [0.01, 0.01, 0.01, 0.01]
      , Alignment.CENTER_CENTER);
    prism.getRootNode().addChild(layout);
    this._prism = prism;
  }

  eventListener(event) {
    if (this._prism.onEvent)
    {
      this._prism.onEvent(event);
    }
    return true;
  }
}

Change manifest.xml

<manifest xmlns:ml="magicleap"
          ml:package="org.magicscript.video"
          ml:version_name="0.0.1"
          ml:version_code="1">
  <application ml:visible_name="Video"
               ml:application_type="untrusted"
               ml:sdk_version="1.0">
    <component ml:name=".universe"
               ml:visible_name="Video"
               ml:binary_name="bin/index.js"
               ml:type="Universe">
      <mode ml:shareable="true"/>
      <icon ml:model_folder=""
            ml:portal_folder=""/>
    </component>
    <uses-privilege ml:name="MagicScript"/>
    <uses-privilege ml:name="Internet"/>
  </application>
</manifest>

Build

magic-script build -i

Run

magic-script run --port=10000

Reference
VideoNode(Magic Script API Doc)
https://docs.magicscript.org/lumin.VideoNode.html

magicscript
https://www.magicscript.org/

Thanks!

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

【Vue.js初心者】Todoリストはこう作る!~Todo削除~

概要

前回より引き続き、
以下のVue.js公式で紹介されているサンプルについてどのように動作しているのか見ていきます。
https://jp.vuejs.org/v2/examples/todomvc.html

jsfiddleでも見れるので実際に触りながら見てみましょう。
https://jsfiddle.net/yyx990803/4dr2fLb7/?utm_source=website&utm_medium=embed&utm_campaign=4dr2fLb7

こんな感じの画面構成です。

image.png

この記事の対象読者

・Vue.jsの公式ドキュメントに一通り目を通している
・でもまだよくわかっていない
・私もそうです

機能概要

以下の機能が存在します。
・TODO追加
・TODO削除
・TODO完了
・完了済みTODO件数表示
・TODOフィルター
・完了済みTODO全削除

前回はTODO追加について見ていきました。
今回はTODO削除について見ていきます。

TODO削除

・画面操作

TODOリストの削除は、リストにカーソルを合わせたときにリスト右側に出てくる
赤いバツボタンをクリックすると削除できます。

・removeTodo

ではバツボタンが押された際、何が起こるのでしょうか。

HTML
    <ul class="todo-list">
      <li v-for="todo in filteredTodos"
        class="todo"
        :key="todo.id"
        :class="{ completed: todo.completed, editing: todo == editedTodo }">
        <div class="view">
          <input class="toggle" type="checkbox" v-model="todo.completed">
          <label @dblclick="editTodo(todo)">{{ todo.title }}</label>
          <button class="destroy" @click="removeTodo(todo)"></button>
        </div>
        // 省略
      </li>
    </ul>

これはリスト部分のHTMLです。
filteredTodosをv-forで1要素づつ取得しています。
filteredTodosは、リストが格納されているtodosにフィルターを掛けたものです。
今のところはtodosと同義だと思っていてください。
v-for="X in Xs"は
Xsの要素を取り出しXに格納する、という意味です。1

この中の

HTML
   <button class="destroy" @click="removeTodo(todo)"></button>

この部分が削除ボタンですね。
クリックした際にremoveTodoが呼ばれているようです。
では次にJavaScriptを見てみます。

JavaScript
    removeTodo: function (todo) {
      this.todos.splice(this.todos.indexOf(todo), 1)
    },

引数で渡されたtodoをspliceでtodosから削除しています。2
これでtodosから削除されました。
さらに、todosが変更されたことによって、watch内の関数が呼ばれます。

・watchでのtodos監視

JavaScript
  watch: {
    todos: {
      handler: function (todos) {
        todoStorage.save(todos)
      },
      deep: true
    }
  },

これは前回も登場した関数ですね。
todoStorage.saveを呼んでいます。

JavaScript
var todoStorage = {
  // 省略
  save: function (todos) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
  }
}

そしてこれがtodoStorage.saveです。
localStorageに変更後のtodosを保持します。

これで削除処理は完了です。

まとめ

追加処理より削除処理のほうが簡単でしたね。
ご参考になれば、いいねしてくれるとうれしいです。

次回はレ点チェックをつけてTODOを完了状態する処理を見ていきましょう。

参考

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

React-Hooksはstateよりreducerの方がサンプルとしてわかりやすい気がした

よくカウンターの例として state と setState を見る気がするけど、実は useReducer の方がわかりやすいのでは仮説。

import React from "react";
import ReactDOM from "react-dom";

function useCounter() {
  return React.useReducer((state, i) => state + i, 10);
}

function App() {
  const [count, countUp] = useCounter();
  return (
    <div className="App">
      <h1>count: {count}</h1>
      <h2>
        <button onClick={() => countUp(1)}>count up</button>
        <button onClick={() => countUp(-1)}>count down</button>
      </h2>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

けど、どうなんだろう。

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

Magic Leap MagicScript Landscape Application. LineNode

Prepare
Magic Leap One
https://www.magicleap.com/magic-leap-one

mlsdk v.0.20.0
https://creator.magicleap.com/downloads/lumin-sdk/overview

magic-script-cli v2.0.1
https://www.npmjs.com/package/magic-script-cli

magic-script-polyfills v2.2.0
https://www.npmjs.com/package/magic-script-polyfills

Create Project

magic-script init my-line org.magicscript.line "Line"
cd my-line

Code

Change app.js

import { LandscapeApp } from 'lumin';

export class App extends LandscapeApp {
  onAppStart () {
    let prism = this.requestNewPrism([1.0, 1.0, 1.0]);

    const line = prism.createLineNode();

    line.setColor([1.0, 1.0, 0.0, 1.0]);
    line.addPoints([0.0, 0.0, 0.0]);
    line.addPoints([-1.0, 0.5, 0.2]);

    line.addLineBreak();
    line.addPoints([1.0, 1.0, 0.0]);
    line.addPoints([0.0, -1.0, -0.3]);

    prism.getRootNode().addChild(line);
  }
}

Build

magic-script build -i

Run

magic-script run --port=10000

Reference
LineNode(Magic Script API Doc)
https://docs.magicscript.org/lumin.LineNode.html

Line Nodes (LineNode)(Guide C++)
https://creator.magicleap.com/learn/guides/lrt-linenode

magicscript
https://www.magicscript.org/

Thanks!

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

PlayCanvasで作ったプロジェクトをWeb上で公開する方法

PlayCanvasのチュートリアルにプロジェクトをセルフホスティングする方法が書かれていなかったので書かせていただきました。

チュートリアル -2Dアクションゲーム- 8/8

プロジェクトをエクスポート

  1. 左側のタブからをPublish/Downloadの選択
    2.png

  2. DOWNLOAD.zipをクリック
    1.png

独自ドメインを振ったウェブページを公開する

PlayCanvasからDONWLOAD.zipを押して、出来上がったzipファイルについてはindex.htmlの存在するフォルダが作成されます。
今回はFirebase Hostingを使用してデプロイを使用と思います。Firebase Hostingの使い方はこちらを閲覧していただくのが確実です。

Firebase Hosting を使ってみる

in.PNG

Firebase へデプロイをする

1.Firebaseの公式サイトを参考にしてデプロイをする。

npm install -g firebase-tools

2.Firebaseへログインをする

firebase login

3.Firebaseの初期化をします

ここで公開するフォルダなどを指定します、デフォルトではカレントディレクトリにあるPublicというフォルダがデプロイ対象になります。今回はエクスポートをしたPlayCanvasのプロジェクト名にしておくと楽かもしれません。

firebase init

4.Firebaseのdeployをする.

firebase deploy

Firebase Hostingでドメインの設定をします。

hagaTest.png

上記の設定を行うと独自ドメインを振ることでアクセスできるようになります。

Sa.PNG

https://2d.spam.tokyo/

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

地獄からの使者、その名はTypeScript ~ 固かった型の形 ~ 2

地獄からの使者、その名はTypeScript ~ 固かった型の形 ~

 TypeScriptは便利である。変な書き方をしていれば教えてくれるし、開発環境が入力補完の支援もしてくれる。まさにヘブンである。しかし書き方を知らないと、その世界は地獄と化す。真っ赤なエラーに焼き尽くされた地獄に。

 地獄を回避するためには解決法を知ることである。TypeScriptは今も刻一刻と進化しており、昨日の知識は原始の人間が棒の握り方を覚えたに等しい。

 ということで地獄を渡り、天国へ到達するための知恵の一部を紹介していきたいと思う。たぶん他にもあるような気がするのだが、TypeScriptを使い始めてだいぶ慣れてしまったので、何に困ったのか全てを覚えていない。思い出したら、記事を追記して投稿していこうと思う。こういう場合にどう解決するべきなのかという疑問があれば、それを考えていきたい。

1. JavaScriptからTypeScript移行時に最初に訪れる試練、連想配列

 これを一度もやらずにTypeScriptを使っている者がいたとすれば驚愕である。JavaScriptからTypeScriptに移行する場合に、最初に放ってくる地獄からの息吹である。これに心を折られ、踵を返してしまうものもいることだろう。

  • 失敗例
    プロパティの存在しないオブジェクトが作られ、aが入らない
const value1 = {}
value1.a = 10 //ここでエラー
console.log(value1.a)
  • 修正例
    連想配列にする
const value1:{[key:string]:any} = {}
value1.a = 10
console.log(value1.a)

2. interfaceを使うほどでもないので、何度も同じ型を定義してしまう

 こうして型を付けるのがかったるいと思ってしまうのだ

const value1: { a?: number, b?: number } = { a: 100 }
const value2: { a?: number, b?: number } = { b: 200 }

 typeofで型が再利用できる

const value1: { a?: number, b?: number} = { a: 100 }
const value2: typeof value1 = { b: 200 }
console.log(value2.a) //コンパイルが通る

3. イキってstrictをtrueにしたときに訪れる試練、オブジェクトのキー

 valuesは a|b|c のキーしか持たないので、string型のkeyを突っ込むとエラーにされる。Object.keysから推論して欲しいところだが、現在はそうなっていない。

const values =  {'a':100,'b':200,'c':300}
for(const key of Object.keys(values)){
    console.log(`${key}:${values[key]}`) //values[key]でエラー
}

 よく見かけるのはアンチパターンの対処法だ。型を吹き飛ばして無理やり引っ張り出す手法だ。テロリストを倒すのに、住民もろとも爆撃してしまうような暴挙と呼べる。この場合はkeyの型を正しく認識させなければいけない。typeofで型を取り出し、さらにkeyofでキーの型を取り出すという二重構造だ。

const value2 = { 'a': 100, 'b': 200, 'c': 300 }
for (const key of Object.keys(value2)) {
    console.log(`${key}:${value2[key as keyof typeof value2]}`)
    //console.log(`${key}:${(value2 as any)[key]}`) //よくあるアンチパターン
}

4. intarface中のプロパティの型が欲しい場合

 interface自体を使えればよいのだが、中のプロパティの型が欲しいケース
 巨大な構造だったら再定義は本当の地獄だ

interface AnyData{
    values:{
        a:number
        b:number}[]
   }
}

const proc = (values)=>{ //←ここで型を付けないといけない
    console.log(`a:${values.a} b:${values.b}`)
}
const anyData: AnyData = { values: [{ a: 100, b: 200 }] }
proc(anyData.values) //インタフェイスの中の一部分を渡す

 ちなみにインタフェイス中のプロパティの型を取得する方法は、「もしかしてこれがやりたいんでしょ」とVSCodeが教えてくれた
 一所懸命調べたが、ネット上では探しきれなかった
 VSCode先生、一生はついていかないけど適当なところまではついていきます!

interface AnyData {
    value: {
        a: number
        b: number
    }
}
//const proc = (values: {a:number,b:number}) => { //再定義してしまう書き方
const proc = (values: AnyData["value"]) => { //元の定義を利用
    console.log(`a:${values.a} b:${values.b}`)
}
const anyData: AnyData = { value: { a: 100, b: 200 } }
proc(anyData.value)

5. プロパティがあるか確認しようとするとエラーになるケース

 これは解説記事が多いので、いまさらかもしれない

//プロパティがあるか確認しようとするとエラー
interface A{
    a:number
}
interface B{
    b:number
}
const proc2 = (value: A | B)=>{
    if(typeof value.a !== 'undefined') //この時点でaが使えない
        console.log(value.a)
}
proc2({a:100})
interface A {
    a: number
}
interface B {
    b: number
}
const proc2 = (value: A | B) => {
    if ("a" in value) //TypeScriptではこう書かないといけない
        console.log(value.a)
}
proc2({ a: 100 })

6. HTMLElementに追加プロパティを設定したい場合

 素のJavaScriptで気軽にやっていたこれは、エラーとなる

const divElement = document.createElement('div')
divElement.numValue = 100 //HTMLDIVElementにnumValueが存在しないのでエラー

 よくあるアンチパターンがこれだ

 なんでもanyさえあれば解決という使い方

 anyは免罪符か何かだろうか?

const divElement = document.createElement('div');
(divElement as any).numValue = 100 //アンチパターン

わざわざinterfaceを作るまでも無く、その場でプロパティを追加したければ以下のように設定する

const divElement: HTMLDivElement & { numValue?: number } = document.createElement('div')
divElement.numValue = 100 

7. childNodesでclassNameなどが使えない場合

 childNodesの中に入っているのがChildNodeになっている

 素のJavaScriptからプログラムを持ってくると、かなりの確率で引っかかるポイントだ

const divElement = document.querySelector('div')
if (divElement) {
    const childNodes = divElement.childNodes //これが NodeListOf<ChildNode> になっている
    for (let i = 0, length = childNodes.length; i < length; i++) {
        const node = childNodes[i]
        node.className = 'CustomClass' //これが使えない
    }
}

 HTMLElementにキャストしてやれば、関連プロパティにアクセスできる

const divElement = document.querySelector('div')
if (divElement) {
    const childNodes = divElement.childNodes as NodeListOf<HTMLElement>
    for (let i = 0, length = childNodes.length; i < length; i++) {
        const node = childNodes[i]
        node.className = 'CustomClass'
    }
}

地獄から天国への道

 TypeScriptの地獄から、天国へいたる道は長く険しい。そして何人もの人間がエンマ大王に舌を抜かれ沈黙する。しかしそこを抜けた先には、必ずやヘブンがあるはずだ。釜茹でにされようが、針で刺されようが、地道に賽の河原で石を積み上げていくしか方法はないのである。

 地獄を知らぬ者に、天国を感じることはできないのだから。

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

gatsby.jsのbuild 時の'TypeError: Cannot read property 'split' of null'エラーの対処

はじめに

gatsby.jsfirebaseでアプリを作成しているのですが、上記のエラーが生じ、「gatsby developではうまくいっているのにgatsby buildになるとうまくいかない、、、なぜだ、、、」となったので今後の為にメモ。

こちらのgithub issueがドンピシャで解決してくれたのでこちらを見ればOKだと思います。

対処方法

ざっくりbefore/after

before

const db = firebase.firestore();
const storage = firebase.storage();
const auth = firebase.auth();

after

let db, storage, auth;

if (typeof window !== 'undefined') {
    db = firebase.firestore();
    storage = firebase.storage();
    auth = firebase.auth();
}

何をしてるのか

読んでコードのごとく、windowオブジェクトの存在をチェックしてfirebaseの各種moduleを呼び出しています。なんでこんなことをするのか。

エラーの原因

gatsby.jsはbuildの際にNode.js環境でhtmlをレンダリングするようです。そのため、window objectが存在しません。なのでwindow objectを明示したコードを記述したりするとbuildがうまくいかなかったりします。

今回の場合はwindow objectを使用しているわけではないのですが、Node.js環境でfirebase.auth()を呼び出していることが原因でした。firebaseのclient side sdkはブラウザー環境で使用されることを前提としているため、Node.js環境はダメでしょ!ってなことみたいです。

gatsby build時のlogでwarning出してくれてました。

Warning: This is a browser-targeted Firebase bundle but it appears it is being 
run in a Node environment.

ちなみに、どこでsplit propertyが呼び出されていてエラーが生じているのかまでは深掘りしていません、、、、

覚えた方が良さげなこと

gatsby.jsのbuildはNode.js環境 !!!!

参考

[v2] TypeError: Cannot read property 'split' of null when building #6668

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

React mapをつかってリストを作る

配列データの要素分のリストを作りたい場合は、JSX内でmapを用いて展開するパターンとrenderメソッド内でmapを用いて展開したリストを変数に格納し、JSX内で展開するパターンがある。

JSX内パターン

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state={
      colors: ["red","orange","black","green","blue","white"]
    }
  }
  render(){
    return(
      <div>
        <ul>
          {this.state.colors.map( c =>
            <li>{c}</li>
          )}
        </ul>
      </div>
    )
  }
}

renderメソッド内パターン

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state={
      colors: ["red","orange","black","green","blue","white"]
    }
  }
  render(){
    const colorsDOM = this.state.colors.map(c => <li>{c}</li>)
    return(
      <div>
        <ul>
          {colorsDOM}
        </ul>
      </div>
    )
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む