20201118のJavaScriptに関する記事は23件です。

JavaScriptを学びなおしたくてポートフォリオサイトを作成した話 [Vue.js]

私の中で JavaScript の知識はかなり前から時間が止まったままでした。
最近、JavaScript の歴史を見ていく中で、JavaScript という言語をまた勉強し直したいという気持ちが強くなりました。そこで今回は、モダンなフロントエンド開発を学ぶという意味で Vue.js を使ってポートフォリオサイトを作成しました。
この記事ではサイトを制作する中で感じたこと・意識したことをまとめていきます。

ポートフォリオサイトを作成した経緯

私は、今まで約 3 年ほどプログラミングを勉強してきましたが、身につけたスキルを使って成果物を作るアウトプット型の学習より、ドットインストールや Progate を使った受動的なインプット学習が中心でした。そこでアウトプット型の学習を始めたいと思い、Vue.js を学習してポートフォリオサイトを作成しました。フレームワークとして Vue.js を選定した理由は様々あるのですが、大きく SPA 形式のサイトを構築してみたい以前に Vue.js を少し触ったことがあったためというのがあります。最近、Web 系の情報や、記事のトレンドを見ていると、React.jsNext.jsNuxt.js などの JavaScript フレームワーク名を目にすることが多くなりました。私が JavaScript を学習したのはかなり前になるので、この機会にモダンな JavaScript を学習したいという気持ちが強くなり、この機会にポートフォリオサイトを作ってみることにしました。

作成したポートフォリオサイト

学習期間が約 2 週間、制作期間として約 2 週間、全体として約 1 ヶ月で開発しました。 Toggle という時間管理ツールを使って開発している時間の記録をしたところ、完成まで 40 時間ほどかかっていました。
以前 Web 技術の概要を勉強したときに HTML / CSSJavaScript を触ったことあり知識はあったのですが、かなり学習期間が空いてしまっている状態からのスタートでした。そのため学習期間を含めてかなり遅いペースでの開発にはなりましたが、Vue.js の教材や情報が充実しており、理解しながら進められたので結果的にモダンな環境での開発ができたことの達成感がすごくありました

主な使用技術と概要

技術 使用バージョン 説明
Vue.js 2.6.12 JavaScript フレームワーク
VueCLI 4.5.7 Vue 用コマンドラインインターフェース
Vuetify 2.3.13 マテリアルデザインフレームワーク
Vue Router 3.4.6 Vue 用のルーティングプラグイン
Netlify 静的サイトホスティングサービス

ポートフォリオサイトに含めたもの

ポートフォリオサイトを作るに当たって様々なエンジニアの方のポートフォリオサイトを拝見しました。作成しているページや記載している内容はそれぞれ個性があって面白いと感じました。かなり悩んだのですが、その中で以下の4つのページを作成することにしました。

  • 簡単なプロフィール
  • 扱える技術
  • 作成したもの
  • 連絡先・お問い合わせ

今までほとんど成果物を作って来なかったため、載せられるものが少なく寂しい感じになってしまいました。今後、個人制作で作ったものを載せてにぎやかにしていきたいです。扱える技術ページには今まで勉強したことのある技術をアイコンとレーティング(★)で載せました。今まで幅広くいろんな技術を勉強してきましたが、それぞれの知識が浅く、アウトプットを通して技術力を底上げしていかなければと危機感も覚えました。

開発で意識したこと

モダンな JavaScript の理解と開発環境構築

ポートフォリオサイトを作る際に、WSL2 が気になっていたこともあり、ローカルの開発環境として WSL2 の環境に Ubuntu 20.04.1 LTS をインストールし Vue.js の開発環境を構築しました。 最近のフロントエンド界隈について調べる中で Node.jsWebpack などの用語は聞いたことあったのですが、使えると何が便利なのかわからず、恥ずかしながら今まで食わず嫌いしていました。今思えばもっと早くに触れておくべきでした。そんなときに Qiita で見かけた@yukiji さんのJavaScript 学習ロードマップという記事に影響を受け、記事内で紹介されていたエンジニア Youtuber さんの動画を見漁りました。ここで Web 技術の流行り廃りが激しいこと、自分が学んできた JQuery等の技術はすでに廃れつつあるということを目の前にしモダンな技術、流行りの技術を取り入れる姿勢を大事にしていかなければならないという思いがより強くなりました。プロジェクトは Node.js をインストールして使うことのできるパッケージマネージャーnpmを使いインターネット上の記事を参考に作成しました。この時点で WebPacknode_module などを目にする機会が多くなり少しパニックになったのを覚えてます。それと同時に VueCLI が簡単な対話形式で簡単に動作するプロジェクトを生成してくれること、開発用のサーバーでのホットリロードがすでに用意されていることなど驚くことが多く、これから開発していくことに対してとてもワクワクしてたのを覚えてます。TypeScript での開発も視野に入れていたのですが、学習リソースに合わせるため今回は JavaScript での開発環境を構築しました。

Vue.js の学習

実は 1 年ほど前に Vue.js を勉強していた時期があります。そのときに学習していた書籍がこちら。

基礎から学ぶ Vue.js
mio さん(@mio3io)が執筆した 猫本 の愛称親しまれている有名な 1 冊です。

この記事を執筆した 2020 年 11 月現在、改定 2 版が出版されています。画像は改定前の表紙になります。

書籍サポートページが充実しているので、動くものを見ながら学習できたのが良かったです。

昨年、書店で購入して読み進めていたのですが、忙しかったこともあり、積読になってしまっていました。触りの部分しか読めていなかったため、中途半端な理解のまま Vue で成果物を作ることなく、令和を迎えてしまいました。
今回、勉強する際に、改めて最初から読み直し、重要なところはメモを取るなどして少しづつ理解を進めていきました。それと同時に Udemy で人気が高かった超 Vue.js 2 完全パック - もう他の教材は買わなくて OK! (Vue Router, Vuex 含む)を購入し、猫本と並行して学習していきました。かなりボリュームがありますが、1 回購入すれば何度も見れるということもあり、再生速度を変えながら通勤時間などで繰り返し見るなどして学習を進めました。どちらも半分くらいまで見終えたぐらいから構築した環境で開発をはじめました。なお Vue.js は公式ドキュメントも充実しているのでとても学習しやすいと感じました。

ポートフォリオサイトを作成し始めるタイミングでちょうどよく Vue3 がリリースされたのですが、書籍と Udemy の講座動画の内容に合わせるため、今回は Vue2.x 系を利用して制作しています。

レスポンシブデザインと UI

今はモバイルファーストと言われるくらいにモバイル環境(タブレット・スマートフォン)への対応が必要不可欠となっています。今回の制作でもレスポンシブデザインに対応しながらおしゃれなデザインにしたいという思いがありました。レスポンシブデザイン対応はかなり難しい印象が強くあり、今まで意識してできていなかったのですが、Vuetify というマテリアルデザインフレームワークを導入することで柔軟に対応できました。もともと Booststrap をよく使っており、BootstrapVue というライブラリを使おうとしていたのですが、公式サイトのデザインに惹かれて今回は Vuetify を採用しました。Vuetify はコンポーネント単位でデザインしていくため、Vue.js の知識が必要ですが、同時にコンポーネントの概念について勉強して対応しました。レスポンシブ対応するときにブレークポイントを使ってレイアウトするのですが、以前 Bootstrap を使った経験があったので、直感的に理解しやすくそこまで難しいと感じることはありませんでした。簡単な記述でマテリアルデザインのコンポーネントを使うことができるので、使っていて楽しかったです。レスポンシブデザインとして、公式ドキュメントとサンプルではドロワーメニューとして以下のデザインが採用されています。

ハンバーガーメニューライク
このデザインがマテリアルデザインの方針としては正しいみたいです。しかしハンバーガーメニューは今まで使ってきて、個人的にあまり好きではありませんでした。UX の観点で否定的な意見を目にすることがあり、モバイル向けメニュー実装をする上どのように実装するかで悩みました。

そこで見つけたのが v-bottom-navigation コンポーネントです。

components/Navigation.vue
<template>
  <div>
    <v-bottom-navigation color="white" dark grow shift app>
      <v-btn v-for="item in items" :key="item.link" :to="item.link">
        <span class="white--text">{{ item.name }}</span>
        <v-icon>{{ item.icon }}</v-icon>
      </v-btn>
    </v-bottom-navigation>
  </div>
</template>

これだけでナビゲーションバーを作ることができます。v-forディレクティブで中身を表示しています。
今回は思い切ってこのナビゲーションを採用してみました。モバイル向け Twitter や Youtube の公式アプリで使われているようなコンポーネントですね。しかし、このままだと PC 用のナビゲーションバーとモバイル向けのナビゲーションバーが混在してしまうため、App.vue ファイルの中でブレークポイントによってコンポーネントの出し分けをしています。ナビゲーションには公式プラグインの Vue Router を使用しています。ページのパスと呼び出したいコンポーネントを指定するだけでルーティングの制御を自動で行ってくれるのが便利でした。

src/App.vue
<MyNavigation class="hidden-md-and-up ma-0"></MyNavigation>

上の例だとコンポーネントのクラスにブレークポイント md 以上(タブレットやラップトップパソコン以上の画面サイズ)では bottom-navigationコンポーネントを表示するように指定しています。これだけの記述で出し分けができるのはすごいですね。

アイコンでページイメージがわかりやすくて好きです。アイコンは Fontawesome 使用

Vuetify はまだ日本語のドキュメントなどが少なく、コンポーネントの使い方について調べるのが少し大変な部分もありましたが、公式のドキュメントを読み込むことで一通り使うことができました。今の段階でも使えるコンポーネントが使い切れないくらいあるのですが、今も開発が進んでおりバージョンが上がり続けているため、今後のコンポーネント追加や機能追加にも期待しています。Bootstrap などの他のフレームワークにも言えますが、デザインフレームワークの導入によって簡単な記述でデザインに一体感が出せる、モダンなデザインにできるのはとても便利です。しかし、デザインがどうしても似通ってしまうためデザイン部分では個性が出しにくいと感じました
その部分で Web デザインと UX について考えることも多く、難しさや奥深さを痛感しました。いろんなサイトを見ていく中で、今後、見直していきたい部分でもあります。

サイトホスティング

ページを公開するために必要となる Web ページのホスティングですが、ホスティングサービスとして上記の書籍と Udemy の動画の中で紹介されていたNetlifyを選びました。GitHub のリポジトリと連携してデプロイできるとのことで、半信半疑ではあったのですが、やってみるとたった数分でデプロイできたのがとても感動的でした。無料枠が設けられており、制限はあるのですが、個人的に利用する文については十分すぎるほどなので SPA を手軽に公開したいときの手段としておすすめできます。コードを変更をした際もコミットして連携したリポジトリに対してプッシュするだけでビルドして反映してくれるので便利です

工夫したところ

ページ遷移時のアニメーション

vue の transitionを使って実装しています。watch プロパティで\$route の中身が変化したときに CSS ライブラリの Animate.css を適用させてアニメーションさせています。Animate.css は使ったことがなかったのですが、クラスを適用するだけでアニメーションさせることができて便利だと感じました。アニメーションが入ることでサイトに動きが生まれて華やかになるので、カスタマイズしていて楽しかったです。
アニメーション実装については以下の記事を参考にさせてもらいました。

全体のデザイン統一とアイコン

全体的に黒と白を貴重としたモノトーンな印象のサイトにしています。Vuetify のコンポーネントのデザインと合わせやすく、サイト全体的にまとまった印象になりました。アイコンは主に FontAwesomeDEVICON を使用しました。保有スキルのページでは技術アイコンを表示するために v-iconコンポーネントを使い DEVICON で配布されているアイコンを埋め込んでいます。アイコンには単色とカラーのものがあったのですが、単色のアイコンを使うことでサイトの雰囲気に合わせやすいのが良かったです。

作り終えてみて

ポートフォリオサイトを構築した結果として動くものができたことは大きな成功体験になりました。作成したものが Web 上で公開されていることが不思議な感覚です。ポートフォリオサイトを構築するにあたって JavaScript でモダンなフロントエンド開発をできたことが楽しく、アウトプットして技術を伸ばしていくことの大切さを実感しました。それと同時に、ドットインストールや Progate を何周も学習して満足していた過去の自分が恥ずかしいです。もっとはやく成果物作成を通して新しい技術に触れておきたかったという後悔もあります。今回の開発を通して新しい体験がたくさんありました。初めてのサイト制作であり、まだまだサイトデザインやサイトのパフォーマンスは改善できる余地があると考えています。細かいところまで見ると至らない点は多くあります。その部分をこれから個人開発をしていくなかで、少しづつ改善していきたいです。次は、今回身につけた Vue.js の知識を踏まえながらサーバーサイドレンダリングが特徴的な Nuxt.jsNext.js などのフレームワークや、Firebase を使ったアプリにも挑戦していきたいです。ここまで読んでいただきありがとうございました。

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

Javascriptの分割代入について

Reactを触っていて下のように突然{}の引数が出てきて「なんだこれ??」となりました。

onSortEnd = ({oldIndex, newIndex}) => {
    this.setState(({items}) => ({
      items: arrayMove(items, oldIndex, newIndex),
    }));
  };
  render() {
    return <SortableList items={this.state.items} onSortEnd={this.onSortEnd} />;
  }

結論

これは分割代入と呼ばれるものらしくて、以下と同じみたいです。要は、オブジェクトが渡されていて{}とすることで勝手に代入してくれているみたいです。

function onSortEnd(param){
  let oldIndex = param.oldIndex
  let newIndex = param.newIndex
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] 等価性比較について

等価性とは?

値を比較して、同じと見なすことができるか?ということ。

等価性を比較するために、比較演算子を使用します。

等価性には 厳格な等価性抽象的な等価性 の2種類あります。
違いは以下の通りです。

等価性 比較演算子で記述した場合  型の比較 
厳格な等価性 a === b あり 
抽象的な等価性 a == b なし 

厳格な等価性と抽象的な等価性の違いについては、データ型の比較を行うか否かというところです。

例1

let a = '1';
let b = 1;

console.log(a === b)

上記の場合、let a = '1'として、文字列型の「1」で変数aを定義しています。一方、let b = 1として、数値型の「1」で変数bを定義してます。

そして最後にconsole.log(a === b)で厳格な等価性で2つの値を比較しております。

この場合、aとbの型が文字列と数値で異なるため、

false

が真偽値として出力されます。

一方、次の場合を見てみましょう。

let a = '1';
let b = 1;

console.log(a == b)

変数の定義については先ほどと同じですが、今回はconsole.log(a == b)で、抽象的な等価性で比較しております。

この場合は

true

が出力されます。
これは、aとbの「1」という値のみ比較しており、文字型と数値型まで比較をしていないためです。

例2

let a = 1;
let b = true;

console.log(b == a)
console.log(b === a)

今回は 変数aを数値型の「1」で定義。変数bを真偽値型の「true」で定義しています。

なお、真偽値型の場合、falsyな値truthyな値 という概念があります。

falseになる値とtrueになる値については以下の通りになります。

falsyな値(falseになる値) truthyな値(trueになる値)
false, null, 0, undefinded, 0n, NaN, "" falsyな値以外のもの

これを踏まえて出力結果を見ると、

console.log(b == a)については、trueを返します。
console.log(b === a)については、falseを返します。

console.log(b === a)の方は、aが数値型で、bが真偽値型ですのでfalseが返ります。
注目するのは抽象的な等価性比較console.log(b == a)の部分です・

抽象的な等価性を比較する時の処理の流れ

抽象的な等価性を比較する際次のプロセスを辿って比較を行います。

①両辺の型を合わせる作業を行う
②厳格な等価性比較を行う

例2のconsole.log(b == a)をもう少し深く突っ込んで記述して表してみると次のようになります。

let a = 1;
let b = true;

console.log(b === Boolean(a));

まず処理の流れとして(b === Boolean(a))として両辺の型を合わせる作業を行っています。
この場合、最初にbが参照され、bの型が真偽値型だと認識します。その後aを参照する際、aをBoolean(真偽値型)に変換しています。
その後、厳格な等価性比較(===)で比較を行い、出力する流れです。

変数aには「1」の値が入っており、1は先ほども紹介したようにtruthyな値に該当するため、aはtrueということになります。

よって、console.log(b == a)については、trueを返すわけです。

仮にlet b = 0;だった場合は、先ほどと同じように両辺の型を合わせて、厳格な等価性比較を行うわけですが、変数bに代入された「0」はfalsyな値となるのでfalseが出力されます。

例3

空文字が含まれているケースを考えてみましょう。

let a = "";
let b = 0;

console.log(a === b);
console.log(a == b);

厳格な等価性console.log(a === b);はこの場合、falseを返します。
抽象的な等価性console.log(a == b);は、trueを返します。

抽象的な等価性についてもう少し深く突っ込んで記述すると

let a = "";
let b = 0;

console.log(a === b);
console.log(Boolean(a) === Boolean(b));

という形になります。
空文字の場合は数値に変換することができないので、この場合は真偽値型として両辺を統一して厳格な等価性比較をしています。
空文字「""」も「0」もfalsyな値に分類されるため、最終的な出力結果はtrueとなります。

例4

値が設定されていない場合を考えます。

let g = null;
let b;

console.log(a === b);
console.log(a == b);

厳格な等価性console.log(a === b);はこの場合、falseを返します。
抽象的な等価性console.log(a == b);は、trueを返します。

これも抽象的な等価性を細かく見てみると、

let a = null;
let b;

console.log(a === b);
console.log(Boolean(a) === Boolean(b));

となります。
let b;のように、変数が宣言された際に値が設定されていない場合は、undefindedが設定されることになっています。

よってこの場合、 a = null, b = undefinded つまり どちらもfalthyな値となりますので、
最終的な出力結果がtrueになります。

比較演算子を使う場合は厳格な等価性比較(===)を使おう!!

抽象的な等価性を使った場合は、例で上げた通り、データ型が異なる場合でもtrueになるケースが沢山あります。

これによって、思わぬバグを引き起こす可能性が高くなりますので、基本的には厳格な等価性比較を使うようにしましょう。

最後に

ご覧いただきありがとうございました。

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

【ReactNative・TypeScript】import に絶対パスを使用する設定

はじめに

ReactNative(0.63~) + TypeScript(4.0~)を使用するプロジェクトで
importに絶対パスを使用する方法をまとめます。

絶対パスによるインポートを許可することで、ネストの深いモジュールをインポートする場合も簡潔にパスを記述できます。

相対パスの例
import * from '../../../component/foo';
絶対パスの例
import * from 'src/component/foo';

方法

以下2つのファイルに設定を加えます。

  • tsconfig.json
  • metro.config.js

ReactNativeだとtsconfigだけではうまく動かない。

tsconfig.json
"baseUrl": "./"
metro.config.js
const path = require("path")

module.exports = {
  resolver: {
    extraNodeModules: {
      "src": path.resolve(__dirname, 'src'),
    }
  },
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    }),
  },
}

まとめ

以下2つのファイルに設定を加えることで実現できる。

  • tsconfig.json
  • metro.config.js

もちろん、通常どおり相対パスによる指定も可能です。

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

ハノイの塔つくってみた

いつものノリで、思い立ったらとりあえず作ってみていた。

再帰を組むのはめんどくさかったので移動させるステップはべた書きでございます。

See the Pen Hanoi by kob58im (@kob58im) on CodePen.

ハノイの塔についての雑な説明

一番ちっさい輪っかから$N$番目の大きさの輪っかまでの$N$個を1塊で移動させるには、
一番ちっさい輪っかから$N-1$番目の大きさの輪っかまでの$N-1$個を、残りの棒のとこに一旦退避して、$N$番目の大きさの輪っかを目的の棒のとこに移動して、再度、
一番ちっさい輪っかから$N-1$番目の大きさの輪っかまでの$N-1$個を、その上に乗っければよい。(語彙力・・)

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

cypressでCSVダウンロードをテストする

まとめ

cypressの公式リポジトリーに例があるからそちらを参照するとできる。
日本語の記事が検索に引っかからなかったから、記事を書いてみました。
エイリアス的な感じですね。

要点

  • cypressにはブラウザのダウンロード機能をハックする仕組みがあり、ダウンロードディレクトリをDownloadsから変えることができる。つまりローカルに持ってこれる。
  • ローカルに持ってきたCSVファイルをneatCSVでパースしてオブジェクト化する。
  • テストする。

※cypressはブラウザのダウンロードAPIをハックする以外にもいろんな事ができる

実際に書いてみてあると便利なコマンド

以下の3つのコマンドを用意しているとテストしやすいかなって考えます。

コマンド 説明
allowDownloadPopup ダウンロードポップアップを自動でYESにするcypressコマンド
readCSV csvファイルを読み込んでパースしてオブジェクト化するcypressコマンド
detectEncoding csvファイルを読み込んでエンコーディングを調べるcypressコマンド
const path = require('path');
const neatCSV = require('neat-csv');
const encoding = require('encoding-japanese');

const defaultDownloadFolder = 'cypress/downloads';

export function allowDownloadPopup(downloadPath, options) {
  // The next command allow downloads in Electron, Chrome, and Edge
  // without any users popups or file save dialogs.
  if (Cypress.browser.name !== 'firefox') {
    // since this call returns a promise, must tell Cypress to wait
    // for it to be resolved
    cy.wrap(
      Cypress.automation('remote:debugger:protocol', {
        command: 'Page.setDownloadBehavior',
        params: { behavior: 'allow', downloadPath: downloadPath || defaultDownloadFolder },
      }),
      { log: false, ...options },
    );
  }
}

export function readCSV(filename, downloadFolder = defaultDownloadFolder, options = {}) {
  const filepath = path.join(downloadFolder, filename);
  return cy.readFile(filepath, { timeout: 15000, ...options }).then(neatCSV);
}

export function detectEncoding(filename, downloadFolder = defaultDownloadFolder, options = {}) {
  const filepath = path.join(downloadFolder, filename);
  return cy
    .readFile(filepath, { timeout: 1500, ...options })
    .then((data, options) => encoding.detect(data));
}

使い方

allowDownloadPopup

beforeEach(function(){
 cy.allowDownloadPopup()
})

readCSV

test.csv を読み込んで、レコード数と最初のレコードの内容をテストする。

test.csv
a,b,c
1,2,3
cy.get('button').contains('CSV(UTF-8))').click()
cy.readCSV('test.csv')
  .then(list => {
     expect(list, 'number of records').to.have.length(1)
     expect(list[0], 'first record').to.deep.equal({
    a: 1,
    b: 2,
    c: 3
     })
  })

detectEncoding

test.csv を読み込んで、エンコーディングが UTF-8 であることをテストする。

cy.detectEncoding('test.csv').then(encoding => {
   expect(encoding, 'encoding').to.equal('UTF-8')
})

以上です。

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

Reduxの基本的な使い方 例付き

Reduxの基本的な書き方

1 actionの作成

actionは、取り扱いたいデータの概要を書いているイメージ。

 
//reducks/dots/action.jsx
export const ADD_DOT = "ADD_DOT";
export const add_dot = (dot) => {
return {
        type: ADD_DOT,
        payload: dot,
    };
};

※export const ADD_DOT の意味… type名である"ADD_DOTS"を変数化することで、type名をtypoしたときに、その旨がエラー文にでるようになる。

2 reducerの作成

actionで作ったデータをどう変更したいかを定義しているイメージ

//reducks/dots/reducer.jsx

import * as Actions from "./action";
import initialState from "../store/initialState"; //次に作る

export const DotReducer = (state = initialState, action) => {
    switch (action.type) {
        case Actions.ADD_DOT:
            return [...state, action.payload];
        default:
            return state;
    }
};

3 initialStateの定義

const initialState = []
export default initialState

このinitialStateが、reducerのstateの初期状態になる。

4 Storeの作成

import { createStore as reduxCreateStore, combineReducers } from "redux";
import { DotReducer } from "../dots/reducers";

export default function createStore() {
    return reduxCreateStore(
        combineReducers({
            dots: DotReducer,
        })
);
}

5 Storeの中身を全てで使えるように設定

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";
import App from "./App";
import createStore from "./redux/store/store";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; //Redux DevToolsを使うために定義
export const store = createStore(composeEnhancers(applyMiddleware(reduxThunk)));

ReactDOM.render(
    < Provider store={store}>
        < App />
    < /Provider>,
    document.getElementById("root")

これでAppコンポーネントにネストされてるものすべてを含めて、 storeで管理している内容が使えるようになった!!いえい!?

6 Actionを使っていく

・actionを使いたいコンポーネントに移動
・useDispatchと、actionをimport
・useDispatchを定義
・actionを使いたいところで「dispatch(アクション名(propsとして渡したいデータ));」


import { useDispatch } from "react-redux";
import { add_dot } from "../../redux/dots/action";

//....

const MiniForm = () => {
    const dispatch = useDispatch();
    const onSubmit = (data) => {
                //...
        });
        dispatch(add_dot(data));
    };
    return (
        < form onSubmit={handleSubmit(onSubmit)}>
        //...
        < /form>
    );
};
export default MiniForm;

これでactionの中に引数に指定したデータ(今回ならdata)が渡る→そのactionに該当するreducerが走る→storeに保管される 、まで行われる

7 Storeに保管されたstateを使う

・useSelectorのimport
・ useSelector((state) => state.dots)を定義。dotsはreducerのexport名(Store参照)

dotというreducerに入っているstateが使えるようになった。
あとは使っていくのみ。(今回はmapで広げる)

import React from "react";
import { useSelector } from "react-redux";

export default function Base() {
    const dots = useSelector((state) => state.dots);
    console.log(dots); //これでstoreからやってきたdotsが見れる!
    return (
        < React.Fragment>
            < Header />
            {dots.map((dot) => {     //storeからもらってきたdotsを使ってます
                return < Dots dot={dot} />; 
            })}
            //...
            < Footer />
        < /React.Fragment>
    );
}

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

社内業務のシステムをWebで作ろう!

投稿の動機

@NP_Systemsさんの 製造業において、Pythonに加えてWebも活用して飛躍しよう の記事を見て正にキャッチ‐な話題でしたので、社内での活動状況を投稿しようと思いました。現在私は定年後の再就職先として広島市内の名盤製造業に嘱託として勤めています。仕事内容は社内業務システム「業務支援・改」の構築・保守です。

何故Webなのか

今のシステムは経理・給与・販売管理(移行開発中)を除く、製造業務のほぼ全般をWebシステムで運用しています。巷では業務システムはクライアント・サーバータイプ(VB, VC, Java 等)で作られているみたいですが、かなり早い段階からWebで開発している身としては、楽なのに何故Webで開発しないのかが不思議でした。実際WebでVBよりも高速な伝票入力画面を作ることができます。現役時代に納品したシステムはOSのバージョンアップを行っても問題無く使えていますし、カーソル移動も[Enter]キーで[tabIndex]順に移動できます。端末へのインストールも不要で遠地の端末が壊れても簡単に機器の入れ替えができます。ハードを更改してもシステムを更改する必要が無いのです。環境に依存しないので製造現場ではiPadで運用している部署も有ります。一度作ったシステムを要件が変わらないのに再度開発するのは、資産が残らないという意味で馬鹿らしいと感じています。

実運用画面のサンプル

実際に現場でどんな画面を使っているのかを見てみましょう。
■ 出荷計上一覧(出荷予定・実績の一覧)
出荷計上一覧画面.jpg
■ 出荷計上保守(俗に言う売上伝票、在庫出納と連動)
出荷計上保守画面.jpg
■ 納品書 先頭ページ (PDF出力)
納品書01.jpg
■ 納品書 最終ページ (PDF出力)
納品書02.jpg
■ 物品受領書 (PDF出力)
納品書03.jpg
■ 品目保守(製品の材料・工程と工数、他各種情報)
品目保守.jpg
■ 生産予定作成(生産オーダーより生産予定を作成)
生産予定作成画面.jpg
■ 生産状況一覧(生産ロットの進捗状況を把握)
生産状況一覧.jpg
■ 生産日程(カレンダー上で予定と進捗を確認)
生産日程画面.jpg
挙げれば切が無いのでこれぐらいにしておきますが、これらはたった一人で設計・施工・導入したものです。意外とWebシステムって簡単に作れるので皆さんもWebで開発する事を強くお勧めします。

開発環境

全てオープンソースです。Java8 (pdfBox), JavaScript, Tomcat9, mariaDB10, JQuery (ほんのチョットだけ), それと検索時に照合が出来ない事が有るので、入力時にANKは半角、カタカナは全角、濁音・半濁音は1文字に合成しています。殆どが手作りなので、皆さんも安価にリーズナブルなシステムを構築できる事請け合いです。生産計画作成画面はJavaScroptでコメントを入れて1,200行強位です。今日嵌って対応していた見積積算画面は少し凝っていて2,200行強でした (共通ライブラリ除く)。意外と手軽だと思いませんか。

まとめ

実は自分の周りに同じ様な事をしている人がいないので、最近疎外感を感じていたところでした。この投稿を切っ掛けにして多くの人にWebで業務システムを開発して貰いたいと思っています。本も買ったけどネットに転がっている情報でプログラムを作れているので、Webによる業務アプリケーション開発は決して敷居の高いものでは無いのです。みんなでWeb開発をやりましよう!

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

【JavaScript】Proxyオブジェクトについてまとめ

Proxyオブジェクトとは

指定したオブジェクトのプロパティを操作する際に独自の処理を追加する為のオブジェクト。

main.js
const target = {
  hoge: 'hoge',
  fuga: 'fuga'
}

const handler = {
  メソッド
}

new Proxy(target, handler);

// target  = 指定するオブジェクトが入る
// handler = 指定したオブジェクト(target)を操作する際に
//           実行されるメソッドが格納されたオブジェクト

使用例1

プロパティの値に変更があった際に「変更がありました。」と出力する。

main.js
const target = {
  hoge: 'hoge',
  fuga: 'fuga'
}
const handler = {
  set(target, prop, value) {
    target[prop] = value;
    console.log('変更がありました。') ;
  }
}

const pxy = new Proxy(target, handler);
pxy.hoge = 'ほげ';

console.log(pxy);

スクリーンショット 2020-11-18 9.12.41.png

setメソッド
値の変更を検知する。

第一引数:targetオブジェクト
第二引数:プロパティの名前
第三引数:変更された値

main.js
const target = {
  hoge: 'hoge',
  fuga: 'fuga'
}
const handler = {
  set(target, prop, value) {
    console.log(target);
    console.log(prop);
    console.log(value);
  }
}

const pxy = new Proxy(target, handler);
pxy.hoge = 'ほげ';

スクリーンショット 2020-11-18 7.52.26.png

使用例2

プロパティの値を取得した際に「取得しました。」と出力する。

main.js
const target = {
  hoge: 'hoge',
  fuga: 'fuga'
}
const handler = {
  get(target, prop) {
    console.log('取得しました。');
    return target[prop];
  }
}

const pxy = new Proxy(target, handler);
pxy.hoge;

console.log(pxy);

スクリーンショット 2020-11-18 9.29.46.png

getメソッド 
値の取得を検知する。

使用例3

プロパティの値を削除した際に「削除しました。」と出力する。

main.js
const target = {
  hoge: 'hoge',
  fuga: 'fuga'
}
const handler = {
  deleteProperty(target, prop) {
    console.log('削除しました。') 
    delete target[prop];
  }
}

const pxy = new Proxy(target, handler);
delete pxy.hoge;

console.log(pxy);

deletePropertyメソッド
値の削除を検知する。

スクリーンショット 2020-11-18 9.42.27.png

使用例4

存在しないプロパティにアクセスしたときエラーを表示させる。

main.js
const target = {
  hoge: 'hoge',
  fuga: 'fuga'
}
const handler = {
  get(target, prop) {
    if (!target[prop]) {
      throw new Error('プロパティが存在しません。');
    }
    return target[prop];
  }
}

const pxy = new Proxy(target, handler);
console.log(pxy.hoge);
console.log(pxy.fuga);
console.log(pxy.piyo);

スクリーンショット 2020-11-18 9.49.53.png


以上、Proxyについてまとめでした。
ここまで見て頂きありがとうございました!

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

ビンゴゲームの制作

ビンゴゲームの制作

STARTボタンをクリックするとビンゴ用のカードを作り、その後に抽選ボタンをクリックするごとに抽選して、該当番号を埋めてくれるプログラムを制作します。

<body>
  <table id="table"></table><br>
  <button onclick="start()">START</button>
  <button onclick="lot()">抽選</button>
  <span id="result"></span><br>
  <span id="result2"></span>
</body>

まず、body要素内にtable要素とbutton要素を2つ、span要素を2つ作ります。
table要素にはビンゴ用のカードの表示、button要素はクリックするとカードを作成してくれるボタンと、クリックすると抽選してくれるボタンです。span要素は抽選の結果の表示と、ビンゴしたときの表示をします。

    let numbers = [];
    let first;

    function start() {
      numbers = numSet();
      first = true;
      let result = document.getElementById("result");
      result.textContent = "";

      let table = document.getElementById("table");
      table.innerHTML = "";
      for(let r = 0; r < 5; r++) {
        let tr = document.createElement("tr");
        for(let c = 0; c < 5; c++) {
          let td = document.createElement("td");
          let s = Math.floor(Math.random()*numbers.length);
          td.value = numbers[s];
          td.textContent = td.value;
          td.id = "num" + td.value;
          td.className = "judge";
          td.index = r * 5 + c;
          td.j1 = false;
          td.j2 = false;
          td.j3 = false;
          numbers.splice(s, 1);
          tr.appendChild(td);
        }
        table.appendChild(tr);
      }
    }

script要素内は、まず変数numbersとfirstを定義します。
numbersは1〜99までの数字を代入する配列です。
firstは関数startの処理の際に配列numbersの1〜99の数字の内ビンゴ用のカードで使用した数字が25個分無くなっているので、関数lot内で再度1〜99までの数字を代入するために使用します。

関数startはビンゴ用のカードを作成する関数です。
最初に変数numbersに関数numSetを代入します。numSetは1〜99までの数字を返してくれる関数です。
変数firstにはtrueを代入します。後で関数lotを発火させたときに値をfalseにすることで、2回目以降に配列numbersに関数numSetが代入されないようにします。(抽選された数が重複しないようにするため)

変数resultの定義と空の値を代入することで、新しいカードを作成した際に結果の文字表示を消去しています。

次にbody要素内のtable要素に二次元配列でビンゴカードの表示をさせます。「table.innerHTML = "";」で2回目以降にSTARTボタンをクリックしたときに前のカードを削除しています。
「let s = Math.floor(Math.random( )*numbers.length);」で配列numbersからランダムに要素を一つ取得しています。「td.value = numbers[s];」で取得した要素の数字をtd要素のvalueプロパティに代入しています。
td要素のj1プロバティは後で横の行が揃ったときに値をtrueに変えます。j2は縦の列、j3は斜めが揃ったときにtrueに変えます。
「numbers.splice(s, 1);」でビンゴ用のカードに使われた数字を削除しています。
配列.splice(x, y)で配列の要素x番目からy個の要素を削除することができます。(xは0から数える)

    function lot() {
      if(first){
        numbers = numSet();
        first = false;
      }
      let s = Math.floor(Math.random()*numbers.length);
      let result = document.getElementById("result");
      result.textContent = `結果:${numbers[s]}`;
      let w = "num" + numbers[s];
      let td = document.getElementById(w);
      if(td) {
        td.textContent = "";
      }
      let td2 = document.getElementsByClassName("judge");
      let result2 = document.getElementById("result2");
      result2.textContent = "";
      if((td2.item(0).index == 0 && td2.item(0).textContent == "" && td2.item(0).j1 == false) && (td2.item(1).index == 1 && td2.item(1).textContent == "" && td2.item(1).j1 == false) && (td2.item(2).index == 2 && td2.item(2).textContent == "" && td2.item(2).j1 == false) && (td2.item(3).index == 3 && td2.item(3).textContent == "" && td2.item(3).j1 == false) && (td2.item(4).index == 4 && td2.item(4).textContent == "" && td2.item(4).j1 == false)) {
        word();
        td2.item(0).j1 =true;
        td2.item(1).j1 =true;
        td2.item(2).j1 =true;
        td2.item(3).j1 =true;
        td2.item(4).j1 =true;
      } else if ((td2.item(5).index == 5 && td2.item(5).textContent == "" && td2.item(5).j1 == false) && (td2.item(6).index == 6 && td2.item(6).textContent == "" && td2.item(6).j1 == false) && (td2.item(7).index == 7 && td2.item(7).textContent == "" && td2.item(7).j1 == false) && (td2.item(8).index == 8 && td2.item(8).textContent == "" && td2.item(8).j1 == false) && (td2.item(9).index == 9 && td2.item(9).textContent == "" && td2.item(9).j1 == false)) {
        word();
        td2.item(5).j1 =true;
        td2.item(6).j1 =true;
        td2.item(7).j1 =true;
        td2.item(8).j1 =true;
        td2.item(9).j1 =true;
      } else if ((td2.item(10).index == 10 && td2.item(10).textContent == "" && td2.item(10).j1 == false) && (td2.item(11).index == 11 && td2.item(11).textContent == "" && td2.item(11).j1 == false) && (td2.item(12).index == 12 && td2.item(12).textContent == "" && td2.item(12).j1 == false) && (td2.item(13).index == 13 && td2.item(13).textContent == "" && td2.item(13).j1 == false) && (td2.item(14).index == 14 && td2.item(14).textContent == "" && td2.item(14).j1 == false)) {
        word();
        td2.item(10).j1 =true;
        td2.item(11).j1 =true;
        td2.item(12).j1 =true;
        td2.item(13).j1 =true;
        td2.item(14).j1 =true;
      } else if ((td2.item(15).index == 15 && td2.item(15).textContent == "" && td2.item(15).j1 == false) && (td2.item(16).index == 16 && td2.item(16).textContent == "" && td2.item(16).j1 == false) && (td2.item(17).index == 17 && td2.item(17).textContent == "" && td2.item(17).j1 == false) && (td2.item(18).index == 18 && td2.item(18).textContent == "" && td2.item(18).j1 == false) && (td2.item(19).index == 19 && td2.item(19).textContent == "" && td2.item(19).j1 == false)) {
        word();
        td2.item(15).j1 =true;
        td2.item(16).j1 =true;
        td2.item(17).j1 =true;
        td2.item(18).j1 =true;
        td2.item(19).j1 =true;
      } else if ((td2.item(20).index == 20 && td2.item(20).textContent == "" && td2.item(20).j1 == false) && (td2.item(21).index == 21 && td2.item(21).textContent == "" && td2.item(21).j1 == false) && (td2.item(22).index == 22 && td2.item(22).textContent == "" && td2.item(22).j1 == false) && (td2.item(23).index == 23 && td2.item(23).textContent == "" && td2.item(23).j1 == false) && (td2.item(24).index == 24 && td2.item(24).textContent == "" && td2.item(24).j1 == false)) {
        word();
        td2.item(20).j1 =true;
        td2.item(21).j1 =true;
        td2.item(22).j1 =true;
        td2.item(23).j1 =true;
        td2.item(24).j1 =true;
      }
      if((td2.item(0).index == 0 && td2.item(0).textContent == "" && td2.item(0).j2 == false) && (td2.item(5).index == 5 && td2.item(5).textContent == "" && td2.item(5).j2 == false) && (td2.item(10).index == 10 && td2.item(10).textContent == "" && td2.item(10).j2 == false) && (td2.item(15).index == 15 && td2.item(15).textContent == "" && td2.item(15).j2 == false) && (td2.item(20).index == 20 && td2.item(20).textContent == "" && td2.item(20).j2 == false)) {
        word();
        td2.item(0).j2 =true;
        td2.item(5).j2 =true;
        td2.item(10).j2 =true;
        td2.item(15).j2 =true;
        td2.item(20).j2 =true;
      } else if ((td2.item(1).index == 1 && td2.item(1).textContent == "" && td2.item(1).j2 == false) && (td2.item(6).index == 6 && td2.item(6).textContent == "" && td2.item(6).j2 == false) && (td2.item(11).index == 11 && td2.item(11).textContent == "" && td2.item(11).j2 == false) && (td2.item(16).index == 16 && td2.item(16).textContent == "" && td2.item(16).j2 == false) && (td2.item(21).index == 21 && td2.item(21).textContent == "" && td2.item(21).j2 == false)) {
        word();
        td2.item(1).j2 =true;
        td2.item(6).j2 =true;
        td2.item(11).j2 =true;
        td2.item(16).j2 =true;
        td2.item(21).j2 =true;
      } else if ((td2.item(2).index == 2 && td2.item(2).textContent == "" && td2.item(2).j2 == false) && (td2.item(7).index == 7 && td2.item(7).textContent == "" && td2.item(7).j2 == false) && (td2.item(12).index == 12 && td2.item(12).textContent == "" && td2.item(12).j2 == false) && (td2.item(17).index == 17 && td2.item(17).textContent == "" && td2.item(17).j2 == false) && (td2.item(22).index == 22 && td2.item(22).textContent == "" && td2.item(22).j2 == false)) {
        word();
        td2.item(2).j2 =true;
        td2.item(7).j2 =true;
        td2.item(12).j2 =true;
        td2.item(17).j2 =true;
        td2.item(22).j2 =true;
      } else if ((td2.item(3).index == 3 && td2.item(3).textContent == "" && td2.item(3).j2 == false) && (td2.item(8).index == 8 && td2.item(8).textContent == "" && td2.item(8).j2 == false) && (td2.item(13).index == 13 && td2.item(13).textContent == "" && td2.item(13).j2 == false) && (td2.item(18).index == 18 && td2.item(18).textContent == "" && td2.item(18).j2 == false) && (td2.item(23).index == 23 && td2.item(23).textContent == "" && td2.item(23).j2 == false)) {
        word();
        td2.item(3).j2 =true;
        td2.item(8).j2 =true;
        td2.item(13).j2 =true;
        td2.item(18).j2 =true;
        td2.item(23).j2 =true;
      } else if ((td2.item(4).index == 4 && td2.item(4).textContent == "" && td2.item(4).j2 == false) && (td2.item(9).index == 9 && td2.item(9).textContent == "" && td2.item(9).j2 == false) && (td2.item(14).index == 14 && td2.item(14).textContent == "" && td2.item(14).j2 == false) && (td2.item(19).index == 19 && td2.item(19).textContent == "" && td2.item(19).j2 == false) && (td2.item(24).index == 24 && td2.item(24).textContent == "" && td2.item(24).j2 == false)) {
        word();
        td2.item(4).j2 =true;
        td2.item(9).j2 =true;
        td2.item(14).j2 =true;
        td2.item(19).j2 =true;
        td2.item(24).j2 =true;
      }
      if((td2.item(0).index == 0 && td2.item(0).textContent == "" && td2.item(0).j3 == false) && (td2.item(6).index == 6 && td2.item(6).textContent == "" && td2.item(6).j3 == false) && (td2.item(12).index == 12 && td2.item(12).textContent == "" && td2.item(12).j3 == false) && (td2.item(18).index == 18 && td2.item(18).textContent == "" && td2.item(18).j3 == false) && (td2.item(24).index == 24 && td2.item(24).textContent == "" && td2.item(24).j3 == false)) {
        word();
        td2.item(0).j3 =true;
        td2.item(6).j3 =true;
        td2.item(12).j3 =true;
        td2.item(18).j3 =true;
        td2.item(24).j3 =true;
      } else if ((td2.item(4).index == 4 && td2.item(4).textContent == "" && td2.item(4).j3 == false) && (td2.item(8).index == 8 && td2.item(8).textContent == "" && td2.item(8).j3 == false) && (td2.item(12).index == 12 && td2.item(12).textContent == "" && td2.item(12).j3 == false) && (td2.item(16).index == 16 && td2.item(16).textContent == "" && td2.item(16).j3 == false) && (td2.item(20).index == 20 && td2.item(20).textContent == "" && td2.item(20).j3 == false)) {
        word();
        td2.item(4).j3 =true;
        td2.item(8).j3 =true;
        td2.item(12).j3 =true;
        td2.item(16).j3 =true;
        td2.item(20).j3 =true;
      }
      numbers.splice(s, 1);
      if(!numbers.length) {
        result.textContent = "抽選は終わりました。";
      }
    }

関数lotはビンゴの抽選をする関数です。
最初のif文では配列numbersに1〜99の数字を代入していますが、抽選を開始した1回目だけ行うようにしています。2回目以降はfalseとなるので抽選された数字が出ることはありません。

次にランダムに数字を出して、後で「let w = "num" + numbers[s];」と「let td = document.getElementById(w);」でtdに代入した値が、if文でビンゴカードに存在する数字のidと同じ場合はカードの数字を埋めます。

変数td2でビンゴカード上の全てのマスのデータを取得します。

「let result2 = document.getElementById(result2);」と「result2.textContent = "";」でビンゴした後に再抽選したときにビンゴの表示を消去しています。

次のif文でビンゴの判定をしています。
横の行、縦の列、斜めのそれぞれでインデックス番号が揃っているかと画面の表示が埋まっているか判定しています。該当する場合はword関数でビンゴの表示を行い、2回目以降にビンゴの表示をしないようにj1,j2,j3の値にtrueを代入します。
判定が終わったら、「numbers.splice(s, 1);」で抽選された要素を削除します。

最後のif文では、全ての抽選が出尽くした際に抽選が終わった表示をするようにします。

    function numSet() {
      let i = [];
      for(let n = 1; n < 100; n++) {
        i.push(n);
      }
      return i;
    }

numSet関数では配列に1〜99の数字を代入して返すようにしています。

    function word() {
      let result2 = document.getElementById("result2");
      result2.textContent = "ビンゴです。おめでとうございます。";
    }

word関数ではビンゴしたときに画面にビンゴの表示を出すようにしています。

下記のコードをコピーしてファイルに貼り付ければ試すことができます。

<!DOCTYPE html>

<html>

<head>
  <meta charset="UTF-8">
  <title>bingo</title>

  <style>
    table {
      border: 3px double black;
      border-collapse: collapse;
    }
    td {
      width: 50px;
      height: 50px;
      border: 1px solid black;
      font-size: 30px;
      text-align: center;
    }
    button {
      width: 60px;
      height: 30px;
      margin: 0px 10px 0 0;
    }
    span {
      font-size: 20px;
      margin: 0;
    }
  </style>

  <script>
    let numbers = [];
    let first;

    function start() {
      numbers = numSet();
      first = true;
      let result = document.getElementById("result");
      result.textContent = "";

      let table = document.getElementById("table");
      table.innerHTML = "";
      for(let r = 0; r < 5; r++) {
        let tr = document.createElement("tr");
        for(let c = 0; c < 5; c++) {
          let td = document.createElement("td");
          let s = Math.floor(Math.random()*numbers.length);
          td.value = numbers[s];
          td.textContent = td.value;
          td.id = "num" + td.value;
          td.className = "judge";
          td.index = r * 5 + c;
          td.j1 = false;
          td.j2 = false;
          td.j3 = false;
          numbers.splice(s, 1);
          tr.appendChild(td);
        }
        table.appendChild(tr);
      }
    }

    function lot() {
      if(first){
        numbers = numSet();
        first = false;
      }
      let s = Math.floor(Math.random()*numbers.length);
      let result = document.getElementById("result");
      result.textContent = `結果:${numbers[s]}`;
      let w = "num" + numbers[s];
      let td = document.getElementById(w);
      if(td) {
        td.textContent = "";
      }
      let td2 = document.getElementsByClassName("judge");
      let result2 = document.getElementById("result2");
      result2.textContent = "";
      if((td2.item(0).index == 0 && td2.item(0).textContent == "" && td2.item(0).j1 == false) && (td2.item(1).index == 1 && td2.item(1).textContent == "" && td2.item(1).j1 == false) && (td2.item(2).index == 2 && td2.item(2).textContent == "" && td2.item(2).j1 == false) && (td2.item(3).index == 3 && td2.item(3).textContent == "" && td2.item(3).j1 == false) && (td2.item(4).index == 4 && td2.item(4).textContent == "" && td2.item(4).j1 == false)) {
        word();
        td2.item(0).j1 =true;
        td2.item(1).j1 =true;
        td2.item(2).j1 =true;
        td2.item(3).j1 =true;
        td2.item(4).j1 =true;
      } else if ((td2.item(5).index == 5 && td2.item(5).textContent == "" && td2.item(5).j1 == false) && (td2.item(6).index == 6 && td2.item(6).textContent == "" && td2.item(6).j1 == false) && (td2.item(7).index == 7 && td2.item(7).textContent == "" && td2.item(7).j1 == false) && (td2.item(8).index == 8 && td2.item(8).textContent == "" && td2.item(8).j1 == false) && (td2.item(9).index == 9 && td2.item(9).textContent == "" && td2.item(9).j1 == false)) {
        word();
        td2.item(5).j1 =true;
        td2.item(6).j1 =true;
        td2.item(7).j1 =true;
        td2.item(8).j1 =true;
        td2.item(9).j1 =true;
      } else if ((td2.item(10).index == 10 && td2.item(10).textContent == "" && td2.item(10).j1 == false) && (td2.item(11).index == 11 && td2.item(11).textContent == "" && td2.item(11).j1 == false) && (td2.item(12).index == 12 && td2.item(12).textContent == "" && td2.item(12).j1 == false) && (td2.item(13).index == 13 && td2.item(13).textContent == "" && td2.item(13).j1 == false) && (td2.item(14).index == 14 && td2.item(14).textContent == "" && td2.item(14).j1 == false)) {
        word();
        td2.item(10).j1 =true;
        td2.item(11).j1 =true;
        td2.item(12).j1 =true;
        td2.item(13).j1 =true;
        td2.item(14).j1 =true;
      } else if ((td2.item(15).index == 15 && td2.item(15).textContent == "" && td2.item(15).j1 == false) && (td2.item(16).index == 16 && td2.item(16).textContent == "" && td2.item(16).j1 == false) && (td2.item(17).index == 17 && td2.item(17).textContent == "" && td2.item(17).j1 == false) && (td2.item(18).index == 18 && td2.item(18).textContent == "" && td2.item(18).j1 == false) && (td2.item(19).index == 19 && td2.item(19).textContent == "" && td2.item(19).j1 == false)) {
        word();
        td2.item(15).j1 =true;
        td2.item(16).j1 =true;
        td2.item(17).j1 =true;
        td2.item(18).j1 =true;
        td2.item(19).j1 =true;
      } else if ((td2.item(20).index == 20 && td2.item(20).textContent == "" && td2.item(20).j1 == false) && (td2.item(21).index == 21 && td2.item(21).textContent == "" && td2.item(21).j1 == false) && (td2.item(22).index == 22 && td2.item(22).textContent == "" && td2.item(22).j1 == false) && (td2.item(23).index == 23 && td2.item(23).textContent == "" && td2.item(23).j1 == false) && (td2.item(24).index == 24 && td2.item(24).textContent == "" && td2.item(24).j1 == false)) {
        word();
        td2.item(20).j1 =true;
        td2.item(21).j1 =true;
        td2.item(22).j1 =true;
        td2.item(23).j1 =true;
        td2.item(24).j1 =true;
      }
      if((td2.item(0).index == 0 && td2.item(0).textContent == "" && td2.item(0).j2 == false) && (td2.item(5).index == 5 && td2.item(5).textContent == "" && td2.item(5).j2 == false) && (td2.item(10).index == 10 && td2.item(10).textContent == "" && td2.item(10).j2 == false) && (td2.item(15).index == 15 && td2.item(15).textContent == "" && td2.item(15).j2 == false) && (td2.item(20).index == 20 && td2.item(20).textContent == "" && td2.item(20).j2 == false)) {
        word();
        td2.item(0).j2 =true;
        td2.item(5).j2 =true;
        td2.item(10).j2 =true;
        td2.item(15).j2 =true;
        td2.item(20).j2 =true;
      } else if ((td2.item(1).index == 1 && td2.item(1).textContent == "" && td2.item(1).j2 == false) && (td2.item(6).index == 6 && td2.item(6).textContent == "" && td2.item(6).j2 == false) && (td2.item(11).index == 11 && td2.item(11).textContent == "" && td2.item(11).j2 == false) && (td2.item(16).index == 16 && td2.item(16).textContent == "" && td2.item(16).j2 == false) && (td2.item(21).index == 21 && td2.item(21).textContent == "" && td2.item(21).j2 == false)) {
        word();
        td2.item(1).j2 =true;
        td2.item(6).j2 =true;
        td2.item(11).j2 =true;
        td2.item(16).j2 =true;
        td2.item(21).j2 =true;
      } else if ((td2.item(2).index == 2 && td2.item(2).textContent == "" && td2.item(2).j2 == false) && (td2.item(7).index == 7 && td2.item(7).textContent == "" && td2.item(7).j2 == false) && (td2.item(12).index == 12 && td2.item(12).textContent == "" && td2.item(12).j2 == false) && (td2.item(17).index == 17 && td2.item(17).textContent == "" && td2.item(17).j2 == false) && (td2.item(22).index == 22 && td2.item(22).textContent == "" && td2.item(22).j2 == false)) {
        word();
        td2.item(2).j2 =true;
        td2.item(7).j2 =true;
        td2.item(12).j2 =true;
        td2.item(17).j2 =true;
        td2.item(22).j2 =true;
      } else if ((td2.item(3).index == 3 && td2.item(3).textContent == "" && td2.item(3).j2 == false) && (td2.item(8).index == 8 && td2.item(8).textContent == "" && td2.item(8).j2 == false) && (td2.item(13).index == 13 && td2.item(13).textContent == "" && td2.item(13).j2 == false) && (td2.item(18).index == 18 && td2.item(18).textContent == "" && td2.item(18).j2 == false) && (td2.item(23).index == 23 && td2.item(23).textContent == "" && td2.item(23).j2 == false)) {
        word();
        td2.item(3).j2 =true;
        td2.item(8).j2 =true;
        td2.item(13).j2 =true;
        td2.item(18).j2 =true;
        td2.item(23).j2 =true;
      } else if ((td2.item(4).index == 4 && td2.item(4).textContent == "" && td2.item(4).j2 == false) && (td2.item(9).index == 9 && td2.item(9).textContent == "" && td2.item(9).j2 == false) && (td2.item(14).index == 14 && td2.item(14).textContent == "" && td2.item(14).j2 == false) && (td2.item(19).index == 19 && td2.item(19).textContent == "" && td2.item(19).j2 == false) && (td2.item(24).index == 24 && td2.item(24).textContent == "" && td2.item(24).j2 == false)) {
        word();
        td2.item(4).j2 =true;
        td2.item(9).j2 =true;
        td2.item(14).j2 =true;
        td2.item(19).j2 =true;
        td2.item(24).j2 =true;
      }
      if((td2.item(0).index == 0 && td2.item(0).textContent == "" && td2.item(0).j3 == false) && (td2.item(6).index == 6 && td2.item(6).textContent == "" && td2.item(6).j3 == false) && (td2.item(12).index == 12 && td2.item(12).textContent == "" && td2.item(12).j3 == false) && (td2.item(18).index == 18 && td2.item(18).textContent == "" && td2.item(18).j3 == false) && (td2.item(24).index == 24 && td2.item(24).textContent == "" && td2.item(24).j3 == false)) {
        word();
        td2.item(0).j3 =true;
        td2.item(6).j3 =true;
        td2.item(12).j3 =true;
        td2.item(18).j3 =true;
        td2.item(24).j3 =true;
      } else if ((td2.item(4).index == 4 && td2.item(4).textContent == "" && td2.item(4).j3 == false) && (td2.item(8).index == 8 && td2.item(8).textContent == "" && td2.item(8).j3 == false) && (td2.item(12).index == 12 && td2.item(12).textContent == "" && td2.item(12).j3 == false) && (td2.item(16).index == 16 && td2.item(16).textContent == "" && td2.item(16).j3 == false) && (td2.item(20).index == 20 && td2.item(20).textContent == "" && td2.item(20).j3 == false)) {
        word();
        td2.item(4).j3 =true;
        td2.item(8).j3 =true;
        td2.item(12).j3 =true;
        td2.item(16).j3 =true;
        td2.item(20).j3 =true;
      }
      numbers.splice(s, 1);
      if(!numbers.length) {
        result.textContent = "抽選は終わりました。";
      }
    }

    function numSet() {
      let i = [];
      for(let n = 1; n < 100; n++) {
        i.push(n);
      }
      return i;
    }

    function word() {
      let result2 = document.getElementById("result2");
      result2.textContent = "ビンゴです。おめでとうございます。";
    }
  </script>
</head>

<body>
  <table id="table"></table><br>
  <button onclick="start()">START</button>
  <button onclick="lot()">抽選</button>
  <span id="result"></span><br>
  <span id="result2"></span>
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] 配列とオブジェクトについて

4回目の投稿です。
記事を投稿する度に少しずつ要領を掴めてきた気がします。
ノートにさっとまとめながら勉強しています。

配列

基本的な書き方

配列は他の言語と書き方はほぼ同じ。

const 定数名 = [要素1,要素2,要素3];
console.log(定数名);
console.log(定数名[インデックス番号]);
定数名[インデックス番号] = "文字列"; //数値の場合は""で囲まない。
console.log(定数名[インデックス番号]);

このように書く。
一行目は、定数を宣言し、定数に配列を代入
二行目は、(定数を記述することで)配列を出力
三行目は、配列の中でも指定のインデックス番号の要素(文字列や数値)を更新
四行目は、二行目と同様に(定数を記述することで)配列を出力

具体的には以下のように書く。

sample1
const names = ["itiro","jiro","saburo"];
console.log(names);
console.log(names[0]);
names[0] = "hanako";
console.log(names[0]);
["itiro","jiro","saburo"]
itiro
hanako

と表すことができる。

配列とfor文の組み合わせ

配列とfor文を組み合わせることによって、配列内のすべての値を簡単に出力できる。

sample2
const names = ["itiro","jiro","saburo"];
for (let i = 0; i < names.length; i++) {
  console.log(names[i]);
}
itiro
jiro
saburo

となる。これは、変数i が 0~ 配列namesの要素数まで繰り返すという意味。
また、配列.length とすることで配列の要素数を取得できる。

sample3
const names = ["itiro","jiro","saburo"];
console.log(names.length);
3

オブジェクト

オブジェクトとは、複数のデータをまとめて管理するのに用いる。
オブジェクトがそれぞれの値に名前を付けたものをプロパティという。

配列
[値1,値2,値3]

オブジェクト
{プロパティ1:値1,プロパティ2:値2}

書き方は配列とほとんど変わらない。要素を更新することもできる。

sample4
const item = {name:"pen",price:150};
console.log(item);
console.log(item.name);
item.price = 300;
console.log(item.price);
{name:"pen",price:150}
pen
300

オブジェクトを要素に持つ配列

[配列]内に{オブジェクト1},{オブジェクト2}が存在する。

[{プロパティ1:値1},{プロパティ2:値2}]
sample5
const items = [
      {name:"pen",price:150},
      {name:"note",prce:200}
];
console.log(items);
console.log(items[0]);
console.log(items[0].name);
console.log(items[1]);
conlole.log(items[1].price);
[{name:"pen",price:150}, {name:"note",price:200}]
{name:"pen",price:150}
pen
{name:"note",price:200}
200

参考

Progate>JavaScriptⅡ

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

JavaScript (Part3)

4回目の投稿です。
記事を投稿する度に少しずつ要領を掴めてきた気がします。
ノートにさっとまとめながら勉強しています。

配列

基本的な書き方

配列は他の言語と書き方はほぼ同じ。

const 定数名 = [要素1,要素2,要素3];
console.log(定数名);
console.log(定数名[インデックス番号]);
定数名[インデックス番号] = "文字列"; //数値の場合は""で囲まない。
console.log(定数名[インデックス番号]);

このように書く。
一行目は、定数を宣言し、定数に配列を代入
二行目は、(定数を記述することで)配列を出力
三行目は、配列の中でも指定のインデックス番号の要素(文字列や数値)を更新
四行目は、二行目と同様に(定数を記述することで)配列を出力

具体的には以下のように書く。

sample1
const names = ["itiro","jiro","saburo"];
console.log(names);
console.log(names[0]);
names[0] = "hanako";
console.log(names[0]);
["itiro","jiro","saburo"]
itiro
hanako

と表すことができる。

配列とfor文の組み合わせ

配列とfor文を組み合わせることによって、配列内のすべての値を簡単に出力できる。

sample2
const names = ["itiro","jiro","saburo"];
for (let i = 0; i < names.length; i++) {
  console.log(names[i]);
}
itiro
jiro
saburo

となる。これは、変数i が 0~ 配列namesの要素数まで繰り返すという意味。
また、配列.length とすることで配列の要素数を取得できる。

sample3
const names = ["itiro","jiro","saburo"];
console.log(names.length);
3

オブジェクト

オブジェクトとは、複数のデータをまとめて管理するのに用いる。
オブジェクトがそれぞれの値に名前を付けたものをプロパティという。

配列
[値1,値2,値3]

オブジェクト
{プロパティ1:値1,プロパティ2:値2}

書き方は配列とほとんど変わらない。要素を更新することもできる。

sample4
const item = {name:"pen",price:150};
console.log(item);
console.log(item.name);
item.price = 300;
console.log(item.price);
{name:"pen",price:150}
pen
300

オブジェクトを要素に持つ配列

[配列]内に{オブジェクト1},{オブジェクト2}が存在する。

[{プロパティ1:値1},{プロパティ2:値2}]
sample5
const items = [
      {name:"pen",price:150},
      {name:"note",prce:200}
];
console.log(items);
console.log(items[0]);
console.log(items[0].name);
console.log(items[1]);
conlole.log(items[1].price);
[{name:"pen",price:150}, {name:"note",price:200}]
{name:"pen",price:150}
pen
{name:"note",price:200}
200

参考

Progate>JavaScriptⅡ

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

中学生でもわかる!非同期処理とは?(JS)

あいさつ

初めての人は初めまして!知っている人はこんにちは
どうもAtieです
サボりにサボったqiitaの記事投稿ついに再開です!

今回はJSを極めるうえで避けては通れない「非同期処理」についてです

ちなみになんで題名が「中学生でもわかる!」が入っているのかというと

僕が理解できたから

そうなんです中学生の僕でも理解ができました!
皆さんにわかりやすく書いていきたいと思います

そもそも同期処理って?

そもそも同期処理とはなんでしょうか?
同期処理とは一つの処理が終わるまで待つプログラムです
とはいってもまだ「?」の人が多いいと思うので具体的に説明します

あるプログラムに「処理A」と「処理B」という二つの処理があったとします
処理Aを実行中に処理Bのをしたいと思いました
なので処理Aの中に処理Bを挟みます
するとどうなるでしょうか?
流れでは「処理Aを一度中断 > 処理Bを実行 > 処理Aの続き」というような流れになります
これが同期処理です「上から順に」これが同期処理の流れです
プログラムの例です

main.js
//処理B
const synchronousFunc = () => {
    console.log('これは同期処理関数内のログです');
    return '完了!';
};

//処理A
const message = "同期処理";
const result = synchronousFunc();
console.log(message + result);

このプログラムは一番最初にsynchronousFunc()という関数を定義しました
その後処理Aをするのですが...
処理Aの2行目にある定数resultに代入する際に処理Bの関数synchronousFunc()を使っています
なので処理Aの途中で処理Bが実行されます
処理Bを実行中は処理Aは待機したままです
そして処理Bが終わったら処理Aから続きをする...

このような流れになっています
このプログラムなら同期処理で大丈夫です
しかしwebAPIやサーバーにクエリを送る場合はどうでしょうか?
webAPIやサーバーにクエリを送る場合は人間にすると一瞬ですがコンピューターから見るとものすごく長いです
詳しくは説明しませんが大体人間で例えると10年くらい待っています
その間何もしないのはCPUなどがもったいないですしもしかしたらユーザーがブラウザバックするかもしれませんそのために非同期処理が必要になってきます

非同期処理とは?

非同期処理とはwebAPIなどの処理に時間がかかる処理をしている間に別の処理を実行することです
いわゆるマルチタスクです
例えばwebAPIをたたいている間にwebAPIを使わない処理を流しておくなどです
もう少しわかりやすく説明すると
処理Aと処理Bがあります
処理AはwebAPIをたたきに処理BはwebAPIをたたいた後にする処理(webAPIは使わない)
処理Aを実行したあとに処理Bが普通は実行されるのですが
非同期処理なので処理Aと処理Bがどっちも実行されます
そうすることで処理にかかる時間を節約することができます

同期処理とは違い「○○を実行に○○を実行する」ということができます
非同期処理が使われるのは主にwebAPIをたたくときです

このように非同期処理を使うことで効率的に時間を使っていくことができます!!

最後に

今回は少し短めの内容でしたが今度詳しく書いた記事を投稿したいと思います!
では!

今日の名言

~笑われるほどでかい夢がある~

By DJ銀太

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

回転盤を回すと動画をコマ送り/逆コマ送りする機能を実装する

デモ

https://codepen.io/qwe001/pen/PozgEvZ

動画はフリー素材として公開されているものを使用しています。

何がしたいか

  • 回転盤を時計回りに回転させた時、動画を1フレームずつコマ送りする
  • 回転盤を反時計回りに回転させた時、動画を1フレームずつ逆コマ送りする

実装

(仕事で必要だったので超急ぎで作りました。時間空いたら多分解説書きます)

参考サイト

素敵な回転盤はこちらの作者さんのものを使用しました

https://codepen.io/adadsa/pen/QEzbak

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

【JavaScriptの超基本】コールバック関数について簡単に解説

概要

この記事では、JavaScriptで少し理解が難しいコールバック関数について、超基本的な知識をメモ的にまとめています。
自分用の備忘録なのであしからず。

目次

コールバック関数とは

コールバック関数とは、どのような関数のことを言うのでしょうか。以下MDN web docsからの引用です。

コールバック関数は他の関数に引数として渡される関数で、外側の関数で何らかの処理やアクションを実行します。
引用 : Callback function (コールバック関数)

引数として渡される関数。。。

関数って引数で渡せるの?って感じで少しイメージが湧きにくいですね。

順序立てて解説していきます。

JavaScriptは関数も値

コールバック関数について詳しくみていく前に、「JavaScriptでは関数も値」と言う話をします。

以前関数を定義する際に、定数に代入することができること(関数式)を学びました。
(関数について詳しくまとめている記事はこちらから)

これが意味していることを考えます。

結論から言うと、JavaScriptでは、関数を定数に代入できるため「関数は値の一つとして扱うことができる」と言うことです。
JavaScriptにおける関数は、文字列や数値、配列、オブジェクトと言った値と同じように値として扱えます。

よって、定数が引数として関数に渡されるように、関数もまた引数として渡されます。

まずはここを理解しましょう。

関数の呼び出し方と渡した方

以下のコードを見ながら解説していきます。

index.js
//関数introduceを定義
const introduce = () => {
    console.log('私の名前はたいちです');
};

//関数callを定義
const call = (callback) => {
    console.log('こんにちは!');
    callback();
};

//introduceをコールバック関数としてcallに渡す
call(introduce);

//以下、出力結果
//こんにちは!
//私の名前はたいちです

ここでは、introducecallが関数として定義されています。
また、introduceは引数としてcallに渡されています。

関数を渡すときは、関数名の後ろに()は要りません。()がない場合は関数の定義そのものを指します。(関数名の後ろに()があると関数は呼び出されます。)

コールバック関数の流れとしては、まず引数としてintroduceが渡され、callが呼び出されます。
次に引数であるintroducecallbackに代入され、call内の処理に書かれてるcallbackに代入されます。コールバック関数を呼び出す際には、()をつけます。

この流れで処理が実行されます。

この例でわかるように、JavaScriptでは関数を引数に渡すことができます。そして、この引数になっている関数のことをコールバック関数と呼びます。

引数で関数を定義する

前の例では、既に定義してある関数をコールバック関数として渡しましたが、関数を直接引数の中で定義することもできます。

index.js
const call = (callback) => {
    console.log('こんにちは!');
    callback();
};

//引数の中で関数を定義
call(() => {
    console.log('私の名前はたいちです');
});

//以下、出力結果
//こんにちは!
//私の名前はたいちです

introduceに代入していた関数をそのままcallの引数として記述します。

このように直接定義することも可能です。

関数の引数

コールバック関数には、普通の関数と同様に引数を渡すことができます。

index.js
//コールバック関数に引数を渡す
const call = (callback) => {
    console.log('こんにちは!');
    callback('たいち');
};


call((name) => {
    console.log(`私は${name}です`);
});

//以下、出力結果
//こんにちは!
//私の名前はたいちです

一気にわかりにくくなりましたね笑
処理の流れを見ていきましょう。

まず関数callを定義しています。関数callは、コールバック関数callbackを引数に取ります。

関数callを呼び出す際に、引数で直接(name) => {console.log(`私は${name}です`);}と言う関数を定義しています。
ここで定義されている関数は、引数にnameを取ります。

では、この引数nameはどこから渡されるかと言うと、関数callを呼び出した際に実行されるcallback('たいち')の部分で渡されます。

このような流れで処理が実行されます。

どこから値が渡されるのかわかりにくいので、難しいですね。

まとめ

今回は、コールバック関数について簡単に解説してみました。コールバック関数は、非常に使用頻度が高めなのでしっかりと理解しておきたいところです。

最後まで読んでいただきありがとうございました。ではまた。

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

Electron は v.11 が安定版になった(2020/11/17)

Electronが、ついに v.11 を安定版としました。主な変更点は以下のページにあります(しかもすでにその15時間後に、v11.0.1に更新済み)。
https://www.electronjs.org/releases/stable#release-notes-for-v1100

すでに、Electronは先行して、v.11, v12 のβ版を走らせていたので、大きな変更やサプライズはありませんが、詳しく何が変更になったのかは各βバージョンをあたるしかなく、散逸的だったので、今回まとまったことで一覧を見ることができるようになりました。

Apple Silicon ビルドに対応

Electron は、いま話題の Apple Siliconに対応を表明していたので(Appleの発表スライドのリストにひっそり載っていた)、今回から実験的にビルドが追加されたようです。おそらく新しい M1チップの載ったマシンでも動作可能だと思います(※私は今回、買えませんので動作未確認)。

context bridge の強化

また、今回のバージョンから、さらに context bridge への強化を図ってきていますので、今後対応はより必須になってくるとは思います。これについては、Qiitaの別記事にまとめていますので、参考になさってください。こちらも、v.11 を私の方で検証しつつ、近々記事を更新したと思っています。

Electron(v10.1.5現在)の IPC 通信入門 - よりセキュアな方法への変遷
https://qiita.com/hibara/items/c59fb6924610fc22a9db

これらも含め、以下に今回のバージョンの主な追加項目、改善点を翻訳してまとめましたので、ご参考にしてください。

Release Notes for v11.0.0(意訳)

https://www.electronjs.org/releases/stable#release-notes-for-v1100

スタックの更新

  • Chromium 87.0.4280.47

  • Node v12.18.3

  • V8 v8.7

破壊的な変更

  • 実験的だったAPI BrowserView.{destroy, fromId, fromWebContents, getAllViews}と、BrowserViewidプロパティの削除。

特徴

追加

  • 新しく実験的にApple Silicon(darwin arm64)ビルドの追加。

  • Apple Silicon で Rosetta が動作するときを検知したときのためのプロパティ app.runningUnderRosettaTranslation を新しく追加。

  • V8エンジンのクラッシュメッセージと場所情報を crashReport パラメータに追加。(v10にも)

  • レンダラーのクラッシュをデバッグするのに役立つように、コンソールに小さなコンソールヒントを追加。(v9, v10にも)

  • システムのコンテキストメニューの防止とオーバーライドを可能にするために、新しい system-context-menu を追加。

  • レンダラーがハングしたときの回復をアシストするために、レンダラープロセスを強制的に終了するための webContents.forcefullyCrashRenderer() を追加。

  • 特定のプロトコルを扱うアプリの詳細情報を返すAPI app.getApplicationInfoForProtocol() を追加。

  • app.getAppMetrics() 出力に、name を追加。

  • utility-process-gone イベントを app に追加。

  • macOS 上でバイブレンシーエフェクト(半透明のぼやけた背景を映す効果)の状態のカスタマイズを許すための visualEffectState オプションを BrowserWindows に追加

  • ファイルパスと、最大サムネイルサイズを与えられたプレビューイメージを返す、app.createThumbnailFromPath() API を追加。

  • 以前からずっと壊れていた setVisibleOnAllWorkspaces のための visibleOnFullScreen オプションを戻して追加。

  • WebContent のストリームを得るための getUserMedia で使用することができるように、desktopCapturer.getMediaSourceIdForWebContents() を追加。

  • どんなアプリケーションのアクティブ状態も監視するために、Mac上で did-become-active イベントを追加。

  • コンテキスト分離が有効になっている場合は、webFrame.executeJavaScript からの返す値がワールドセーフになることを保証をするための worldSafeExecuteJavaScript の webPreference を新しく追加。(v9, v10にも)

  • macOS トレイのタイトルのための特定等幅フォントタイプへのオプションパラメーターを追加。

  • Windows へのイベントのサスペンドとレジュームサポートを追加。(v8, v9, v10にも)

  • macOS へのイベントのサスペンドとレジュームサポートを追加。(v8, v9, v10にも)

  • Apple's StoreKit in-app-purchasing ライブラリが提供するが、currencyCode フィールドを追加。

  • Apple の StoreKit アプリ内課金ライブラリが提供しているが、inAppPurchase.getProducts が返す Product オブジェクトには追加されていない currencyCode フィールドを追加。

  • 'resized' (Windows/macOS) と、'moved' (Windows) イベントを BrowserWindow へ追加。

改善

  • context bridge を経由して、JSプリミティブを送信する際のパフォーマンスを改善。(v9, v10にも)

  • context bridge を経由しての大きなオブジェクトを送信する際のパフォーマンスを改善。

  • --interactive フラグで Electron を走らせた時のデフォルトの REPL エクスペリエンスを改善。

  • takeHeapSnapshot() のパフォーマンスを改善。

  • アプリケーションがログイン時に起動できるかどうかを判断する際に起動承認キーを考慮するように app.getLoginItemSettings() と、app.setLoginItemSettings() API を変更。

  • デバッガーモジュールからターゲットに関連付けられた sessionId を公開。(v.8, v9, v10にも)

  • Windows に systemPreferences.getMediaAccessStatus() を実装。(v.8, v9, v10にも)

  • コードキャッシングポリシーを強制するため、V8CacheOptions は、新しい webpreference オプションになった。(v.8, v9, v10にも)

  • disabled-by-default-v8.cpu_profiler のトレースカテゴリが有効になっている場合、レンダラーだけでなくメインプロセスからも cpu サンプルが収集されるようになりました。

削除/非推奨

  • 実験的だったAPI BrowserView.{destroy, fromId, fromWebContents, getAllViews}と、BrowserViewidプロパティの削除。

修正点は、要望があれば

修正点は、かなりの量なので、要望が多ければ翻訳して追記したいと思います。

詳しくは、
https://www.electronjs.org/releases/stable#release-notes-for-v1100

を参照ください。

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

jQuery 備忘録

・js側で値の受け渡し方法

index.html
<form id="modal-form">
 <input type="hidden" id="modal-value-1" /> //値を入れたいhidden
</form>
<input type="text" id="modal-input-1" name="data[User][name]" /> //値を受け取るinput
index.js
var inputVal = $("#̲modal-input-1").val(); //一度変数に受ける
$("#modal-value-1").val(inputVal);

data属性を使って値を設定すれば、あらゆることに利用できる。

form.html
<button class="add mon" data-daynum="1">
<button class="add tue" data-daynum="2">
<button class="add wed" data-daynum="3">
.....
form.js
$(document).on("click", "クラス名", function(){
 let daynum = $(this).attr("data-daynum"); //daynumに曜日番号が入る
 以下いろいろな処理
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【jQuery】日付プルダウンの期間指定と年齢の自動計算

やりたいこと

1.日付プルダウンで、現在から4年前までの期間のみを選択可能にする。
2.日付プルダウンで選択された年月日で年齢を自動計算できるようにする。

完成図

<tr>
  <td><label>お子様のお誕生日</label></td>
  <td>
    <select name="year">
      <!-- 
      <option value='2018'>2018年</option>
      <option value='2019'>2019年</option>
      <option value='2020'>2020年</option> 
      -->
    </select>
    <select name="month">
      <!-- 
      <option value="1">1月</option>
      <option value="2">2月</option>
      <option value="3">3月</option>
      <option value="4">4月</option>
      <option value="5">5月</option>
      <option value="6">6月</option>
      <option value="7">7月</option>
      <option value="8">8月</option>
      <option value="9">9月</option>
      <option value="10">10月</option>
      <option value="11">11月</option>
      <option value="12">12月</option>
      -->
    </select>
    <select name="day">
      <!-- 
      <option value="1">1日</option>
      <option value="2">2日</option>
      <option value="3">3日</option>
      <option value="4">4日</option>
      <option value="5">5日</option>
      <option value="6">6日</option>
      <option value="7">7日</option>
      <option value="8">8日</option>
      <option value="9">9日</option>
      <option value="10">10日</option>
      <option value="11">11日</option>
      <option value="12">12日</option>
      <option value="13">13日</option>
      <option value="14">14日</option>
      <option value="15">15日</option>
      <option value="16">16日</option>
      <option value="17">17日</option>
      <option value="18">18日</option>
      <option value="19">19日</option>
      <option value="20">20日</option>
      <option value="21">21日</option>
      <option value="22">22日</option>
      <option value="23">23日</option>
      <option value="24">24日</option>
      <option value="25">25日</option>
      <option value="26">26日</option>
      <option value="27">27日</option>
      <option value="28">28日</option>
      <option value="29">29日</option>
      <option value="30">30日</option>
      <option value="31">31日</option>
      -->
    </select>
    <select name="old1">
      <option value="0">0歳</option>
      <option value='1'>1歳</option>
      <option value="2">2歳</option>
      <option value="3">3歳</option>
    </select>
    <select name="old2">
      <option value="0">0ヶ月</option>
      <option value="1">1ヶ月</option>
      <option value="2">2ヶ月</option>
      <option value="3">3ヶ月</option>
      <option value="4">4ヶ月</option>
      <option value="5">5ヶ月</option>
      <option value="6">6ヶ月</option>
      <option value="7">7ヶ月</option>
      <option value="8">8ヶ月</option>
      <option value="9">9ヶ月</option>
      <option value="10">10ヶ月</option>
      <option value="11">11ヶ月</option>
    </select>
  </td>
</tr>

// 現在年月日を変数に代入
let today = new Date();

// 現在年月日をそれぞれ変数に代入
let year_val = today.getFullYear();
let month_val = today.getMonth() + 1;
let day_val = today.getDate();

// 期限(始)
start_year = year_val;
start_month = month_val;
start_day = day_val;

// 期限(終)
end_year = year_val - 4;
end_month = month_val;
end_day = day_val;

setYear()
setMonth();
setDay();
setOld();

$('select[name=year]').change(function () {
  setMonth();
  setDay();
  setOld();
});

$('select[name=month]').change(function () {
  setDay();
  setOld();
});

$('select[name=day]').change(function () {
  setOld();
});

// 年プルダウンの制御
function setYear() {
  let min_year = end_year; //(2016)
  let max_year = start_year; //(2020) 

  // 初期化
  $('select[name=year] option').remove();

  // 要素をひとつずつ取得
  for (var i = min_year; i <= max_year; i++){
    $('select[name=year]').append('<option value="' + i + '">' + i + '' + '</option>');
  }
}

// 月プルダウンの制御
function setMonth() {
  // 選択した月を値を取得
  year_val = $('select[name=year]').val();

  // 初期化
  $('select[name=month] option').remove();

  // 初期値
  var min_month = 1;
  var max_month = 12;

  // 月範囲
  if (year_val == start_year) { // 選択した値と現在の年が等しい場合、1月〜現在月までを選択範囲にする
    min_month = 1;
    max_month = start_month;
  } else if (year_val == end_year) { // 選択した値と期限末の年が等しい場合、現在月〜12月までを選択範囲にする
    min_month = start_month;
  }

  // 要素をひとつずつ取得
  for (var i = min_month; i <= max_month; i++){
    $('select[name=month]').append('<option value="' + i + '">' + i + '' + '</option>');
  }
}

// 日プルダウンの制御
function setDay() {
  // 選択した年の値を取得
  year_val = $('select[name=year]').val();
  // 選択した月の値を取得
  month_val = $('select[name=month]').val();

  // 指定月の末日
  var t = 31;
  // 2月
  if (month_val == 2) {
    // 4で割りきれる且つ100で割りきれない年、または400で割り切れる年は閏年
    if (Math.floor(year_val%4) == 0 && Math.floor(year_val%100) != 0 || Math.floor(year_val%400) == 0) {
      t = 29;
    }  else {
      t = 28;
    }
    // 4,6,9,11月
  } else if (month_val == 4 || month_val == 6 || month_val == 9 || month_val == 11) {
    t = 30;
  }

  // 初期化
  $('select[name=day] option').remove();

  // 初期値
  var min_day = 1;
  var max_day = t;    


  if (year_val == start_year && month_val == start_month) { //選択した年月と現在の年月が等しい時、1日〜現在日までを選択範囲とする
    max_day = start_day;
  } else if (year_val == end_year && month_val == end_month) { //選択した年月と期限末の年月が等しい時、現在日の次の日〜指定月の末尾までを選択範囲とする
    min_day = start_day +1;
  }

  // 要素をひとつずつ取得
  for (var i = min_day; i <= max_day; i++){
    $('select[name=day]').append('<option value="' + i + '">' + i + '' + '</option>');
  }
}

// 年齢プルダウンの制御
function setOld() {
  // 選択した年の値取得
  select_year = $('select[name=year]').val(); 
  // 選択した月の値取得
  select_month = $('select[name=month]').val();
  // 選択した日の値取得
  select_day = $('select[name=day]').val();  

  let current = new Date();

  let birthday  = new Date(select_year, select_month, select_day); // 選択した年月日を変数に代入
  let age = today.getFullYear() - birthday.getFullYear();  // 現在の年と設定した年を差し引き年齢を取得
  let current_year = ( current.getFullYear() * 12 ) + (current.getMonth());  // 現在生年月日の月数を取得し、変数に代入
  let birthday_year = ( birthday.getFullYear() * 12 ) + birthday.getMonth();  // 誕生年月日の月数を取得し、変数に代入
  let month = current_year - birthday_year;  // 現在生年月日の月数を誕生年月日の月数で差し引き、月数を取得
  if(month >= 12) {  // month が 12 以上の時
    month = month % 12  // 12で割った余りを month に代入
    if (select_day <= start_day) {  // 今日の日付が選択した日付より大きい時に mouth に 1 を足す
      month = month + 1;
    } else {
      ;
    }
  } else {  // month が 12 以下の時
    if (select_day <= start_day) {
      month = month + 1;
    } else {
      ;
    }  
  }
  //  絶対値
  if (month == 12 || month == -12) {
    month = 0;
  }
  // 選択した月と現在の月が一致している && 選択した日が今日よりも大きい場合年齢をひとつ下げる
  if ( (select_day > start_day) && (select_month == start_month) ) {
    age = age - 1
  }

  $('select[name=old1] option[value=' + age + ']').prop('selected', true);
  $('select[name=old2] option[value=' + Math.abs(month) + ']').prop('selected', true);
}

参考サイト

http://php.o0o0.jp/article/jquery-term_pulldown
https://javascript.programmer-reference.com/js-calculation-age/

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

【Ruby on Rails5】フラッシュメッセージをいい感じに表示させる。

環境

Ruby 2.5.7
Rails 5.2.4

前提

形を問わずフラッシュメッセージの表示が確認できる状態。
turbolinksは切っています。オンの状態で動作確認はしていません。

gem

gem 'devise'

経緯

タイトルに「いい感じ」と表現しましたが、他に言葉が見つかりませんでした・・・笑
ユーザーが何かアクションを起こした時に、それをお知らせする方法としてページ内へのフラッシュメッセージの埋め込みがありますが、それをただ表示させるのは物足りないと感じて今回の形を実装してみました。(AbemaTVのアプリから発想を得ました笑)

目標

ログイン・ログアウトをメインに、アクションを起こすごとに下の方からニュッっとお知らせが一定時間出てくる。
ezgif.com-gif-maker (2).gif

手順

1.お知らせ窓layouts/_flash_window.html.erbを作成する
2.cssでお知らせ窓をいい感じにする
3.コントローラーでflash[:notice]を作成する
4.お知らせ窓専用のjsファイルassets/javascripts/flash_window.jsを作成する

1.お知らせ窓layouts/_flash_window.html.erbを作成する

お知らせ窓views/layouts/_flash_window.html.erbを部分テンプレートとして作成します。
今回は簡単なのでapplication.html.erbに直接書いてもいいのですが、窓の中身が今後複雑になったり、基本的に見えていない部分なので最初から分けておいてもいいかと思います。

layouts/_flash_window.html.erb
<div class="flash-window hidden">
  <p><%= notice %><span class="flash-window--delete">X</span></p>
</div>

CSSクラス名にhidddenを付けていますが、これがついているときは非表示、これがjs(toggle)で外れると表示される仕組みです。
2行目はnoticeメッセージ(フラッシュメッセージ)の後ろにX(バツボタン)を表示させることでお知らせ窓が自動で閉じる前に任意で閉じることができるようにしていきます。

ここで作成した部分テンプレートをapplication.html.erbに反映させていきます。

application.html.erb
...

<main>
  <div class="main">
    <%= yield %>
    <%= render partial: 'layouts/flash_window' %>
  </div>
</main>

...

<main>タグの下に<div>を挟んでいる理由としては、お知らせ窓の位置を<main>直下の<div>に基点にするためです。
<main>タグを起点にしても良かったのですが、後述するcssにmainを指定してしまうと思わぬところで依存関係ができてしまいそうでしたので、今回はお知らせ窓用に<div>を作った形です。
また、<main>タグの中に書くことで、どこのページでもお知らせ窓が表示されるようになります。

2.cssでお知らせ窓をいい感じにする

作成したお知らせ窓をcssでいい感じにデザインします。
これは一例なので、お好きにカスタマイズしてください。
(scssで記述していきます。)

assets/stylesheets/application.scss
main {
  .main {
    .flash-window  {
      // 窓の形、色、文字の形、色
      height: 50px;
      padding: 10px;
      color: white;
      background: black;
      border-radius: 10px;

      // 中のp要素(メッセージ)を上下左右中央揃え
      display: flex;
      justify-content: center;
      align-items: center;

      // 表示・非表示にかける時間
      transition: 0.5s 0s ease;

      // 窓の位置調整
      position: fixed;
      bottom: 40px;
      left: 0;
      right: 0;
      margin: auto;

      p {
        color: white;

        span {
          margin-left: 1vw;
          color: white;

          &:hover {
            cursor: pointer;
          }
        }
      }

      // 窓が非表示の時の状態
      &.hidden {
        opacity: 0;
        bottom: -50px;
      }
    }
  }
}

@media (min-width: PC, タブレット共通) {
  main {
    .main {
      .flash-window  {
        // 大画面の時は窓の横幅は固定
        width: 500px;
      }
    }
  }
}
@media (min-width: スマホ) {
  main {
    .main {
      .flash-window  {
        // 小画面の時は画面サイズの90%で可変
        width: 90%;
        p {
          font-size: 4vw;
        }
      }
    }
  }
}

CSSセレクタのhiddenが付くと"非表示"と言いましたが、実際はbottom: -50px;で要素が画面外に隠れているだけです。
さらにopacity: 0;も指定しているので、hiddenの時は完全に透明になっている状態です。
なので画面内に現れる時は透明の状態から具現化し、画面外にいく(非表示になる)時は段々と透明になっていきます。

3.コントローラーでflash[:notice]を作成する

今回はredirect後のフラッシュメッセージ(ログイン後TOPページにリダイレクトと同時にお知らせが出現など)を想定しています。
render後のフラッシュメッセージ(バリデーションエラーによるrender&エラーメッセージ)については最後に触れます。

users_controller.rb
...

def update
  if current_user.update(user_params)
    flash[:notice] = '会員情報を更新しました。'
    redirect_to user_path(current_user)
  else
    render :edit
  end
end

...

ここでは会員情報の更新時に会員詳細にリダイレクト&フラッシュメッセージの表示をする記述をしましたが、必要なところがあれば適宜行ってください。
基本的にはflash[:notice] = '内容'でnoticeキーにフラッシュメッセージを格納したあとに、redirect_toで任意のページに遷移することで、遷移先で先ほど格納したフラッシュメッセージが表示されるようになります。
ちなみに、deviseを使った新規登録・ログイン・ログアウトの処理はdevise内部で自動的にflash[:notice] = 'ログインしました。'等が格納されるので、自分で記述する必要はありません。

4.お知らせ窓専用のjsファイルassets/javascripts/flash_window.jsを作成する

最後に専用のjsファイルを作成していきます。(既存のapplication.jsから切り分ける理由は後述します。)

flash_window.js
addEventListener('load', ()=> {
  const flashWindow = document.getElementsByClassName("flash-window")[0];
  flashWindowToggle();
  setTimeout(flashWindowToggle, 3000)
  document.getElementsByClassName("flash-window--delete")[0].addEventListener('click', flashWindowToggle);
  function flashWindowToggle() {
    flashWindow.classList.toggle("hidden");
  }
});

addEventListenerのイベントハンドラにloadを指定しています。
これはページの読み込みが全て完了した時点でイベントを発生させないと、ページが読み込み中(白紙)の時に先にお知らせウィンドウだけが表示されてしまうからです。
toggleメソッドを使ったCSSクラスのhiddenの追加・削除は関数化しています。
この関数が呼び出されると、hiddenが削除されお知らせ窓が表示されます。
その後、この関数が3000ms(3秒)後に自動的に呼び出されるか、X(バツボタン)がクリックされると、hiddenが追加されます。

そして、このjsファイルはflash[:notice]に値が入っている時だけ発火させたいので、設定を少し変えていきます。

application.js
...

//= stub flash_window #ここを追加
//= require_tree .

...

application.jsの冒頭の読み込み設定で//= stub flash_windowを書くことで、flash_window.jsがプリコンパイルで結合されるjsファイルから除外されます。
しかし、この状態ではflash_window.js自体がプリコンパイルされないことになってしまうので、config/initializers/assets.rbに以下を追記します。

config/initializers/assets.rb
...

# 以下を追加
Rails.application.config.assets.precompile += %w( flash_window.js )

...

これにより、flash_window.jsのプリコンパイルを明示できます。

しかし、このままではプリコンパイルはされてもapplication.jsには結合されていないので読み込まれません。
そこでapplication.html.erb<head>タグ内にflash_window.jsの読み込みタグを直接追加する必要があります。

layouts/application.html.erb
<head>

...

<%= javascript_include_tag 'application', defer: true %>

# 以下を追加
<% if flash[:notice] %>
  <%= javascript_include_tag 'flash_window' %>
<% end %>

...

</head>

このようにapplication.jsからflash_window.jsを切り離すことでif文でflash[:notice]がある時だけflash_window.jsが読み込まれるという動作を作ることができました。
defer: trueオプションを付けていない理由は、今回はflash_window.js内のイベントがページ読み込み完了後であることをaddEventListener('load', ()=>{...});という形で直接書いているためです。
このファイルに他のイベントを書く場合はdefer: trueが必要になるかも知れませんので、そこは適宜お願いします。

以上で、目的の動作が達成されます。

ここからは番外編です!

番外編

番外編その1.不要なフラッシュメッセージが表示される場合

deviseのcreate, update, destroyアクションにはほとんどの場合フラッシュメッセージflash[:notice]が格納されているため、不要な場面でも窓が出てきてしまうことがあります。
その場合の対処法を書いていきます。

application_controller.rb
...

protected

...

# deviseで作成されたflashのリセット
def delete_devise_flash_messages
  flash[:notice] = nil
end

application_controller.rbでこのメソッドを定義し、不要なフラッシュメッセージが表示されるコントローラー内でbefore_action :delete_devise_flash_messagesまたはafter_action :delete_devise_flash_messagesを書くとフラッシュメッセージflash[:notice]が表示されなくなります。(:notice以外のキーは残ります。)

私の場合は新規登録の場面で利用しています。

入力内容の確認画面から認証メールの送信アクション、そして"メールを確認してください。"というページに遷移するのですが、そのページ内でメールについて詳しい説明を書いているのに、改めてフラッシュメッセージで"メールを確認してください"は重複するので不要です。
そこで下記のような記述をしています。

registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

  before_action :delete_devise_flash_messages, only: %i[email_notice]

  # 入力された内容を確認するページ
  def confirm
  end

  # 認証メールが送信されたことをお知らせするページ
  def email_notice
  end

  # 登録が完了したことをお知らせするページ
  def complete
  end

end

これで認証メールの送信をお知らせするページemail_noticeアクションのみflash[:notice]のリセットが働いてフラッシュメッセージが表示されなくなります。

番外編その2.エラーのフラッシュメッセージに対応する方法

ここまではflash[:notice]に格納されたメッセージのみを取り扱ってきましたが、バリデーションで発生したエラーなども表示したい場合はflash[:alert]を追加していきます。
さらにフラッシュメッセージごとに挙動を変更したい場合はflash[:error]flash[:success]なんかも自作されてみるといいでしょう。
変更箇所は主に2カ所です。

layouts/application.html.erb
...

<%= javascript_include_tag 'application', defer: true %>

<%# if文に || flash[:alert]を追加 %>
<% if flash[:notice] || flash[:alert] %>
  <%= javascript_include_tag 'flash_window' %>
<% end %>

...

if文に|| flash[:alert]キーを追加します。
これにより:noticeキーもしくは:alertキーに値があるとflash_window.jsが発火するようになります。

次に部分テンプレート内にもalertを追加をしていきます。

layouts/_flash_window.html.erb
<div class="flash-window hidden">
  <% if flash[:notice] %>
    <p><%= notice %><span class="flash-window--delete">X</span></p>
  <% end %>
  <% if flash[:alert] %>
    <p><%= alert %><span class="flash-window--delete">X</span></p>
  <% end %>
</div>

部分テンプレートにもif文を書くことで表示や動作などの細かい部分を分けることができるようになります。

まとめ

今回ご紹介した物はベースとなると考えていますので、動作などはお好きなようにカスタムしていただければと思います^^
みなさまの参考になれば幸いです。

また、質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

私のTwitterでも毎日このようなテクニックや感想・考察を発信していますので、もしご興味があれば一度覗いてみてください(´ー`)
Twitter - @Masao_Sasaki_ae

最後まで読んでいただきありがとうございました!

参考サイト

より実用的な使い方については、私のGitHubに実際に使っているファイルを公開しているのでこちらも参考にしていただければと思います!
GitHub - MasaoSasaki/matchi

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

【kintone】kintone.Promise.all で他アプリからデータを取得して計算しよう

前回の記事では、async/await を使って他のアプリからデータを取得して計算をしてみました。

【kintone】async/await を使って他のアプリからデータを取得する

今回は、前回と同様の内容のJavaScriptプログラミングを、同じく非同期処理の同期がとれる kintone.Promise.all というモノを使ってやってみて、前回のasync/await との違いを比べてみたいと思います。

kintone.Promise.all と async/await の使い分けができるようになることが目標です。
初心者向けのほんわり記事を目指しています

アプリの準備

前回作成したアプリをそのまま使いましょう!
アプリA、B、Cの数値フィールドの値の和を
アプリDの数値フィールドに表示させたいと思います。

アプリA アプリB
image.png image.png
アプリC アプリD
image.png image.png

kintone.Promise.all を使ったJavaScript

Promise とは?

Promise は、MDN web docs によると、
非同期処理の結果を表現するオブジェクト・・・とのことですが、つまり

Promise は、非同期処理の終了を待ち、非同期処理の結果(成功か失敗か)によって
次の処理選んで実行することができるようになるモノ(オブジェクト)です。

Promise を使うと何が嬉しいかと言うと、async/await と同様に非同期処理を同期させて、想定した順番通りにプログラムを実行させることができるようになります。
バラバラな非同期処理の交通整理をして、うまいことやってくれるのです。

今はPromiseの正体はよくわからないかも知れませんが、使ってみることで少しずつ掴んでいきましょう!
この記事ではPromiseとは?というところにあまり深入りせずに、 Promise.all と async/await を使い分けることを目指したいと思います。

Promise.all というメソッド(技)について

非同期処理をうまいことやってくれる Promise には、
これまたうまいことやってくれる Promise.all というメソッド(技)があります。

この技は、複数の非同期処理が全部終わるまで待って処理をする! という技です。
複数の非同期処理がすべて「成功」で終わった場合、 .then でつないだ先の処理を行います。このとき、複数の非同期処理は順番に行うわけではなく、同時並行で進んでいます。

Promise.all([非同期処理A, 非同期処理B, 非同期処理C]).then(非同期処理が終わったあとにする処理)

// 家事で表現するとこんな感じ
Promise.all([ご飯を炊く, 副菜を作る, メインのおかずを作る]).then(食卓に並べて食べる)

// ドラゴン●ールで表現するとこんな感じ
Promise.all([一星球を探す,二星球を探す,三星球を探す,・・・・,七星球を探す]).then(神龍を呼び出す)

非同期処理が全部成功したら、次の処理を行います。
ドラゴ●ボールが全部揃ったら神龍を呼び出します。

こんな感じでたとえ話と比較するとわかりやすいかと思います。

async/await を Promise.all で書いてみる

前回の、async/await を用いたプログラムを、Promise.allで書き換えます。
アプリA~CのレコードIDが1のレコードの数値フィールドを全部足す・・・というプログラムです。
まずは前回のプログラムを見ていきましょう。

async/await を用いたプログラム

    kintone.events.on('app.record.create.show', async event => {

        //アプリA~Cからレコード番号1のレコードの情報を取ってくる
        const respA = await kintone.api('/k/v1/record', 'GET', {'app': アプリAのID,'id':1});
        const respB = await kintone.api('/k/v1/record', 'GET', {'app': アプリBのID,'id':1});
        const respC = await kintone.api('/k/v1/record', 'GET', {'app': アプリCのID,'id':1});

        // アプリA~Cのレコード番号1の数値フィールドの合計を、アプリDの数値フィールドに入れる
        event.record.数値.value = Number(respA.record.数値.value) 
                                + Number(respB.record.数値.value) 
                                + Number(respC.record.数値.value);
        return event;

    });    

kintone.Promise.all を用いたプログラム

kintone では Promise ではなく、 kintone 向けに用意されている、 kintone.Promise を使います。

    kintone.events.on('app.record.create.show', event => {

        //非同期処理A~Cを準備
        const respA = kintone.api('/k/v1/record', 'GET', {'app': アプリAのID,'id':1});
        const respB = kintone.api('/k/v1/record', 'GET', {'app': アプリBのID,'id':1});
        const respC = kintone.api('/k/v1/record', 'GET', {'app': アプリCのID,'id':1});

        //respA ~ C の処理が全部成功したら、sum を計算する処理を行う
        return kintone.Promise.all([respA, respB, respC]).then(resps => { 
            //respA~Cの処理が全部成功したら行う処理
            //アプリA、アプリB、アプリCのレコードIDが1のレコードの数値フィールドの総和を求める
            let sum =0;
            resps.forEach(v=>{
                sum += Number(v.record.数値.value);
            });
            //総和をアプリDの数値フィールドに表示させる
            event.record.数値.value = sum;
            return event;
        });                                

    });    

比較ポイント

Promise.all の方では async/await 書かないよ

async/await を使うプログラムでは返り値(レコード情報を持っているPromise)が戻ってくるのを待ち、respA~Cに代入していました。

kintone.events.on('app.record.create.show', async event => {
    const respA = await kintone.api('/k/v1/record', 'GET', {'app': アプリAのID,'id':1});

Promise.all を使うプログラムでは、async/await は書きません。後のPromise.allでrespA, respB, respC 全部の非同期処理が終わるのを待ちます。

kintone.events.on('app.record.create.show', event => {
    const respA = kintone.api('/k/v1/record', 'GET', {'app': アプリAのID,'id':1});

Promise.all は並列処理をまとめて待つ

async/await では、respA, respB, respCの処理を順番に待っていました。
Aが終わるまで待ち、Bが終わるまで待ち、Cが終わるまで待ちます。
一つ一つの処理を待つので時間がかかります。

//アプリA~Cからレコード番号1のレコードの情報を取ってくる
const respA = await kintone.api('/k/v1/record', 'GET', {'app': アプリAのID,'id':1});
const respB = await kintone.api('/k/v1/record', 'GET', {'app': アプリBのID,'id':1});
const respC = await kintone.api('/k/v1/record', 'GET', {'app': アプリCのID,'id':1});

Promise.allは、並列処理のA,B,Cが全部終わるのを待ちます。
並列処理とは、例えば・・・料理を作るときのような処理です。

ご飯を炊きながら、複数のおかずを同時並行で作るような・・・。
同時並行で処理をして、順番関係なく、全部できあがったら食卓に出すような処理を行っています。

そのため、A,B,Cの処理が並行処理で良ければ(順番が関係ない処理であれば)、
async/await よりも早く処理が終わります。

※ドラ●ンボールで例えると、一人でド●ゴンボールを1つずつ探すのではなく、Z戦士みんなで手分けして探す(並列処理)ほうが早く全部見つかって神龍を早く呼び出す事ができます。

kintone.Promise.all を書くところ

kintone.Promise.all(~は、return の後に書いて・・・、後に続く処理の中でreturn event;します。

//respsにはrespA,respB,respCの値が配列で入ります。
return kintone.Promise.all([respA, respB, respC]).then(resps => { 
    ・・・(計算の処理など)・・・
    ・・・(表示の処理等)・・・
    return event;
});

まとめ

というわけで、長くなりましたが

並列処理でタイミングを待ちたい非同期処理には Promise.all
処理の順番が大切な非同期処理場合は await/async

を使いましょう!

次回は、非同期処理を async/await を用いて順番通りに実行する方法について書きたいと思います。

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

ISO8601 形式を日本時間に変換して表示 JavaScript

前提・やりたいこと

Amazon DynamoDB に日付がISO-8601 形式の文字列として格納されていて、さらにUTCでした。
日本時間かつ、日付だけ表示したい。

これ

const dateFormatter = (dateIso8601: string) =>
  new Date(Date.parse(dateIso8601))
    .toLocaleDateString('ja-JP')
    .replace(/\//g, '.');
// 結果
2020.11.17

参考

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

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

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

rails new〜デプロイまでの学習レポート

はじめに

本記事は、ぼくが2020年7月〜10月までに学習した、Ruby及びRailsの内容を振り返るものです。

今年の2月くらいの段階ではHTMLもまるで書けないくらい無知だったのですが、
等の学習サイトや各種記事を参考にさせていただいて、なんとかデプロイまで漕ぎ着けることができました。

なお現在の状況としては、

  • 就職活動中(HTMLコーダー職志望)
  • 学習を継続し、社内でのエンジニア登用を希望している
  • フロントエンドとインフラに関心が強い
  • Railsアプリの他に、Web制作に向けたWordPressの学習も行った
  • フロント・バックともにそれらサイトで学習の継続を予定

というところです。

制作したアプリケーション

動いている実際のアプリはこちら
使用言語はRuby、フレームワークはRuby on Railsです。

AWS EC2サーバーにて、

  • アプリケーションサーバ ▶︎ Puma
  • Webサーバ ▶︎ Nginx
  • データベース ▶︎ MySQL

という構成で動いています。

機能は現在

  • 映画作品のランダム表示機能
    スクリーンショット-2020-11-07-14.33.45-1024x587.png

  • 記事投稿機能
    スクリーンショット-2020-11-07-14.40.55-1024x587.png

以上の2つのみ。
正直ポートフォリオと呼んでいいのかさえ怪しいものではあります。

しかしながら、学習の目標を立てる際は「方向性だけわかるようにして機能は後回し、まずはデプロイまで一通りやる」ということを重点的に考えていたので、その目標自体は達成できました。本当に難しかったので、機能を書くより先にすませられたのは正直ホッとしています。

学習したこと

【1】Webサイトが動く大まかな仕組みについて

Rubyへの理解というよりは、Railsの仕組みに関して学ぶところが多かったように思えます。
フレームワーク側で効率化されている要素がかなり多く、大したRubyの知識がなくても何かしらの動作をするものは作れてしまうのが、すごいところでもあり、逆に油断してしまいそうだという印象です。

RailsはMVCというデザインパターンを採用しており、
「扱う情報を定義したクラス内で処理を記述する」
「SQL文によってデータベースから情報を取得する」
という概要に触れただけでも、Webサイトの動作に必要な処理の大まかなイメージを掴めたのはよかったと思っています。

【2】サーバーとデータベース

ユーザーからのリクエストを処理するWebサーバ(Nginx)と、Webサーバからのリクエストを受けてRailsを実行するアプリケーションサーバ(Puma)など、サーバーサイドの初歩的な技術を体験することができました。

AWS EC2で立てたインスタンスにNginxとPumaをインストールした構成にしており、データベースはRDSを使わずに、直接MySQLをインストールしてRailsと接続しています。

また、ターミナルからコマンドを使用したサーバーへのSSH接続も大変にいい経験でした。
FTPを使わなくてもサーバーに直接ログインし、viコマンドでファイルの書き換えなどを行うことができるようになりました。
作業の安全性を確保する上で、重要な知見になったと思います。

機能が少ない段階で一度デプロイしようと思ったのは、

  1. 初めは難しいだろうから、先に乗り越えてしまおうと思ったから
  2. 機能を実装しても、Web上で動作できなければ人に見せられないから
  3. 一度デプロイしてしまえば、その後はコードを書くことに集中できると判断したから
  4. さらにその過程で、Capistorano等の自動デプロイの方法も勉強できるから

というのが理由です。
実際unicornサーバが起動できずに何日も浪費してしまうなど、ローカル環境構築以上につまづくことの多い部分でした。公開が無事に完了しただけでもまずはよかったと思っています。

【3】フロントエンド

JavaScriptはProgateを一周しただけでしたが、少し苦手意識がありました。
処理が軽快になるというメリットもあって、動作させる部分はしばらくCSSアニメーションによって記述していたのですが、結果的にそれはjQueryを理解する大きな助けになりました。

いずれにせよクラスやidを使って直接的に動作を指定するという点では、両者に違いはないと気がつきました。
同時にCSSの場合、モバイル(タップ操作)での動作が厳密には定義されていない部分があり、レスポンシブ対応する上ではjQueryでなければ書けないこともあるのだ、というのも興味深い発見でした。

フロントエンドは現在特に関心を持っている分野であり、今後は基礎的なJavaScriptへの理解をもっと進めていきたいと考えています。
さらに、「jQueryによるDOM操作は規模が大きくなればなるほど面倒が増えていくだろう」という見当もついたため、今後はVue.jsによる柔軟な実装にも対応できるようになりたいと考えており、今後しばらくの学習目標として考えています。


今後の学習について

これらの経験を踏まえ、今後の個人学習は以下のような内容を考えています。

  1. JavaScriptを初め、RubyやPHPなどの動的型付け言語の基本文法

    1. 復習的な内容にはなりますが、一旦インフラは置いておき、まずはプログラミング言語自体の理解をどんどん深めていきたいです。
    2. JavaScriptの理解から派生して、フロントならVue.jsTypeScript、バックエンドはPHP等、学習内容を応用しながら順序立てて理解していきます。
  2. HTML・CSSに関する更なる理解

    1. HTML5及びCSS3の機能の多さに驚きました。SVG素材がとても使いやすく、音声や動画も簡単に挿入できたり、文字の折りたたみや計算などもHTMLだけで記述できます。CSSではアニメーションをつけたり、Sassによる効率化も可能です。
    2. これらを学習するだけでもレベルアップできますし、併せてJavaScriptの学習も続ければ、すぐに複雑なサイトを作れるようになると思います。
  3. デプロイの自動化
     1.Circle CICapistranoを使った自動デプロイももちろんですが、今一番やりたいのはWordPressテーマのソースコードを変更した際、GitHubのmasterブランチにPushした時点で自動的に反映できるようにすることです。
    Railsアプリで同様の自動化を行う入門もかねて、WordPressによるコーディング・フロントエンド学習を効率化したいと考えています。

最後に

7月に前職を退職してから現在に至るまで、学習においては多数の方のお世話になっております。
Qiitaやブログ、オウンドメディア等にあるたくさんの記事を参考にさせていただきましたし、teratailで拙い質問に答えていただいたこともありました。

皆様にはこの場を借りてお礼を申し上げます。

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

JavaScript PHP 正規表現を使用した文字列置換でが何を指しているかわからなかったので調べて簡単にまとめる

目的

  • JavaScriptとPHPの文字列置換でどうして$1にヒットした文字列が入っているのかわからなかったので調べてみた

前提情報

確認

  1. Chromeの検証モードのコンソールでで下記を実行してみる。

    /(https?:\/\/[^\s]*)/g.exec('https://qiita.com/miriwo は筆者のQiitaのホームです');
    
  2. 下記の出力が得られた。

    画像

  3. キー0とキー1にヒットした文字列「https://qiita.com/miriwo」が格納されている。

  4. 正規表現部分を下記のように修正して実行してみる。

    /https?:\/\/[^\s]*/g.exec('https://qiita.com/miriwo は筆者のQiitaのホームです');
    
  5. 下記の出力が得られた。

    v-html_-_Google_検索.png

  6. キー0にのみヒットした文字列https://qiita.com/miriwoが格納されている。

  7. どうやら$1などは正規表現部分のいくつめの括弧でマッチしたものかを表しているようである。

  8. 下記のリンクの「引数としての文字列指定」の「$n」の記載にそれっぽいことが書かれている。

  9. $2を作り出すために意図的に下記を実行してみる。ドキュメントの記載どおりなら$2はキーとしては存在するが、検索条件を指定していないので何も値が格納されないはずである。

    /(https?:\/\/[^\s]*)()/g.exec('https://qiita.com/miriwo は筆者のQiitaのホームです');
    
  10. 予想通り下記のように出力された$2はキーとして存在しているが値は格納されていない。

    v-html_-_Google_検索-2.png

  11. では$0は何を指しているのかを確認するために下記を実行してみた

    /(https?)(:\/\/[^\s]*)/g.exec('https://qiita.com/miriwo は筆者のQiitaのホームです');
    
  12. 下記のような出力が得られた。下記の結果から$0は括弧に関係なく正規表現部分全体でヒットした文字列が格納されているものであると言える。

    v-html_-_Google_検索-3.png

  13. 個人のブログではあるがまとめてくださっている方がいた。「$0 は正規表現にマッチした文字列全体を表し、$1, $2, $3 は、それぞれ1番目、2番目、3番目の左括弧で囲まれた部分のパターンに一致した部分文字列を表します。」と書かれているのでおそらく自分の見解は間違えていないように思える。

付録

  1. 自分がよく使うPHPでは正規表現を使った置換はどのように行われるのかが気になったので公式ドキュメントを呼んでみた。
  2. PHPで正規表現を用いた文字列置換を行うときはpreg_replace()メソッドを使用するようである。
  3. 下記のように当該メソッドを使用する。

    preg_replace(検索条件文字列, 置換後の文字列, 置換対象文字列);
    
  4. 「検索条件文字列」は正規表現を用いて記載するものでありJavaScriptと同じようにデリミタでくくって記載する。

  5. そして検索にヒットしたものが入ってくるキーも$0や$1などJavaScriptと一緒のようだった。公式ドキュメントのリンクを下記に記載し抜粋したもの載せる。(PHPのドキュメントのほうがわかりやすく書いてくれている気がした。先にこっちを読めばよかった。)

    • https://www.php.net/manual/ja/function.preg-replace.php

      replacement では、 \n 形式または $n 形式で参照を指定することができます。 後者の形式の方が好ましい形式です。各参照は、n 番目のキャプチャ用サブパターンにマッチしたテキストにより置換されます。 n は 0 から 99 までとすることができ、 \0 または $0 は パターン全体にマッチするテキストを参照します。キャプチャ用サブパターンの番号 については、その左括弧が左から右に(1から)カウントされます。 string に含まれるバックスラッシュリテラルは、エスケープが必須であることに注意して下さい。

  6. 試しに下記を実行してURL部分が検索にヒットするか確認してみた。

    echo preg_replace('/(https?:\/\/[^\s]*)/', '<a href="$1">$1</a>', 'https://qiita.com/miriwo は筆者のQiitaのホームです');
    
  7. 下記のように出力されたので問題無く置換できているようである。

    <a href="https://qiita.com/miriwo">https://qiita.com/miriwo</a> は筆者のQiitaのホームです
    
  8. ヒットした文字列が格納されているキーがJavaScriptのものと同じであるのか確認してみる。下記を正規表現部分と置換後の文字列をいじって下記を実行してみる。

    echo preg_replace('/(https?)(:\/\/[^\s]*)()/', '$0 - $1 - $2 - $3  ', 'https://qiita.com/miriwo は筆者のQiitaのホームです');
    
  9. 下記のように出力された。1つ目のハイフンまでが$0であり、すべての正規表現にヒットしたのでURLがフルで表示されている。1つ目から2つ目のハイフンまでが$1であり1つ目の括弧のヒット部分が表示されている。2つ目から3つ目のハイフンまでが$2であり2つ目の括弧のヒット部分が表示されている。最後のハイフンのあとは$3であり3つ目の括弧のヒット部分が表示されるが括弧の中に何も記載されておらず何もヒットしていないので何も出力されていない。

    https://qiita.com/miriwo - https - ://qiita.com/miriwo -    は筆者のQiitaのホームです
    

まとめ

  • JavaScriptでもPHPでも正規表現を使った文字列置換の$1とか$2とかは左から数えた正規表現の括弧の数にリンクしており、それぞれ$1には1つ目の括弧の正規表現にヒットした文字列が、$2には2つ目の括弧の正規表現にヒットした文字列が格納れている。$0だけは少し特殊で括弧に関係なくデリミタで区切られた正規表現すべてを通してヒットした文字列が格納される。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む