20201104のJavaScriptに関する記事は25件です。

そして TypeScript へ

JavaScriptからTypeScriptへ

JavaScript(以下JS)を学んでいたら、その進化版としてTypeScript(以下TS)があることを知った。
そこで学んだことをまとめていく。

TypeScriptとは?

JSをベースに拡張して作られた言語。違いは、JSは動的型付け で TSは静的型付 となり、大規模開発に耐えられる使用となっている。JSを拡張した言語のため、JSの構文を使うことができる。

JSでの大規模開発では、チームで作成するのでいつの間にか意図した型が変数に入らずにエラーが起きることもあり、これに気付けるのは実行した時にエラーが出た時である。一方TSは型を定義するので実行前にエラーに気づくことができ、コード製作者の意図もわかりやすく可読性も上がる。
ただ、TSだけではブラウザで直接実行できないので、JSに変換する必要がある。

型の指定

まず大切な型の指定についてまとめる。明示的に変数やclassなどに型を指定することで、間違った値が入ることがなくなり、製作者の意図を理解しやすくしたりすることができる。

書き方

class Name {
 firstName: string;
 lastName: string;

 constructor(firstName: string, lastName: string){
  this.firstName = firstName;
  this.lastName = lastName;
 }
}

上のように、変数名の横に 「: 型の種類」 とつけて、変数の型を指定する。

型の種類

よく使いそうな型をとりあえずまとめる

number型、string型、boolean型

よく見る形なのでわかると思うが、numberは数値、stringは文字列、booleanはtrue/falseを意味している

Object型

 オブジェクトの形を型として定義する。

const name: {
 firstName: string,
 lastName: string
} = {
 firstName: 'test',
 nastName: 'test'
}

何もかも型を定義しないといけないの?
TSには型推論と言うTS側が自動で型を予想して適応してくれる機能がある。このおかげで全ての変数などが出てきた時に、型を指定する必要はない。このおかげで冗長になることもない。上のオブジェクト型でも明示的に型を定義しなくても推論してくれる。また、変数に値を代入したときは、推論してくれる。

Array型

 配列を意味している。書き方は 「: string[]」 のように型の種類 + [] と書く。注意点は配列内の値全てが、型の種類に固定される。

Tuple型

TSで導入された型。配列の要素数が固定の時に使う。

 const name: [string, number] = ['test', 30]

Enum型

 TSで導入された型。定数のリスト。配列のように0〜の番号を自動で適応される。

 enum Name { Satou, Katou, Yamada = 100}

 Name.Satou

enum型で定義しておけば、TSの入力補助機能で選ぶことができ、いちいち定数名を覚える必要もなくなって、使いやすい。
それぞれの文字に対する数値は自動的に適応すると0〜になるが、Yamadaのように数値を指定することもできる。文字を定義することもできる。

any型

 anyは型を指定せず、どんな値も入ることができる。しかし、TSは型定義をすることに意味があるので、このany型は使用しない方がいい。

もし、値が代入される型が複数あるかもしれないときは?

Union型

型指定の種類が複数の可能性がある場合の書き方

 const name: string | number ;

「 | 」で複数の型をつなげることで、そのどれでも型が適応することができる。

Literal型

 今までは、もともとTSが持っていた型を使用していたが、文字を指定することもできる。

 const name: 'satou' | 'katou';

これは、'satou'か'katuo'の値しかとることができない。
例えば、'finish'|'unfinish'のようにすれば、完了・未完了の定義ができ、それをもとにif文で条件を分けることができる。

エイリアス型

Literal型やUnion型を何度も書くのは面倒だし、修正も大変となる。それを解決してくれるのがエイリアス型となる。型の定義を変数のようにまとめられる。

 type Test = number | string;

typeはTSで導入されたものである。

function型(void型)

 変数に関数を入れたい時に指定する。戻り値の型も指定できる。

 function test(n1: number, n2: number): number {何かしらの処理} //戻り値の型指定

 let plus: Function; // let plus: (a:number, b:number) => number;
 plus = test;

functionで定義したときの()後ろに戻り値の型を指定できる。returnが無い場合は「: void」を指定できる。
また、Function型はfunctionを代入すると言うことを型定義してる。しかし、ここで問題が出てしまう。どんな形のfunctionが代入できてしまう。そこでアロー関数を使った書き方(コメントの中)で引数と戻り値の型を指定することで、代入したい目的の形の関数を代入するように、指定を厳しくすることができる。

型をどんどん指定するので長く見えるかもしれないが、エラーに気付きやすくなるので、今後使っていきたい。

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

Ajax通信でコメントを削除する

Ajaxを用いたコメント削除機能を実装するにあたり詰まった点がいくつかあったので、記録として残しておきます。
実装イメージは以下の通りです。
ForGif.gif

詰まった点は以下の2点です。
1.ネストされているコメントに対する削除方法(引数をどのように渡せばいいか)
2.js.erbファイルの記述方法

この2点に対する解決方法を記述をします。(解決までに自分が行ったことも書いていくため、長くなります。)

環境は以下のとおりです。
ruby 2.6.5
Ruby on Rails 6.0.3.3

1.ネストされているコメントに対する削除方法(引数をどのように渡せばいいか)

まずは同期通信で削除ができるように実装を進めていきましたが、いきなり躓きました。ネストされている機能(今回の場合はコメントに関する機能)を削除したことがなかったからです。
とりあえず引数どうすればよいのかと考え以下の様にしました。
①コメントのidを渡す
単純にコメントのidを渡してどのような動きになるのか確認をしました。

  <% @comments.reverse.each do |comment| %>
    <div class="comment">
        <%=link_to image_tag("delete.png", class: "delete-btn"), laundry_comment_path(comment.id), method: :delete %>
      (略)
    </div>

結果
ecf4483bf2a896dc759a44d5c6266805.png

id=11のコメントなどないと怒られました。実際データベースを確認したところid=11のコメントはありませんでした。
不思議に思いつつbinding.pryで確認してみたところ何故か店舗のidとコメントのidが逆に渡されていました。098dc402f419712643eb7007203598a1.png
(本来はlaundry_idが11でid(コメントのid)が123でないといけません。)

何故このようになってしまったのかはわかりませんがとりあえず違うということで別の方法を考えます。
※結果として今でも分からずにいます。わかる方いましたらご教授ください。

②インスタンス変数を渡す
ネットで検索をしていたところ以下のQiita記事を発見しました。
[Rails]ネストしたコメントの削除機能の作成
まさに私が探し求めていたものだ!ということで早速実践。

 <%=link_to image_tag("delete.png", class: "delete-btn"), laundry_comment_path(@laundry,comment.id), method: :delete %>

引数を@laundry,commentの2つにしました。(@laundryはコントローラーで定義しています。)
実行してみましたがエラーがでてうまくいきませんでした。(そもそもブロック変数を用いる式の中でインスタンス変数を用いることってあるのか?という疑問が残りました)
ここでネストをしている時は2つ引数を渡す必要があると気づきパスを確認してみたところ/laundries/:laundry_id/comments/:idというパスになっていました。
普段も:idの部分がわかるように引数を渡しており、今回もlaundry_idとコメントのidが渡せればいいためブロック変数のcommentからlaundry_idを渡すために以下のように引数を指定して解決しました。

解決策

<%=link_to image_tag("delete.png", class: "delete-btn"), laundry_comment_path(comment.laundry_id, comment.id), method: :delete  %>

ネストがあったとしても基本は同じで、どこのパスにいくのか、そのためにはどの引数が必要なのかを判断する必要があると学びました。

2.js.erbファイルの記述方法

同期通信が実装できたため、次は非同期の実装に取り掛かります。
基本的にはlink_toに対してremote: trueのオプションをつけることで非同期通信が実行されてhoge.js.erbファイル(今回はdestroy.js.html)を探してレンダリングするという流れですが、destroy.js.html.js.erbの記述でまた躓きました。
そもそもjs.erbファイルを記述したことがなかったためjs.erbファイルとは?というところから始まったのですが要点をまとめると以下のような感じになるかと思います。

・js(ajax)リクエストが行わたときにレンダリングされるファイル。
・Javascriptを用いて記述ができる。
・ERBの記法(<%= >や<% >)が使用できる

Javascriptが使えるのか〜と思いながら削除機能に関するjs.erbファイルの書き方等を調べていると全部と言ってもいいぐらいネットの記事ではjQueryで書かれていました。
そこまで記述量多くなさそうだし素のJavascriptでやってみようと思い、Javascriptで記述をすることにしました。

const commentArea = document.getElementById('comment-area');
commentArea.innerHTML = "<%= render partial: 'index', locals: { comments: @comments } %>";

とりあえず必要な要素を取得してそこにrenderで部分テンプレートを埋め込めばOKそうだったのでこのように記述して実行をしてみました。

結果
Uncaught SyntaxError: Unexpected identifierエラーが表示される。
該当箇所を確認すると以下のように表示されました。
4c8eda50ceeb08012ef5bfae938fcf51.png

エラー内容とエラー箇所からダブルクォーテーションの中身がダブルクォーテーションなので構文エラーになっているのでは?とご指摘いただき修正したのですが同じエラーが表示されてしまいました。

解決方法

`<%= render partial: 'index', locals: { comments: @comments } %>`;

バッククオートで囲むことで解決しました。

とりあえずできるようにはなったのですが、続いて1回しか削除ができない(読み込みをしないで別のコメントを削除しようとするとエラーが表示)という事象が発生しました。

const commentArea = document.getElementById('comment-area');
commentArea.innerHTML = `<%= render partial: 'index', locals: { comments: @comments } %>`;

Identifier 'commentArea' has already been declared
commentAreaは宣言されているよということで「定数だから1回しか宣言できないのか」ということでconst→letに変更

let commentArea = document.getElementById('comment-area');
commentArea.innerHTML = `<%= render partial: 'index', locals: { comments: @comments } %>`;

変更後実行してみましたが再度同じエラーが出現。
「定数やめたのになんでよ」と思いましたが確かにletを使うのは変数を定義するときだけで値を変更する時はlet使わないよな〜ということでletを削除

commentArea = document.getElementById('comment-area');
commentArea.innerHTML = `<%= render partial: 'index', locals: { comments: @comments } %>`;

・・・これ、もう2行に分ける必要ないじゃんということで1つにまとめてようやく実装できました。

document.getElementById('comment-area').innerHTML = `
<%= render partial: 'index', locals: { comments: @comments } %>`;

すごく遠回りをした気がしますが、なんとが実装ができて一安心という感じです。
ですがとりあえずできた段階で理解には全然達していないため、理解を深めていきます!

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

ReactDatepickerで週の開始日を任意の曜日にする方法

早速サンプルコード

import ja from 'date-fns/locale/ja';
import React, { ReactElement } from 'react';
import DatePicker from 'react-datepicker';

// Locale Classのインスタンスではないが、Typescriptのコンパイルエラーを潰すために強引に
// インスタンスをちゃんと作る方べきかもだが今回はasで簡単に回避
const sundayStartJa = { ...ja, options: { ...ja.options, weekStartsOn: 0 } } as Locale; 

export const Datepicker = (): ReactElement => 
  <DatePicker locale={sundayStartJa} onChange={console.log} />;

上記の前提は

  • ロケールはja (日本)

    • jaにすると、週の開始日が月曜日になる
  • これを日曜日開始に変更する

    • weekStartsOn: 0が日曜なので任意の曜日を指定する

ドキュメントにも見当たらず、だいぶ無理やり実装した感があるが想定どおりの挙動をしている
ja.option = {//略}などでも動いたが、怖いのでコピーを作成

React Datepicker
https://reactdatepicker.com/
https://github.com/Hacker0x01/react-datepicker#localization

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

privateの想像と違う(?)動作のお話

初書:2020/11/4
php:7.4.10

前書き

オブジェクトを使う際にほぼ間違いなく使うprivate。
今回は(個人的に)意外な性質を見つけたのでメモ。

コード

先にコードを提示

class tes{
    private int $a = 1232;
    private function fc():int{
        return $this->a;
    }
    public function func(tes $val) : int
    {
        return $val->fc() + 158;
    }
}

$a = new tes();
$b = new tes();
var_dump($b->func($a));

数字・関数名変数名は適当。
いや今気がついたけどこのコードだとfcの定義している意味が完全に無いじゃん。
まぁ支障はないので一旦放置。

簡単にコードの説明をしておくと、private変数$aを用意しておき、fc関数で取得可能。
またfunc関数では、tesクラスのインスタンスを引数とし、fc関数と158を足した値を戻す。
そして2つのインスタンスを生成し、片方のインスタンスを引数にもう片方のfuncを呼ぶというもの。

さて、上記コードを実行してみるとどうなるか。

php > var_dump($b->func($a));
int(1390)

普通に実行できる。

何が言いたいのか

つまり、privateな関数や変数1であっても、同じクラス内から呼び出す分には別のインスタンスであっても呼び出すことが可能である。(初知り)

javascriptでも試してみる。

class tes{
    #a = 1232;
    #fc(){
        return this.#a;
    }

    func(val){
        return val.#fc() + 158;
    }
}

a = new tes();
b = new tes();
console.log(b.func(a));

(ちなみに#から始まるのをフラグメント演算子というらしいのだが、いつから実装されてるのか不明。2

さて、結果はどうなったのか。ちなみにページを用意するのが面倒だったので、コンソールに投げて実行した。

safari

ver 14.0

SyntaxError: Invalid character: '#'

そもそもフラグメント演算子に対応していない

chrome

ver 86.0

1390

phpと同様の結果に。

Firefox

ver 82.0

Uncaught SyntaxError: private fields are not currently supported

認識はするものの対応自体はしていない模様

結果

phpと同じと認識してもいいと思われる

C#でも試す。

C#というよりunity。

using UnityEngine;

public class test2 : MonoBehaviour{
    void Start(){
        var a = new tes();
        var b = new tes();
        print(b.func(a));
    }
}
class tes{
    private int a = 1232;
    private int fc(){
        return a;
    }
    public int func(tes val){
        return val.fc() + 158;
    }
}

普通に1390と出た。

結論

もしかして単純に無知を晒した投稿なのでは?(今更。でも書いてしまった)
「privateは1カプセル毎に個別で持つもの、外部からは一切アクセスできない」という認識があったので少し意外な処理だった。


  1. 上記コードでは出てこないが、$val->fc()$val->aに変えても実行できる 

  2. ここのサイトにて、node.jsのv12からという記述は確認。 

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

実家の会社の日報表をwebアプリ化した。(スマホ用)

はじめに

実家の会社が未だに紙ベースでやりとりをしているので、実際に使ってほしいという気持ちを持ちつつ作ってみました。(スマホ用)

概要と背景

  • 営業マンがその日行った営業を日報表にまとめてその日の終わりに提出する。
  • 紙面でのやりとりなので、事前に報告ができない、過去の営業記録をいちいち紙で探す必要がある、など不便なことが多い。
  • 日報表の項目は、訪問先住所・訪問先氏名・会見者・訪問結果・内容・日付の6項目。
  • 社員の管理は社員用テーブルで管理し、訪問履歴はセールス記録テーブルで管理する。
  • 訪問履歴は社員に個別に与えられた社員番号で個別に管理する。

開発環境

  • サーバーサイド
    • java
    • java EE
  • フロントサイド
    • javascript
    • html
    • css
  • データベース
    • MySQL
  • その他
    • bootstrap

基本機能

  • ログイン機能
    • 社員名と社員ごとに与えられたパスワードで管理
    • セッションによるログイン管理
  • ログアウト機能
    • セッションを削除
  • 日報表の入力・送信機能
    • 項目は概要の通り
    • 営業記録の入力の際に、社員番号を入力せずにセッション情報から社員番号を取得し、サーバーサイドで自動的に入力
  • 入力の際のチェック機能、送信の際の再確認機能
  • 入力した日報の一覧表示
    • 社員が自分の日報のみ見られる(他の社員の日報は見れない)ように設定
    • 今日、今週、今月、全て、から選ぶことが可能
  • 訪問履歴の検索機能
    • 訪問先氏名または郵便番号による検索が可能

できたもの

  • ログイン画面
    日報表ログイン.jpg

  • 日報入力画面
    日報表入力.jpg

  • 営業記録一覧を表示した例
    一覧は横スクロールで対応
    input.gif

  • 検索例
    input2.gif

工夫した点

  • 社員は結構年を取っている人が多いのでUIをなるべく大きめに設定した。
  • ajaxを使って郵便番号を入力すると該当する都道府県市区まで自動入力されるようにした。
  • 日報を入力し、送信を押したときに再確認するよう入力された情報をアラートで表示し、OKか確認するよう設計した。
  • 名前別の検索だけだと別の住所であっても重複する可能性があるため、より確実な郵便番号による検索を設けた。

作った感想

日報表をwebアプリ化してみて、紙でのやりとりより圧倒的に役立つと確信できた。(まだ使ってくれてない)

最後に

この日報表の他にも、紙でやりとりされていて、かつアプリ化できるものがたくさんあるので、これからアプリ化してどんどん実家の会社の業務効率をよくしたい。(使ってくれるかは分かりませんが。)

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

【Vue】テーブル操作エキスパートへの道。ボタンクリック列でを削除する方法

ボタンクリックで表から列を削除する方法について。

これまで、

v-forを使ったテーブルの作成
表で複数選択を可能にする方法
ボタンクリックで行を追加する方法
ボタンクリックで列を追加する方法
ボタンクリックで列を追加する方法

について確認したので、次のステップとして列を削除する方法を確認する。

実装イメージ

image.png

↓ 選択中のセルを削除する

image.png

考え方

  1. 現在選択中のセルの有無を判断
  2. 現在選択中のセルの列番号を取得する
  3. 表から選択中のセル以外を残す
  4. 選択を解除

実装

    removeColumn(){
      //選択中のセルがある場合のみ処理を実行
      if(this.currentCells.length != 0){

        //削除したいセル番号を抽出(重複削除)
        const cellIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxcellIndex )
        )

        //削除したい列を除外
        this.rows.map((row)=> 
          row.table_cells = (row.table_cells.filter((x, cellIndex)=>
            !cellIndicesToRemove.has(cellIndex)
          ))
        )

        //選択中のセルを解除
        this.currentCells = []
      }
    }


1. 現在選択中のセルの有無を判断

if(this.currentCells.length != 0)

現在選択中のセルの行列番号を格納する配列の要素の長さで判断する。


2. 現在選択中のセルの列番号を取得する

const cellIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxcellIndex )
        )

mapメソッドを使って、現在選択中の列番号を抽出する。
重複削除するため、Setオブジェクトに変換する。


3.表から選択中のセル以外を残す

        //削除したい列を除外
        this.rows.map((row)=> 
          row.table_cells = (row.table_cells.filter((x, cellIndex)=>
            !cellIndicesToRemove.has(cellIndex)
          ))
        )

filterとSetオブジェクトのhasメソッドを使って、現在選択中以外のセル番号のみを残す。

mapメソッドで行要素を一つづつ抜き出す。
filterメソッドで各行の列要素を一つづつ抜き出し、!とhasメソッドで番号が一致したセルを除外していく。

filterは非破壊のため、不要なセルを排除した行を元の行に代入する。


4. 選択を解除

      this.currentCells = []

現在選択中のセルを空にする。


フルコード

<template>
  <div>
    <p>〜TmpRemoveColumn.vue〜</p>
    <p>currentCells : {{currentCells}}</p>


    <p>
      <button
        @click="removeColumn"
      >列を削除
      </button>
    </p>

    <table>
      <template v-for="(tr, rowIndex) in rows">
        <tr :key="rowIndex">
          <template v-for="(cell, cellIndex) in tr.table_cells">
            <th :key="cellIndex" 
                v-if="cell.cell_type == 'TH'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              ( {{rowIndex}} , {{cellIndex}} )
            </th>

            <td :key="cellIndex" 
                v-else-if="cell.cell_type == 'TD'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              ( {{rowIndex}} , {{cellIndex}} )
            </td>
          </template>
        </tr>
      </template>
    </table>

  </div>
</template>

<script>
export default {
  data(){
    return{
      currentCells:[],
      rows: [
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
                {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
      ]
    }
  },
  methods:{
    //isActiveの判定
    //currentCellsの中にあればtrueにする
    //指定した行列番号の要素がある=数値が-1以外ならtrueにする。
    isActive(rowIndex, cellIndex){
      return this.currentCells.findIndex((elem) =>
        elem.xxxrowIndex == rowIndex && elem.xxxcellIndex == cellIndex
        ) > -1
    },

    clickCell(event){
      //クリックされたセルの情報
      const cell = event.target
      const tr = event.target.parentNode

      //クリックされたセルが既に選択されている場合は、配列から削除する
      if(this.isActive(tr.rowIndex, cell.cellIndex)){

        //選択中の配列の何番目の要素かを求める
        const rmIndex = this.currentCells.findIndex((elem)=>
          elem.xxxrowIndex == tr.rowIndex && elem.xxxcellIndex == cell.cellIndex 
        )

        //選択した要素を選択中の配列から削除する
        this.currentCells = [
          ...this.currentCells.slice(0, rmIndex),
          ...this.currentCells.slice(rmIndex + 1)
        ]

      } else{
        this.currentCells = [
          ...this.currentCells,
          {
            xxxrowIndex: tr.rowIndex,
            xxxcellIndex: cell.cellIndex
          }
        ]
      }
    },
    removeColumn(){
      //選択中のセルがある場合のみ処理を実行
      if(this.currentCells.length != 0){

        //削除したいセル番号を抽出(重複削除)
        const cellIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxcellIndex )
        )

        //削除したい列を除外
        this.rows.map((row)=> 
          row.table_cells = (row.table_cells.filter((x, cellIndex)=>
            !cellIndicesToRemove.has(cellIndex)
          ))
        )

        //選択中のセルを解除
        this.currentCells = []
      }
    }
  }
}
</script>

<style lang="scss" scoped>
table{
  width: 80%;

  th,td{
    border: thin solid rgba(0, 0, 0, 0.12);
    text-align: center;
    color: gray;
  }
  th{
    background: #ccc;
  }
  th, td{
    //選択状態
    &.is-active{
      border: 1px double #0098f7;
    }
  }
}
button{ 
   background: gray;
   padding: 5px 20px;
   color: white;
   border-radius: 50px;
}
input{
  margin: 7px;
  box-sizing: border-box;
}
</style>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】テーブル操作エキスパートへの道。ボタンクリックで行を削除する方法

ボタンクリックで表から行を削除する方法について。

これまで、

v-forを使ったテーブルの作成
表で複数選択を可能にする方法
ボタンクリックで行を追加する方法
ボタンクリックで列を追加する方法

について確認したので、次のステップとして行を削除する方法を確認する。

アウトプットイメージ

image.png

↓ 選択している行のみを削除

image.png

行削除の考え方

複数選択時は、選択した行をすべて削除する。
考え方は比較的シンプル

  • 選択中の行番号を抽出する(重複削除)
    • Set, mapメソッド
  • 抽出した行番号以外の表を作る
    • filter, hasメソッド

行削除実例

行削除のメソッド
removeRow(){
      //選択中のセルがある場合のみ処理を実行
      if(this.currentCells.length != 0){

        //削除したいセル番号を抽出(重複削除)
        const rowIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxrowIndex )
        )

        //削除したい列を除外(該当した行番号はスルー)
        this.rows = this.rows.filter((row, rowIndex)=>
          !rowIndicesToRemove.has(rowIndex)
        )

        //選択中のセルを解除
        this.currentCells = []
      }
    }


・選択中のセルがある場合のみ処理を実行

if(this.currentCells.length != 0)
現在選択中のセルがない場合は何もしない。

・選択中のセルの行番号を抜き出す

Setオブジェクトを使う。

Setオブジェクトとは、重複がない特別なオブジェクト。配列のように並び順もない。袋の中に個々の値が入っているイメージ。

const rowIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxrowIndex )
        )

new Set()でSetオブジェクトを生み出す。

this.currentCells.map(cell => cell.xxxrowIndex)
選択中のセルの行番号を一つづつ取得し配列にする。

この処理を実行することで下記のようなイメージになる。

Setオブジェクトの詳しい使い方はこちら

・指定した要素以外を抜き出す

        this.rows = this.rows.filter((row, rowIndex)=>
          !rowIndicesToRemove.has(rowIndex)
        )

filterメソッドはtrueになった場合のみ要素を残す。第二引数はインデックス番号になる。

rowIndicesToRemove.has(rowIndex)
現在選択中の行番号が入ったSetオブジェクトrowIndeicesToRemoveに対し、hasメソッドで一致する値があった場合にtrueを返す。

hasメソッドはSet専用のメソッド。指定した値があるとtrue,該当しない場合はfalseを返す。

ArrayオブジェクトのindexOfincludeメソッドと似た役割。

!をつけることで、一致しない場合のみtrueとすることで、残したい列のみを残す。


フルコード

<template>
  <div>
    <p>〜TmpRemoveRow.vue〜</p>
    <p>currentCells : {{currentCells}}</p>


    <p>
      <button
        @click="removeRow"
      >行を削除
      </button>
    </p>

    <table>
      <template v-for="(tr, rowIndex) in rows">
        <tr :key="rowIndex">
          <template v-for="(cell, cellIndex) in tr.table_cells">
            <th :key="cellIndex" 
                v-if="cell.cell_type == 'TH'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              ( {{rowIndex}} , {{cellIndex}} )
            </th>

            <td :key="cellIndex" 
                v-else-if="cell.cell_type == 'TD'"
                :class="{'is-active': isActive(rowIndex, cellIndex)}"
                @click="clickCell($event)">
              ( {{rowIndex}} , {{cellIndex}} )
            </td>
          </template>
        </tr>
      </template>
    </table>

  </div>
</template>

<script>
export default {
  data(){
    return{
      currentCells:[],
      rows: [
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
                {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
        {
          "table_cells": [
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
            {"cell_type": "TD"},
          ]
        },
      ]
    }
  },
  methods:{
    //isActiveの判定
    //currentCellsの中にあればtrueにする
    //指定した行列番号の要素がある=数値が-1以外ならtrueにする。
    isActive(rowIndex, cellIndex){
      return this.currentCells.findIndex((elem) =>
        elem.xxxrowIndex == rowIndex && elem.xxxcellIndex == cellIndex
        ) > -1
    },

    clickCell(event){
      //クリックされたセルの情報
      const cell = event.target
      const tr = event.target.parentNode

      //クリックされたセルが既に選択されている場合は、配列から削除する
      if(this.isActive(tr.rowIndex, cell.cellIndex)){

        //選択中の配列の何番目の要素かを求める
        const rmIndex = this.currentCells.findIndex((elem)=>
          elem.xxxrowIndex == tr.rowIndex && elem.xxxcellIndex == cell.cellIndex 
        )

        //選択した要素を選択中の配列から削除する
        this.currentCells = [
          ...this.currentCells.slice(0, rmIndex),
          ...this.currentCells.slice(rmIndex + 1)
        ]

      } else{
        this.currentCells = [
          ...this.currentCells,
          {
            xxxrowIndex: tr.rowIndex,
            xxxcellIndex: cell.cellIndex
          }
        ]
      }
    },
    removeRow(){
      //選択中のセルがある場合のみ処理を実行
      if(this.currentCells.length != 0){

        //削除したいセル番号を抽出(重複削除)
        const rowIndicesToRemove = new Set(
          this.currentCells.map(cell => cell.xxxrowIndex )
        )

        //削除したい列を除外(該当した行番号はスルー)
        this.rows = this.rows.filter((row, rowIndex)=>
          !rowIndicesToRemove.has(rowIndex)
        )

        //選択中のセルを解除
        this.currentCells = []
      }
    }
  }
}
</script>

<style lang="scss" scoped>
table{
  width: 80%;

  th,td{
    border: thin solid rgba(0, 0, 0, 0.12);
    text-align: center;
    color: gray;
  }
  th{
    background: #ccc;
  }
  th, td{
    //選択状態
    &.is-active{
      border: 1px double #0098f7;
    }
  }
}
button{ 
   background: gray;
   padding: 5px 20px;
   color: white;
   border-radius: 50px;
}
input{
  margin: 7px;
  box-sizing: border-box;
}
</style>

コード自体はすごく簡単。Set, has, map, filterを使えるかが鍵となる。

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

Photoshopスポイトツールオプション(範囲・対象)をキーボードショートカットで設定する

レタッチ・色調補正で忘れてはいけないスポイトツールの設定

が、Photoshopではアクセスし辛いところにあります。

2030961048024.jpg

この設定内容、特にサンプル範囲の指定値は、
* ブラシでのOPTION(Alt)+クリックでの色拾い
* ターゲット調整ツール
* カラーサンプラー
などなどの、サンプル範囲を指定する全てのツールで共有しています。
画像の状態によって適宜変更しないとちょっと面倒なんですが、まぁ、面倒くさいところにあるんですね…
これをキーボードショートカットで変更できないかと思っていたら、
『Solved: Setting eyedropper options - Adobe Support Community - 10115167』https://community.adobe.com/t5/photoshop/setting-eyedropper-options/td-p/10115167?page=1&profile.language=en
Adobeフォーラムのr-bin氏がそのものズバリなScriptをアップしていました。
これをお借りして、KeyboardMaestroでちょうどいいキーに割り当てて使ってみましょう。
KMマクロのメインは、AppleScriptで包んだJavaScriptです。包んでるので変数もKMから渡せますね!

(*
This AppleScript uses the script of r-bin. Thanks to r-bin
https://community.adobe.com/t5/photoshop/setting-eyedropper-options/td-p/10115167
r-bin(https://community.adobe.com/t5/user/viewprofilepage/user-id/7619033)
*)

(*
eyeDropperSample(サンプルサイズ)     0:指定/1:3/2:5/5:11/15:31/25:51/50:101
eyeDropperSampleSheet(サンプル対象)   0:全て 1:現在 3:現在以下 6:全て調整なし 8:現在以下調整なし
*)

use Photoshop : application id "com.adobe.photoshop"
tell application "Keyboard Maestro Engine" to set mySampleSize to getvariable "SampleSize" as text

tell Photoshop
    do javascript ("var r = new ActionReference();
r.putProperty(stringIDToTypeID(\"property\"), stringIDToTypeID(\"tool\"));
r.putEnumerated(stringIDToTypeID(\"application\"), stringIDToTypeID(\"ordinal\"), stringIDToTypeID(\"targetEnum\"));
var curr_tool = executeActionGet(r).getEnumerationType(stringIDToTypeID(\"tool\"));

var r = new ActionReference();
r.putClass(stringIDToTypeID(\"eyedropperTool\"));
var d = new ActionDescriptor();
d.putReference(stringIDToTypeID(\"null\"), r);
executeAction(stringIDToTypeID(\"select\"), d, DialogModes.NO);

var r = new ActionReference();
r.putProperty(stringIDToTypeID(\"property\"), stringIDToTypeID(\"tool\"));
r.putEnumerated(stringIDToTypeID(\"application\"), stringIDToTypeID(\"ordinal\"), stringIDToTypeID(\"targetEnum\"));
var op = executeActionGet(r).getObjectValue(stringIDToTypeID(\"currentToolOptions\"));
op.putInteger(stringIDToTypeID(\"eyeDropperSample\"), " & mySampleSize & ");
var r = new ActionReference();
r.putClass(stringIDToTypeID(\"eyedropperTool\"));
var d = new ActionDescriptor();
d.putReference( stringIDToTypeID( \"null\" ), r );
d.putObject(stringIDToTypeID(\"to\"), stringIDToTypeID(\"null\"), op);
executeAction(stringIDToTypeID(\"set\"), d, DialogModes.NO);

var r = new ActionReference();
r.putClass(curr_tool);
var d = new ActionDescriptor();
d.putReference(stringIDToTypeID(\"null\"), r);
executeAction(stringIDToTypeID(\"select\"), d, DialogModes.NO);")
end tell

option+1〜7で値を渡してサンプル範囲変更、option+0,.で対象レイヤー変更を仕込みます。便利べんり。

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

チャットアプリを作った。

はじめに

webアプリの練習としてチャットアプリを作ってみました。

基本機能

  • ログイン制のアプリ
    • パスワードはハッシュ化してDBで管理
  • セッションを使ったログイン管理機能
  • アカウント作成機能
  • メッセージ投稿機能
    • 投稿内容をDBに保存
    • XSS対策のエスケープ処理
  • 画像投稿機能
    • 画像をbyte配列に変換し保存し軽量化
  • 入力チェック機能
    • ログイン時
    • アカウント作成時
    • メッセージ投稿時
  • メッセージ削除・編集機能(ユーザー視点から、DBに保存してある個人のメッセージを個別に削除・編集が可能)
  • Bootstrapフレームワークを使ったアプリデザイン
  • スマホに対応したレスポンシブデザイン

開発環境

  • 使用言語
    • Java
    • javascript
    • html
    • css
  • データベース
    • MySQL
  • その他
    • bootstrap

画面

ログイン画面

  • PC用 roguinngamen.png
  • スマホ用
  • スマホの画面.jpg

チャット画面

  • PC用
    チャット画面.png

  • スマホ用
    tyattogamen.png

作った感想

  • 普通のチャットアプリでもいざ作ってみると面倒なことが多い。
    • 改行を判定するための処理とXSS対策の両立
  • 画像を投稿する際にbyte列に変換したほうが良いということを学んだ。表示する際にはbase64にエンコードして表示する方法を選んだが、この方法はページを表示する際に負荷がかかるため、今後の反省点にしたい。
  • デザインには自信がないため、bootstrapのおかげでだいぶマシになった。

最後に

チャットアプリといえど、その処理や構造はほぼほぼ掲示板や、他の入力フォームを伴うアプリと同じあるいは似ているので、他のアプリでも流用が効くことがわかった。

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

unicode正規表現サンプル

unicode正規表現サンプル

毎回調べるのが面倒なので、自分が使いそうなunicode正規表現を記事にしておく...

概要 unicode正規表現
漢字ひらがな \u30a0-\u30ff\u3040-\u309f\u3005-\u3006\u30e0-\u9fcf
全角スペース \u3000
全角アルファベット大文字 \uFF41-\uFF5A
全角アルファベット小文字 \uFF21-\uFF3A
全角数字 \uFF10-\uFF19

参考

https://www.tamasoft.co.jp/ja/general-info/unicode.html

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

Nuxt.jsでAnimate.cssをcdn経由で使う方法

はじめに

Nuxt.js に Animate.css を導入するための方法です。
すでに Nuxt.js の環境構築などを終えられている方が対象です。

他にも、cdn を使って何かを読み込ませたい人のプラスになるかもしれません。

早速本題

  • head タグの中にいろいろ書き込みたいので、head メソッドを使います。
import Loading from "@/components/Loading"; //追加

  export default {
// 省略
    head() {
      return {
        link: [{
          rel: "stylesheet",
          href: "https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
        }]
      }
    }

これだけです。
default.vueファイルなどに以上の記述をしていただければ、class 名のところに Animate.css の記述を書くだけでいろんな動きをしてくれます。
例)class = "animate__animated animate__slideInDown"

Animate.css に限らず、他の外部リソースを使う時にも応用できるかと思います。
自分がやろうとした時に、以下の参考記事にたどり着くまでに時間がかかったので、まとめてみました。

参考

NUXT JS 外部リソースを使うには?

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

Google maps APIで東京駅を表示する方法

事前準備(APIキー取得)

Google maps APIを利用するには、まずAPIキーを取得しなければいけないです。
取得の方法については以下の通りです。

*Google Maps APIコンソールでAPIキーを作成する為に、Googleアカウントが必要なので、事前に準備の必要があります。

Googleアカウントの作成

まず、Googleアカウントでログインします。

プロジェクトの作成

My Projectをクリックします。

新しいプロジェクトをクリックします。

「プロジェクト名」を入力して作成ボタンをクリックします。

作成したプロジェクトを確認して、「APIとサービズ」→「ダッシュボード」の順番でクリックします。

APIの有効化

例:Maps JavaScript APIを有効化にしましょう!

APIキーの取得

東京駅を表示する

html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample_GoogleMap</title>
  <script src="http://maps.google.com/maps/api/js?key={}&language=ja"></script>
  <style>
    html { height: 100% }
    body { height: 100% }
    #map { height: 100%; width: 100%}
  </style>
</head>
<body>
  <div id="map"></div>
  <script>
    var MyLatLng = new google.maps.LatLng(35.6811673, 139.7670516);
    var Options = {  
     zoom: 15, 
     center: MyLatLng,
     mapTypeId: 'roadmap'   
    };
    var map = new google.maps.Map(document.getElementById('map'), Options);
    var marker = new google.maps.Marker({
      position: map.getCenter(),
      map: map,

    });
  </script>
</body>
</html>

東京駅のマーカーを変更する

html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Sample_GoogleMap</title>
<script src="http://maps.google.com/maps/api/js?key={}&language=ja"></script>
<style>
html { height: 100% }
body { height: 100% }
#map { height: 100%; width: 100%}
</style>
</head>
<body>
<div id="map"></div>
<script>
var MyLatLng = new google.maps.LatLng(35.6811673, 139.7670516);
var Options = {  
 zoom: 15, 
 center: MyLatLng,
 mapTypeId: 'roadmap'   
};
var map = new google.maps.Map(document.getElementById('map'), Options);
var marker = new google.maps.Marker({
  position: map.getCenter(),
  map: map,

 // アイコンの画像を表示する。
 icon: {
      url: "https://maps.google.com/mapfiles/ms/micons/yellow.png",
      scaledSize: new google.maps.Size(60, 60)
    }
});
</script>
</body>
</html>

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

いまさらの schema.org > saleEvent 編

今回もschema.org を見ていきますが、前回の記事、「[霊園ガイドサイト開発日記 いまさらの schema.org 書いてみた。]」(https://qiita.com/toshirot/items/b02c3eae35297f11e1fa) で扱った パンくずリスト BreadcrumbListタイプに引き続き、セールイベント saleEvent をみてみます。

あいかわらずですが、長年勤めた牛久大仏・牛久浄苑を卒業して、新しい会社で「CTOから戸締り役まで」なんでもこなしはじめた高橋登史朗@元牛久大仏 https://github.com/toshirotによる、「霊園ガイド https://reien.top/」のサイト開発日記のていで書いていきますので、そこは生活もかかっておりますのでどうかご了承ください(^^;;、

schema.orgのイベント Event タイプはサイト巡回していると良く見かけるのですが、想定されているのはコンサートやスポーツなどのイベントで、いわゆる特売サービス期間などのようなセールス関連のイベントには向いていません。

そこで今回のセールイベント saleEvent ですが「霊園ガイド 」では、丁度11月末までに工事契約するとなんとAmazonギフト券1万円分進呈しちゃうっ!という太っ腹なセールスイベントを実施中なので、さっそく実装してみました。

では、まず今日も先にコードから入ります。

コード

index.html
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>お墓の【霊園ガイド】</title>
    <script type="application/ld+json">
    {
        "@context" : "http://schema.org",
        "@type" : "saleEvent",
        "name" : "秋のキャンペーン",
        "startDate" : "2020-11-01",
        "endDate": "2020-11-30",
        "eventAttendanceMode": "https://schema.org/OnlineEventAttendanceMode",
        "eventStatus": "https://schema.org/EventScheduled",
        "location" : {
            "@type": "VirtualLocation",
            "url": "https://reien.top/"
        },
        "image": [
            "https://reien.top/img/300x300.png"
       ],
       "description": "ギフト券1万円分進呈!(紹介先の工事契約+アンケートで2020年11月末まで)",
       "offers": {
            "@type": "Offer",
            "url": "https://reien.top/campaign/2020-09.html",
            "price": "10000",
            "priceCurrency": "JPY",
            "availability": "https://schema.org/InStock",
            "validFrom": "2020-11-01"
       },
       "performer": {
            "@type": "PerformingGroup",
            "name": "(株)六月書房neo"
       },
       "organizer": {
            "@type": "Organization",
            "name": "(株)六月書房neo",
            "url": "https://reien.top/"
       }
    }
    </script>

前回と同様に「type="application/ld+json"」のSCRIPT要素で囲まれた中にJSON-LD形式で書かれています。

index.html
 <script type="application/ld+json">
   :
  //ここにコード
   :
 </script>

前回よりもプロパティ数が多いのですが順番に見ていきます。

1階層目 (type:saleEvent)

前回同様にコードはJSONで書かれており、中カッコ「{」から「}」で囲まれたオブジェクトです。最初の階層は前回は3つだけでしたが、今回は結構あります。

  1. "@context": "https://schema.org" を指定してこの定義がschema.orgに準拠していることを宣言します。これは前回と同じ。
  2. "@type": "saleEvent" 前回個々はBreadcrumbListでパンくずリストであることを宣言しましたが、今回はsaleEventです。
  3. "startDate" : "2020-11-01" でイベントの開始日
  4. "endDate": "2020-11-30" はイベントの終了日
  5. "eventAttendanceMode": "https://schema.org/OnlineEventAttendanceMode" はオンラインイベントを意味します。他にオフラインOfflineEventAttendanceModeとミックスMixedEventAttendanceModeがあります。
  6. "eventStatus": "https://schema.org/EventScheduled"はイベントが変更なしですが他に、次のような キャンセルや変更についてのステータスも選べます EventCancelled、EventMovedOnline、EventPostponed、EventRescheduled
  7. "location"は、イベントが発生している場所、組織が配置されている場所、またはアクションが発生している場所です。ここでは url を https://reien.top/にしておきました。
  8. "image" はイベントに関する画像です。
  9. "description"はイベントについての説明
  10. "offers"葉提供内容ですここにセール価格などが書けます。
  11. "performer" は普通のイベントだと歌手名だったりしますが、ここはセールを提供するプレゼンター。
  12. "organizer"はイベントの主催者。

ざっとこういう感じです。これらを@type Eventのプロパティで書こうとしてもうまくいきません。

このほかにもたくさんのプロパティがあるのでもし興味をもたれたら schema.org > Event > saleEvent で確認してみてください。

とはいえ、私もこのtypeは初めて使ったので間違えがあるかもしれません。。私の使い方が間違ってるなどのつっこみは歓迎します。

上記のコード自体は「霊園ガイド https://reien.top/」のサイトのトップページのソースを開くとみることができます。

テスト

そしてこのコードをGoogleのリッチリザルト テスト にかけると下記のような結果になります。

image.png

参考

schema.org
https://schema.org/
github.com/schemaorg
https://github.com/schemaorg/schemaorg
SCHEMA.ORG COMMUNITY GROUP
https://www.w3.org/community/schemaorg/
schema.org > Event > saleEvent
https://schema.org/SaleEvent

JSON-LD形式 JSON for Linking Data
https://json-ld.org/
W3C JSON-LD Working Group
https://www.w3.org/2018/json-ld-wg/

Microdata
https://www.w3.org/TR/microdata/

RDFa 1.1 Primer - Third Edition
https://www.w3.org/TR/rdfa-primer/

リッチリザルト テスト
https://search.google.com/test/rich-results

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

new 演算子で生成したインスタンスのオブジェクト名取得

インスタンス.constructor.nameでオブジェクト名を参照できる。

function Foo() {};
let foo = new Foo();

console.log(foo.constructor.name);
//=> 'Foo'

参考
Function.name - JavaScript | MDN
new 演算子 - JavaScript | MDN

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

JavaScript 特定桁の0と1で表すことができる全ての組み合わせを求める関数

やりたいこと

例: 引数1に3を渡すと3ビットで表せれるすべての組み合わせを出力する。
たまに使用するので、個人的なメモ(誰かの役に立つと嬉しい)

出力例

[
  [ '0', '0', '0' ],
  [ '0', '0', '1' ],
  [ '0', '1', '0' ],
  [ '0', '1', '1' ],
  [ '1', '0', '0' ],
  [ '1', '0', '1' ],
  [ '1', '1', '0' ],
  [ '1', '1', '1' ]
]

コード

const getAllBit = (len) => {
  //すべての組み合わせの個数
  const end = Math.pow(2, len) - 1

  let result = []
  for (let i = 0; i <= end; i++) {
    //len分だけ0うめ
    const bit = toBinary(i).toString().padStart(len, '0')
    result.push(bit.split(''))
  }

  return result
}

const toBinary = (n) => parseInt(n.toString(2))

console.log(getAllBit(5))

実行例

引数に5を渡した場合(上記のコードと同じ)

[
  [ '0', '0', '0', '0', '0' ],
  [ '0', '0', '0', '0', '1' ],
  [ '0', '0', '0', '1', '0' ],
  [ '0', '0', '0', '1', '1' ],
  [ '0', '0', '1', '0', '0' ],
  [ '0', '0', '1', '0', '1' ],
  [ '0', '0', '1', '1', '0' ],
  [ '0', '0', '1', '1', '1' ],
  [ '0', '1', '0', '0', '0' ],
  [ '0', '1', '0', '0', '1' ],
  [ '0', '1', '0', '1', '0' ],
  [ '0', '1', '0', '1', '1' ],
  [ '0', '1', '1', '0', '0' ],
  [ '0', '1', '1', '0', '1' ],
  [ '0', '1', '1', '1', '0' ],
  [ '0', '1', '1', '1', '1' ],
  [ '1', '0', '0', '0', '0' ],
  [ '1', '0', '0', '0', '1' ],
  [ '1', '0', '0', '1', '0' ],
  [ '1', '0', '0', '1', '1' ],
  [ '1', '0', '1', '0', '0' ],
  [ '1', '0', '1', '0', '1' ],
  [ '1', '0', '1', '1', '0' ],
  [ '1', '0', '1', '1', '1' ],
  [ '1', '1', '0', '0', '0' ],
  [ '1', '1', '0', '0', '1' ],
  [ '1', '1', '0', '1', '0' ],
  [ '1', '1', '0', '1', '1' ],
  [ '1', '1', '1', '0', '0' ],
  [ '1', '1', '1', '0', '1' ],
  [ '1', '1', '1', '1', '0' ],
  [ '1', '1', '1', '1', '1' ]
]

5ビットで表すことができるすべての組み合わせが出力できてます。

参考にした記事

js 与えられた整数を二進数で返す

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

【データ分析】Google Charts便利機能まとめ

対象者

  • グラフを使ってデータ分析したい
  • グラフのカスタマイズ機能やフィルター機能など目一杯使いたい
  • javascript 応用者

グーグルチャートについて

グーグルチャートについては、こちら の記事にまとめておりますのでご覧ください。
今回は、グーグルチャートには実はこういった便利機能がある!という紹介なので、
使い方について、こちらの記事では言及していません。
気になったものがあれば公式ドキュメントをご覧ください。

グーグルスプレッドシートを活用したグーグルチャートの使用方法

Google Spreadsheets

日本語訳

GoogleChartsとGoogleSpreadsheetsは緊密に統合されています。
GoogleチャートをGoogleスプレッドシート内に配置すると、GoogleチャートはGoogleスプレッドシートからデータを抽出できます。
このドキュメントは、両方を行う方法を示しています。
どちらの方法を選択しても、基になるスプレッドシートが変更されるたびにグラフが変更されます。

グーグルチャートの印刷方法

Printing PNG Charts

Google Chartsは、ブラウザから直接印刷することも、JavaScriptからprint()関数を介して印刷することもできます。チャートのPNG画像へのアクセスを提供する場合は、このgetImageURI()方法を使用できます 。

グラフの色や3Dグラフなどのグラフカスタマイズ方法

How to Customize Charts

チャートは通常、その視覚化に適したカスタムオプションをサポートしています。
たとえば、表グラフはsortColumnデフォルトの並べ替え列を指定するオプションをサポートし、円グラフの視覚化はcolorsスライスの色を指定できるオプションをサポートします。
各チャートのドキュメントには、サポートするオプションが記載されている必要があります。

y軸x軸のカスタマイズ方法について

Customizing Axes

軸を使用してグラフを作成する場合、それらのプロパティの一部をカスタマイズできます。

グラフの線をカスタマイズする方法

Customizing Lines

面積グラフ、線グラフ、コンボグラフなど、一部のGoogleグラフには、データポイントを結ぶ線があります。このページの手法を使用して、線の色、太さ、および破線をカスタマイズできます。

グラフの上に要素をつける方法(オーバーレイ)

Overlays

オーバーレイは、Googleのチャートの上に置かれた領域です。これは通常、特定の統計を呼び出すために使用されますが、HTMLとCSSだけなので、好きなものにすることができます。
簡単な使用法には、CSSクラスを作成し、それをHTMLで参照することが含まれます。JavaScriptは必要ありません。より高度な使用法には、GoogleChartsを使用してオーバーレイの位置とコンテンツをカスタマイズすることが含まれます。

グラフのポイントを変更する方法

Customizing Points

多くのGoogleグラフでは、データ値は正確なポイントに表示されます。折れ線グラフは、線で接続されたこれらのポイントのセットであり、散布図はポイントにすぎません。
散布図を除くすべてのグラフで、これらのポイントはデフォルトでゼロサイズになっています。pointSize オプションでサイズを制御でき、pointShape オプションで形状を制御できます。

ツールチップのカスタマイズ方法

Tooltips

ツールチップは、何かにカーソルを合わせるとポップアップする小さなボックスです。(ホバーカードはより一般的で、画面のどこにでも表示できます。ツールチップは、散布図のドットや棒グラフの棒など、常に何かに添付されます。)
このドキュメントでは、GoogleChartsでツールチップを作成およびカスタマイズする方法を学習します。

フィルター機能や他のグラフと連携させて動的にする方法

Controls and Dashboards

このページでは、複数のグラフをダッシュ​​ボードに組み合わせて、表示するデータを操作するためのコントロールをユーザーに提供する方法を説明します。

ダッシュボードは、同じ基になるデータを共有する複数のグラフをまとめて管理するための簡単な方法です。このページで説明されているAPIを使用することで、ダッシュボードの一部であるすべてのグラフを相互に接続して調整する負担から解放されます。

その他 ツールバー

Toolbar

ツールバー要素を任意のビジュアライゼーションに追加して、ユーザーが基になるデータをCSVファイルまたはHTMLテーブルにエクスポートできるようにしたり、ビジュアライゼーションを任意のWebページまたはガジェットに埋め込むためのコードを提供したりできます。

まとめ

グーグルチャートには、豊富なオプションと便利な機能がついていますね…。
使い方が難しいものもありますが、一度使いこなせるとデータ分析もしやすくなると思いますのでぜひ。

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

JavaScript 小数の誤差を出さない四則演算

桁ごとに分けた10進数で計算することで誤差を出さないようにすることが主旨です。
同様のライブラリも多数あるかと思いますが、自分で組んで理解を深めたかったので。

引数、戻り値の受け渡しは文字列で、内部の計算は桁ごとに配列に入れた数値で行なっています。
桁数上限はありませんが、高速化は重視していないので桁が多くなればそれなりに時間はかかるかと思います。

スクリプト

decimalcalc.js
'use strict';
const decimalCalc = (function() {
    return {
        // 加算
        add: function(_a, _b) {
            let a = _a,
                b = _b;
            a = this.toString(a);
            b = this.toString(b);
            if(isNaN(a) || isNaN(b)) return NaN;
            else if(a === 'Infinity' && b === '-Infinity') return NaN;
            else if(a === '-Infinity' && b === 'Infinity') return NaN;
            else if(/-?Infinity/.test(a)) return a;
            else if(/-?Infinity/.test(b)) return b;

            let aSign = 1;
            if(a[0] === '-') {
                aSign = -1;
                a = a.slice(1);
            }
            a = a.replace(/^0+/, '').replace(/(\.\d*?)0*$/,"$1").replace(/\.$/, '');
            let aDecimalPosition = 0;
            if(a.indexOf('.') !== -1) {
                aDecimalPosition = a.replace(/^.*\./, '').length;
                a = a.replace('.', '');
            }
            const ra = a.split('');

            let bSign = 1;
            if(b[0] === '-') {
                bSign = -1;
                b = b.slice(1);
            }
            b = b.replace(/^0+/, '').replace(/(\.\d*?)0*$/,"$1").replace(/\.$/, '');
            let bDecimalPosition = 0;
            if(b.indexOf('.') !== -1) {
                bDecimalPosition = b.replace(/^.*\./, '').length;
                b = b.replace('.', '');
            }
            const rb = b.split('');

            // bのみマイナスの場合はbのマイナス符号を削除して減算へ
            if(aSign === 1 && bSign === -1) {
                return this.sub(_a, _b.replace(/^-/, ''));
            }
            // aのみマイナスの場合はbにマイナス符号を付加して減算へ
            else if(aSign === -1 && bSign === 1) {
                return this.sub(_a, '-' + _b);
            }

            // 小数部桁揃え
            if(aDecimalPosition > bDecimalPosition) {
                for(let i = 0; i < aDecimalPosition - bDecimalPosition; i++) rb.push('0');
                bDecimalPosition = aDecimalPosition;
            }
            else if(bDecimalPosition > aDecimalPosition) {
                for(let i = 0; i < bDecimalPosition - aDecimalPosition; i++) ra.push('0');
                aDecimalPosition = bDecimalPosition;
            }

            // 整数部桁揃え
            if(ra.length > rb.length) {
                const n = ra.length - rb.length;
                for(let i = 0; i < n; i++) rb.unshift('0');
            }
            else if(rb.length > ra.length){
                const n = rb.length - ra.length;
                for(let i = 0; i < n; i++) ra.unshift('0');
            }

            // 加算
            for(let i = 0; i < ra.length; i++) {
                const p = ra.length - i - 1;
                rb[p] = Number(ra[p]) + Number(rb[p]);
                // 繰り上がり
                if(p > 0 && rb[p] >= 10) {
                    rb[p - 1]++;
                    rb[p] %= 10;
                }
            }

            const
                result = rb.join(''),
                n = aDecimalPosition === 0 ? result :
                    result.slice(0, -aDecimalPosition);

            return (aSign === -1 ? '-' : '') + ((n === '' ? '0' : n) +
                    ('.' + (aDecimalPosition ? result.slice(-aDecimalPosition) : '')).
                    replace(/0+$/, '').replace(/\.$/, ''));
        },

        // 減算
        sub: function(_a, _b) {
            let a = _a,
                b = _b;
            a = this.toString(a);
            b = this.toString(b);
            if(isNaN(a) || isNaN(b)) return NaN;
            else if(a === 'Infinity' && b === 'Infinity') return NaN;
            else if(a === '-Infinity' && b === '-Infinity') return NaN;
            else if(/-?Infinity/.test(a)) return a;
            else if(/-?Infinity/.test(b)) return -b;

            let aSign = 1;
            if(a[0] === '-') {
                aSign = -1;
                a = a.slice(1);
            }
            a = a.replace(/^0+/, '').replace(/(\.\d*?)0*$/,"$1").replace(/\.$/, '');
            let aDecimalPosition = 0;
            if(a.indexOf('.') !== -1) {
                aDecimalPosition = a.replace(/^.*\./, '').length;
                a = a.replace('.', '');
            }
            const ra = a.split('');

            let bSign = 1;
            if(b[0] === '-') {
                bSign = -1;
                b = b.slice(1);
            }
            b = b.replace(/^0+/, '').replace(/(\.\d*?)0*$/,"$1").replace(/\.$/, '');
            let bDecimalPosition = 0;
            if(b.indexOf('.') !== -1) {
                bDecimalPosition = b.replace(/^.*\./, '').length;
                b = b.replace('.', '');
            }
            const rb = b.split('');

            // bのみマイナスの場合はbのマイナス符号を削除して加算へ
            if(aSign === 1 && bSign === -1) {
                return this.add(_a, _b.replace(/^-/, ''));
            }
            // aのみマイナスの場合はbにマイナス符号を付加して加算へ
            else if(aSign === -1 && bSign === 1) {
                return this.add(_a, '-' + _b);
            }

            // 小数部桁揃え
            if(aDecimalPosition > bDecimalPosition) {
                for(let i = 0; i < aDecimalPosition - bDecimalPosition; i++) rb.push('0');
                bDecimalPosition = aDecimalPosition;
            }
            else if(bDecimalPosition > aDecimalPosition) {
                for(let i = 0; i < bDecimalPosition - aDecimalPosition; i++) ra.push('0');
                aDecimalPosition = bDecimalPosition;
            }

            // 整数部桁揃え
            if(ra.length > rb.length) {
                const n = ra.length - rb.length;
                for(let i = 0; i < n; i++) rb.unshift('0');
            }
            else if(rb.length > ra.length){
                const n = rb.length - ra.length;
                for(let i = 0; i < n; i++) ra.unshift('0');
            }

            let sign = '';
            if( aSign === 1 && ra.join('') < rb.join('') ||
                aSign === -1 && ra.join('') > rb.join('') ) sign = '-';

            // bのほうが大きい場合はaとbを交換
            if(rb.join('') > ra.join('')) {
                for(let i = 0; i < ra.length; i++) {
//                    [ra[i], rb[i]] = [rb[i], ra[i]];
                    const tmp = ra[i];
                    ra[i] = rb[i];
                    rb[i] = tmp;
                }
            }

            // 減算
            for(let i = 0; i < ra.length; i++) {
                const p = ra.length - i - 1;
                ra[p] = Number(ra[p]) - Number(rb[p]);
                // 繰り下がり
                if(ra[p] < 0) {
                    ra[p] += 10;
                    if(p > 0) ra[p - 1]--;
                }
            }

            const
                result = ra.join(''),
                n = (aDecimalPosition === 0 ? result :
                    result.slice(0, -aDecimalPosition)).replace(/^0+/, '');

            return sign + ((n === '' ? '0' : n) +
                ('.' + (aDecimalPosition ? result.slice(-aDecimalPosition) : '')).
                replace(/0+$/, '').replace(/\.$/, ''));
        },

        // 乗算
        mul: function(a, b) {
            a = this.toString(a);
            b = this.toString(b);
            if(isNaN(a) || isNaN(b)) return NaN;
            else if(/^-?[0.]+$/.test(a) && /-?Infinity/.test(b)) return NaN;
            else if(/-?Infinity/.test(a) && /^-?[0.]+$/.test(b)) return NaN;

            else if(a === 'Infinity' && b === 'Infinity') return Infinity;
            else if(a === '-Infinity' && b === '-Infinity') return Infinity;
            else if(/-?Infinity/.test(a) || /-?Infinity/.test(b)) return a * b;

            let aSign = 1;
            if(a[0] === '-') {
                aSign = -1;
                a = a.slice(1);
            }
            a = a.replace(/^0+/, '').replace(/(\.\d*?)0*$/,"$1").replace(/\.$/, '');
            let aDecimalPosition = 0;
            if(a.indexOf('.') !== -1) {
                aDecimalPosition = a.replace(/^.*\./, '').length;
                a = a.replace('.', '');
            }
            const ra = a.split('');

            let bSign = 1;
            if(b[0] === '-') {
                bSign = -1;
                b = b.slice(1);
            }
            b = b.replace(/^0+/, '').replace(/(\.\d*?)0*$/,"$1").replace(/\.$/, '');
            let bDecimalPosition = 0;
            if(b.indexOf('.') !== -1) {
                bDecimalPosition = b.replace(/^.*\./, '').length;
                b = b.replace('.', '');
            }
            const rb = b.split('');

            const rc = new Array(ra.length + rb.length);
            for(let i = 0; i < rc.length; i++) rc[i] = 0;

            for(let l = 0; l < ra.length; l++) {
                const an = Number(ra[ra.length - 1 - l]);
                for(let i = 0; i < rb.length; i++) {
                    const bn = Number(rb[rb.length - 1 - i]);
                    rc[rc.length - 1 - l - i] += an * bn;
                    if(rc[rc.length - 1 - l - i] >= 10) {
                        const k = Math.floor(rc[rc.length - 1 - l - i] / 10);
                        rc[rc.length - 2 - l - i] += k;
                        rc[rc.length - 1 - l - i] %= 10;
                    }
                }
            }

            const
                result = rc.join(''),
                dPos = aDecimalPosition + bDecimalPosition,
                rn = dPos ?
                    result.slice(0, -dPos).replace(/^0+/, '') : result.replace(/^0+/, ''),
                rd = dPos ?
                    ('.' + result.slice(-dPos)).replace(/0+$/, '').replace(/\.$/, '') : '';

            return (aSign !== bSign ? '-' : '') +
                (rn === '' ? '0' : rn) +
                rd;
        },

        // 除算
        div: function(a, b, m) {
            a = this.toString(a);
            b = this.toString(b);
            if(isNaN(a) || isNaN(b)) return NaN;
            else if(/-?Infinity/.test(a) && /-?Infinity/.test(b)) return NaN;
            else if(/-?Infinity/.test(a) && !/-?Infinity/.test(b)) return a / b;
            else if(/-?Infinity/.test(b)) return 0;
            else if(/^-?[0.]+$/.test(a) && /^-?[0.]+$/.test(b)) return NaN;
            else if(/^[0.]+$/.test(b) || b === '') return Infinity;
            else if(/^-[0.]+$/.test(b)) return -Infinity;

            if(m === undefined) m = 20;
            else if(m < 0) m = 0;
            m = Math.floor(m);

            let aSign = 1;
            if(a[0] === '-') {
                aSign = -1;
                a = a.slice(1);
            }
            a = a.replace(/^0+/, '').replace(/(\.\d*?)0*$/,"$1").replace(/\.$/, '');
            const anL = a.replace(/\..*/, '').replace(/^0/, '').length;
            let aDecimalPosition = 0;
            if(a.indexOf('.') !== -1) {
                aDecimalPosition = a.replace(/^.*\./, '').length;
                a = a.replace('.', '');
            }
            const ra = a.split('');

            let bSign = 1;
            if(b[0] === '-') {
                bSign = -1;
                b = b.slice(1);
            }
            b = b.replace(/^0+/, '').replace(/(\.\d*?)0*$/,"$1").replace(/\.$/, '');
            const bnL = b.replace(/\..*/, '').replace(/^0/, '').length;
            let bDecimalPosition = 0;
            if(b.indexOf('.') !== -1) {
                bDecimalPosition = b.replace(/^.*\./, '').length;
                b = b.replace('.', '');
            }
            const rb = b.split('');
            const dpdf = aDecimalPosition > 0 && bDecimalPosition > 0 && aDecimalPosition > bDecimalPosition;

            let dpF = 1;
            // 小数部桁揃え
            if(aDecimalPosition > bDecimalPosition) {
                for(let i = 0; i < aDecimalPosition - bDecimalPosition; i++) rb.push('0');
                bDecimalPosition = aDecimalPosition;
                dpF = 0;
            }
            else if(bDecimalPosition > aDecimalPosition) {
                for(let i = 0; i < bDecimalPosition - aDecimalPosition; i++) ra.push('0');
                aDecimalPosition = bDecimalPosition;
            }
            const dP = aDecimalPosition;

            // 整数部桁揃え
            let nn = 0;
            if(ra.length > rb.length) {
                const n = ra.length - rb.length;
                nn = n;
                for(let i = 0; i < n; i++) rb.unshift('0');
            }
            else if(rb.length > ra.length){
                const n = rb.length - ra.length;
                for(let i = 0; i < n; i++) ra.unshift('0');
            }

            const lM = Math.max(ra.length, rb.length);

            const rc = new Array(lM);
            for(let i = 0; i < rc.length; i++) rc[i] = 0;

            const bL = b.length;
            b = b.replace(/^0+/, '');
            const blDiff = bL - b.length;

            let k = b.length - 1,
                kk = k;

            a = ra.slice(1 + k - b.length, 1 + k).join('');
            let aa = a;

            let sp = 0;
            do {
                if(ra[k + b.length] === undefined) {
                     ra.push('0');
                     rc.push(0);
                }
                let count = 0;
                while(('00' + aa).slice(-(b.length + 1)) >=
                      ('00' +  b).slice(-(b.length + 1)) ) {
                    aa = this.sub(aa, b);
                    rc[sp]++;
                    if(++count > 9) break;
                }
                sp ++;
                aa += ra[k + 1];
            } while(++k < m + 1 + lM);

            const
                result = rc.join(''),
                sign = aSign !== bSign ? '-' : '',
                xd = dpdf && blDiff ? blDiff : 0,
                p = dpF ? lM - kk : nn + 1 + xd;

            let
                rn = result.slice(0, p).replace(/^0+/, ''),
                rd = result.slice(p);

            const
                an = rn.split(''),
                ad = rd.split('');
            if(an.length === 0) an.push('0');

            if(ad[m] >= 5) {
                if(m > 0) {
                    ad[m - 1]++;
                }
                else {
                    an[an.length - 1]++;
                }
            }
            for(let i = m; i < ad.length; i++) ad[i] = 0;

            for(let i = 0; i < m; i++) {
                const p = m - i;
                if(ad[p] > 9) {
                    ad[p] %= 10;
                    ad[p - 1]++;
                }
            }
            if(ad[0] > 9) {
                ad[0] %= 10;
                an[an.length - 1]++;
            }

            for(let i = 0; i < an.length - 1; i++) {
                const p = an.length - i - 1;
                if(an[p] > 9) {
                    an[p] %= 10;
                    an[p - 1]++;
                }
            }

            rn = an.join('');
            rd = ('.' + ad.join('')).replace(/0+$/, '').replace(/\.$/, '');
            return sign + rn + rd;
        },

        // 引数のキャスト用
        toString: function(str) {
            if(typeof str !== 'string') str = String(str);
            str = str.replace(/^\++/, '');
            if(/^(0x[\da-f]+|0o[0-7]+|0b[01]+)$/i.test(str)) str = String(Number(str));
            str = str.trim();
            let tmp;
            // 指数表記だったらパース
            if(tmp = str.match(/^(-?)([\d.]+)(e)([+-]?)(\d+)$/i)) {
                let   n = tmp[2].split('.');
                const s = tmp[1],
                      f = tmp[4],
                      e = Number(tmp[5]);

                if(n[1] === undefined) n[1] = '';
                n[1] += this.repeat('0', e);

                if(f === '-') {
                    n[0] = this.repeat('0', e) + n[0];
                    n[1] = n[0].slice(-e) + n[1];
                    n[0] = n[0].slice(0, -e);
                }
                else {
                    n[0] += n[1].slice(0, e);
                    n[1] =  n[1].slice(e);
                }
                str = s + n[0].replace(/^0+$/, '') + '.' + n[1].replace(/0+$/, '');
                str = str.replace(/^\./, '0.').replace(/\.$/, '');
            }
            return str;
        },
        repeat: function(str, n) {
            if(String.prototype.repeat) {
                return String(str).repeat(n);
            }
            // ES6未満
            return Array(n + 1).join(str);
        },
    };
}());

桁揃えなど各メソッドで重複している部分も多いので、いずれ纏めるかもしれません。
明らかな計算違い等の不具合は可能な限り都度修正します。

使用例

各演算メソッドの引き数は文字列型と数値型どちらでも構いませんが、数値型だと値によっては渡す時点で丸められることもあります。

<script src='./decimalcalc.js'></script>
<script>
const c = decimalCalc;
const
    a = '9876543210987654321098765432109876543210',
    b = '1234512345123451234512345123451234512345';

console.log('加算');
console.log(c.add(a, b));
console.log(Number(a) + Number(b));

console.log('減算');
console.log(c.sub(a, b));
console.log(a - b);

console.log('乗算');
console.log(c.mul(a, b));
console.log(a * b);

console.log('除算');
console.log(c.div(a, b));
console.log(c.div(a, b, 50)); // 第3引数 小数部桁数(デフォルト:20)
console.log(a / b);

console.log(c.add('1e-20', '1e20'));
</script>

結果

加算
11111055556111105555611110555561111055555
1.1111055556111106e+40
減算
8642030865864203086586420308658642030865
8.642030865864204e+39
乗算
12192714521109470354099966925608898681578341524155845132525385611263518670927450
1.219271452110947e+79
除算
8.00036002070112956222
8.00036002070112956221592097566266135637550064353548
8.00036002070113
100000000000000000000.00000000000000000001

引き数は指数表記も受け取れますが、戻り値は桁数の多い値を指数表記に変換するような機能は今のところ実装していません。

簡易動作確認フォーム


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

Firebase+Reactで会員登録機能を作る際に、ユーザー名(username)も一緒に登録したい!

Firebase Auth reactfirebase reactで検索すると、基本的なemail+passwordでユーザー登録する機能の作り方は出くるのですが、ユーザー名も一緒に登録する方法を調べていても一向にしっくりくる情報が得られないんですよね。。

そこで、この記事ではfirebase+reactでユーザー登録の機能を作る際に、ユーザー名もセットで登録する方法について解説します。

実装の全体像

まずは全体像を掴みましょう。

firebaseでユーザー登録といえば、Authenticationですよね。以下の画像のように、様々な認証方法があってとても便利な機能です。
スクリーンショット 2020-11-03 21.46.29.png

スクリーンショット 2020-11-03 21.49.04.png

ただ、初心者の僕はここで疑問が浮かびます。

「Authenticationでメールアドレスとパスワード、ユーザーのIdが管理できるのはわかった。だけど、ユーザー名を登録したいと思ったらどうすれば良いんだろう??」

僕はこの疑問についてずっと考えたのですが、良い答えに辿り着けず数日が経過したある日、ふと思いました。

「もしかしたら、Firestore使えばいけるんじゃね?」

そうです。firebaseでユーザー名を登録する場合はAuthenticationではなくfirestoreにデータを保存する必要があったのです。

このAuthenticationでどうすればユーザー名登録できるかみたいな考えに束縛されて、答えに辿り着けずにいました。

具体的にどうすれば良いのか

前述の通り、firestoredb.collection('users').doc(uid).set(userInitialData)を使ってusernameemailpasswordのデータを保存します。(後述します)

ユーザーを追加する方法を解説

仮に、emailpasswordだけでユーザー登録するとしたら、特にfirestoreを使う必要はなく、createUserWithEmailAndPasswordでできるのですが、ユーザー名(username)を登録するとなると、firestoreを使う必要があります。

まずは、お好みの形でFormを作成してください。

僕の場合は、Bootstrapを使っているので、SignUp.jsxファイルは以下のようになりました。
今回特にみて欲しいのはhundlesubmitの部分です。

SignUp.jsx
import React, { useRef, useState } from "react"
import { Form, Button, Card, Alert } from "react-bootstrap"
import { useAuth } from "../contexts/AuthContext"
import { Link, useHistory } from "react-router-dom"

export default function Signup() {
 // ※長くなりすぎるので、一部省略しています
  const [error, setError] = useState("")
  const [loading, setLoading] = useState(false)
  const history = useHistory()

//特にみて欲しいのはここ
  async function handleSubmit(e) {
    e.preventDefault()

    //パスワードの一致値チェック
    if (passwordRef.current.value !== passwordConfirmRef.current.value) {
      return setError("パスワードが一致しません")
    }

  //fromで送られてきた値を処理する
    try {
      setError("")
      setLoading(true)
    //signUpという別のコンポーネントに記述した関数に引数として、fromの値を渡し、実行させる
      await signup( usernameRef.current.value, emailRef.current.value, passwordRef.current.value)
      history.push("/")
    } catch {
      setError("アカウントの作成に失敗しました")
    }

    setLoading(false)
  }

  return (
    <>
      <Card>
        <Card.Body>
          <h2 className="text-center mb-4">サインアップ</h2>
          {error && <Alert variant="danger">{error}</Alert>}
          <Form onSubmit={handleSubmit}>
            <Form.Group id="username">
              <Form.Label>名前</Form.Label>
              <Form.Control type="text" ref={usernameRef} required />
            </Form.Group>
            <Form.Group id="email">
              <Form.Label>Email</Form.Label>
              <Form.Control type="email" ref={emailRef} required />
            </Form.Group>
            <Form.Group id="password">
              <Form.Label>パスワード</Form.Label>
              <Form.Control type="password" ref={passwordRef} required />
            </Form.Group>
            <Form.Group id="password-confirm">
              <Form.Label>パスワードの確認</Form.Label>
              <Form.Control type="password" ref={passwordConfirmRef} required />
            </Form.Group>
            <Button disabled={loading} className="w-100" type="submit">
              登録する
            </Button>
          </Form>
        </Card.Body>
      </Card>
    </>
  )
}

以下のファイルにSignUpの挙動を示す関数を記述します。

AuthContext.jsx
//省略しています  

 //formが値を送信するとこのSignUp関数が実行される
  function signup(username, email, password) {
    //createUserWithEmailAndPassword(これはfirebaseのメソッド)でユーザーを作成
    return auth.createUserWithEmailAndPassword(email, password)
    //ユーザーを作成したら
      .then(result => {
     //userが戻り値として返ってくるので、それをuserに代入
        const user = result.user
     //userに値が入っていれば
        if(user) {
          //userの中にあるuidをuidに代入
          const uid = user.uid
          //他にも持っている値でfirestoreに登録したいものをuserInitialDataに代入
          const userInitialData = {
            email: email,
            uid: uid,
            username: username
          }

       //firestoreのusersというコレクションに、uidをドキュメントIDとしてもつ、 userInitialDataを登録する
       firebase.firestore().collection('users').doc(uid).set(userInitialData)
            .then('ユーザーが作成されました!')
        }
      })
  }

//以下省略

auth.createUserWithEmailAndPassword(email, password)のauthはfirebaseの初期設定ファイルで、app.auth()を定数化しています。

firebase.jsx
const app = firebase.initializeApp({
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID
})

export const auth = app.auth()
export default app

ちなみに、appの部分は直接このファイルに書き込むのではなく、別に用意した.envファイルに記述して、.gitignoreしています。

その方法はこちらの記事で説明しています。

AuthContext.jsxで使っているcreateUserWithEmailAndPasswordメソッドは戻り値がPromise < UserCredential >となっています。UserCredentialはemailやuidなど、他にも様々なデータを格納しています。

今回は、そこからuserの中に入っているemailusernameuidを取り出しました。

(createUserWithEmailAndPasswordはfirebaseの公式のリファレンス(英語)にメソッドの返り値や引数などの詳細が載っています。)

そして、firebase.firestore().collection('users').doc(uid).set(userInitialData)でコレクションにデータを保存します。
繰り返しになりますが、usersというコレクションの、uidをIDにもつドキュメントに、先ほど取得したuserInitialData.set()メソッドで登録します。

これで完了です??

参考

Firebase API Reference

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

【3ヶ月】未経験転職したエンジニアが自社開発企業で行なったこと【そこから見えるやっておくべきこと】

自己紹介

yukiと申します。DMMWEBCAMPにお世話になって、今はWEBエンジニアをしつつ、自分で仲間を集めてサービス開発したり、プログラミングの家庭教師したり毎日エンジニアライフをエンジョイしています。

本日は未経験エンジニアとして、自社開発企業に就職して3ヶ月の間に何をしたか紹介します。
勤務先にもSNSで身分を明らかにすることは承諾を得ているので、その部分もお伝えします。

会社の紹介

私は現在、株式会社ダイアログという物流×ITの会社に勤務しております。
2020年9月現在、エンジニアの募集はしていませんが、他にも様々な職種を募集しているので、Wantedlyのページをご覧ください。いつか自分のQiitaきっかけで応募してくださる方がいたら、嬉しいなと思います。

インタビュー記事(入社後の感想など)

さて、ではそんなダイアログで行なったことをまとめます。

1ヶ月目

プログラミングの部分

  • 環境構築
  • 自社サービスの使用方法、ファイルの場所などの研修
  • 【Laravel】CSVファイルの書き出しの際の簡単なロジックの追加
  • 【HTML/CSS/JS】でプロダクトのモックを作成

それ以外の部分

  • エンジニアの先輩方、部署を跨いだメンターとの1on1面談
  • 興味があったので、お客様とのMTGに参加

1ヶ月目は環境構築を終えたのち、大変ありがたいことにサービスの操作方法や関連するファイルの場所などの研修がありました。プログラミングに関してはある程度研修が終わったのち、Laravelを用いてCSVファイルの出力の際に、こういう値がきたらこうするといった簡単な処理の追加をOJT形式で行わせていただきました。また、個人の案件としてはこれまで学習した知識を生かし、HTML/CSS/JSでモック(DBの複雑な処理などはないが、見た目としてはできているプロダクト)の作成をしました。

それ以外の部分では、メンター制度が手厚く、エンジニアの先輩方に質問しやすい1on1面談があっただけではなく、他部署の先輩が一人ずっとメンターとしてついてくださるので会社の精度などの質問ができました。また、お仕事の部分では開発だけではなく、早いうちからお客様の意見を直接お聞きしたかったので、ミーティングに参加希望を出したところ承認していただきました。

2ヶ月目

プログラミングの部分

  • 【Laravel】APIを作成、Service層の切り分け
  • 【AngularJS】APIに連携させるフロントエンドの処理を作成
  • 【SQL】20行くらいのSQLを教えていただき、理解しながらLalavelの処理の中に組み込む

それ以外の部分

  • Qiitaのプロジェクト提案&開始

2ヶ月目に入ると少しだけ難易度が上がり、フロントエンドとバックエンドを分けた開発に携わることになりました。LaravelでAPIを作成し、フロント側のAngularJSと連携させるような実装です。他にも、スクールでは学習しなかった【Service層】という概念を知り、ロジックを切り分けることの大切さを確認しました。また、自社サービスとなると複雑なDBがつきもので、これまで見たことのない(自分にとっては)長いSQLを扱うようになりました。ただSQLがわかるだけではなく、Railsにもアクティブレコードなんかがありますが、LaravelでのORMも同時に学ぶ必要があったので少し大変でした。

プログラミング以外の部分では、社内でこれまでにQiitaを運用したいができていなかったという課題をお聞きできたので、投稿経験もあったため、メリットデメリットをまとめ、スケジュールをひき、自分主体で投稿サイクルを作成するQiitaプロジェクトを提案し、無事まかせていただきました。(まさにこの記事もその一環なので、ぜひLGTMお願いしますっ)

3ヶ月目

プログラミングの部分

  • 【Laravel】モデル以外の場所に、より細かいバリデーション(削除の際の制御など)追加
  • 【AngularJS】カスタムディレクティブのメリット理解と作成
  • ER図の離れたデータの取得の仕方を学習 ### それ以外の部分
  • 社内の懇親会企画

3ヶ月目は、これまでの実装の他に削除制御について機能を実装しました。スクールで学習するレベルだと、モデルに簡単なバリデーションを書くだけでしたが、もっと細かい処理を書く必要があったのでいい経験になりました。フロントエンドの方では、カスタムディレクティブという概念を学び、コードをより短く書くなどリファクタリングと所謂コンポーネントの役割をしっかり決める必要性を学びました。大きいER図を見ながらデータを取りに行くことがあるのですが、その際テーブル同士の関係は非常に重要で、3ヶ月経過した今でも無駄なくそこにアクセスできるように頑張っています。企業にもよりますが、弊社にはER図のまとめがあったので印刷して暇な時に眺めています。

全く話が変わりますが、大学生の頃にボードゲームでたくさん遊んでいた経験があったので、メンターの方と協力して社内の懇親会を企画しました。オンラインが多い現状ですが、いろいろ考えながら引き続きやっていきたいと思います。

やっておいてよかったこと

  • 質問の仕方をしっかり身につける まだまだわかりにくい質問をしてしまっていると思いますが、スクールで学習する際にここだけはしっかりと意識していました。

大体以下のような形で質問しています。

#【何がやりたくて】
#【今どうやってて】
#【どこでエラーが起きて】
#【自分ではこうだと思ったのだけど】
#【うまくいかないのでアドバイスをください】+【あれば参考にした記事】

相手が答えやすい質問をすることはとても大切だと思うので、この辺は早いうちから意識した方がいいと思います。

  • フロントとバックを分けた開発を経験していた
    ポートフォリオを作る段階で、RailsでAPIを作って、フロントエンドのフレームワークNuxt.jsを使ってバックフロントを分けた開発をしていたので、その辺の合流は思っていたよりスムーズにできていたのかなと思います。これは、他の記事でもまとめておりますので、よければご覧ください。後でリンク貼ります。

反省

以下、反省点です。精進します。

  • もっとSQLやDBの学習をしておけばよかった
  • 使うフレームワークや言語がわかったら、どんなに期間が短くても深く学んでおけばよかった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript ~HTMLを置き換え~】勉強メモ⑨

JavaScriptちゃんと学習中。
ほぼ自分の勉強メモです。
過度な期待はしないでください。

過去投稿記事

【JavaScript ~変数・定数、if文・switch文~】勉強メモ
【JavaScript ~for文、配列、オブジェクトについて~】勉強メモ②
【JavaScript ~関数について~】勉強メモ③
【JavaScript ~クラスやインスタンス、メソッドについて~】勉強メモ④
【JavaScript ~ファイルの分割について~】勉強メモ⑤
【JavaScript 読み込み】勉強メモ⑥
【JavaScript ~配列のメソッド~】勉強メモ⑦
【JavaScript ~コールバック関数~】勉強メモ⑧

HTMLを置き換える

  • 手順

 1、JavaScriptを使って、書き換えたい部分のHTML要素のid名やクラス名を指定することで、マッチする要素を取得
 2、取得した要素のコンテンツを書き換える

1、要素を取得

 documentオブジェクトには、ブラウザに表示されているHTMLや、それに関連するCSSを
 操作する機能を持っている。また、いくつかのメソッドを利用することでマッチする要素を
 抽出して取得することが出来る。

  • getElementById("id名")メソッド
id名で取得する書き方
document.getElementById("id名");

 今回使用したgetElementById("id名")メソッドは、( )内に指定されたid名を
 持つ要素を丸ごと取得する。
 id名は、文字列で指定する必要があるのでシングルクォテーションで囲む。

  • getElementsByClassName("class名")メソッド
class名で取得する書き方
document.getElementsByClassName("クラス名");

 getElementsByClassName("クラス名")メソッドは、( )内に指定されたclass名を
 指定して取得する。
 ここで注意する所は、getElementsと複数形になっている事。
 class名を指定するgetElementsByClassName("class名")の場合は、同じclassを持つ
 要素を全て取得することが可能な為。

  • document.querySelector("セレクタ名")メソッド
セレクタ名で取得する書き方
document.querySelector("セレクタ名");

 セレクタ名とは、CSSでスタイルを適用するために指定している要素。
 querySelector("セレクタ名")メソッドは、セレクタ名を指定して要素を取得する。
 しかしHTML上から、引数で指定したセレクタに合致するもののうち一番最初に見つかった要素1つ
 取得する。


2、コンテンツを書き換える

 取得したHTMLの要素のコンテンツを書き換える為には下記の記述をする。

  • textContentプロパティ
取得した要素のコンテンツを書き換える
document.getElementById("id名").textContent = '書き換えたい文字列';


  • innerHTMLプロパティ
取得した要素のコンテンツを書き換える
document.getElementById("id名").innerHTML = '書き換えたい文字列';

 上記2つのつ違いは、HTMLを解釈して出力するかしないか。

 textContentプロパティは、htmlのタグを文字としてそのまま出力する(HTMLを解釈せず出力)
 innerHTMLプロパティは、htmlのタグを解釈して出力する。

 例えば、<b>ボタンを押しました</b>という文字列に書き換える場合、
 textContentプロパティは、<b>ボタンを押しました</b>と出力し、タグも文字として表示する。
 innerHTMLプロパティは、ボタンを押しましたと出力し、タグをHTMLとして解釈して表示する。

※参考記事:JavaScript textContentとinnerHTMLの違い

 documentやconsoleなどの全てオブジェクトには、それぞれ固有のメソッドとプロパティを持っている。
 メソッドには、必ず後ろに( )がつき、オブジェクトに◯◯しなさいと指示を出す。
 また、プロパティはその値を読み取たり、書き換えたりすることが出来る。ここでいうプロパティの値とは、
 = の横の'書き換えたい文字列'の部分を指す。

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

【JavaScript ~HTMLを置き換え、ダイアログボックス~】勉強メモ⑨

JavaScriptちゃんと学習中。
ほぼ自分の勉強メモです。
過度な期待はしないでください。

過去投稿記事

【JavaScript ~変数・定数、if文・switch文~】勉強メモ
【JavaScript ~for文、配列、オブジェクトについて~】勉強メモ②
【JavaScript ~関数について~】勉強メモ③
【JavaScript ~クラスやインスタンス、メソッドについて~】勉強メモ④
【JavaScript ~ファイルの分割について~】勉強メモ⑤
【JavaScript 読み込み】勉強メモ⑥
【JavaScript ~配列のメソッド~】勉強メモ⑦
【JavaScript ~コールバック関数~】勉強メモ⑧

HTMLを置き換える

  • 手順

 1、JavaScriptを使って、書き換えたい部分のHTML要素のid名やクラス名を指定することで、マッチする要素を取得
 2、取得した要素のコンテンツを書き換える

1、要素を取得

 documentオブジェクトには、ブラウザに表示されているHTMLや、それに関連するCSSを
 操作する機能を持っている。また、いくつかのメソッドを利用することでマッチする要素を
 抽出して取得することが出来る。

  • getElementById("id名")メソッド
id名で取得する書き方
document.getElementById("id名");

 今回使用したgetElementById("id名")メソッドは、( )内に指定されたid名を
 持つ要素を丸ごと取得する。
 id名は、文字列で指定する必要があるのでシングルクォテーションで囲む。

  • getElementsByClassName("class名")メソッド
class名で取得する書き方
document.getElementsByClassName("クラス名");

 getElementsByClassName("クラス名")メソッドは、( )内に指定されたclass名を
 指定して取得する。
 ここで注意する所は、getElementsと複数形になっている事。
 class名を指定するgetElementsByClassName("class名")の場合は、同じclassを持つ
 要素を全て取得することが可能な為。

  • document.querySelector("セレクタ名")メソッド
セレクタ名で取得する書き方
document.querySelector("セレクタ名");

 セレクタ名とは、CSSでスタイルを適用するために指定している要素。
 querySelector("セレクタ名")メソッドは、セレクタ名を指定して要素を取得する。
 しかしHTML上から、引数で指定したセレクタに合致するもののうち一番最初に見つかった要素1つ
 取得する。


2、コンテンツを書き換える

 取得したHTMLの要素のコンテンツを書き換える為には下記の記述をする。

  • textContentプロパティ
取得した要素のコンテンツを書き換える
document.getElementById("id名").textContent = '書き換えたい文字列';


  • innerHTMLプロパティ
取得した要素のコンテンツを書き換える
document.getElementById("id名").innerHTML = '書き換えたい文字列';

 上記2つのつ違いは、HTMLを解釈して出力するかしないか。

 textContentプロパティは、htmlのタグを文字としてそのまま出力する(HTMLを解釈せず出力)
 innerHTMLプロパティは、htmlのタグを解釈して出力する。

 例えば、<b>ボタンを押しました</b>という文字列に書き換える場合、
 textContentプロパティは、<b>ボタンを押しました</b>と出力し、タグも文字として表示する。
 innerHTMLプロパティは、ボタンを押しましたと出力し、タグをHTMLとして解釈して表示する。

※参考記事:JavaScript textContentとinnerHTMLの違い

 documentやconsoleなどの全てオブジェクトには、それぞれ固有のメソッドとプロパティを持っている。
 メソッドには、必ず後ろに( )がつき、オブジェクトに◯◯しなさいと指示を出す。
 また、プロパティはその値を読み取たり、書き換えたりすることが出来る。ここでいうプロパティの値とは、
 = の横の'書き換えたい文字列'の部分を指す。

ダイアログボックス

ファイル名

  • アラートダイアログボックス

 windowオブジェクトとalertメソッドを用いることで、上記の画像のように
 ブラウザ上にアラートを表示することが出来る。
 引数にアラートを表示させる情報を渡します。上記の例では、文字列を直接引数にしましたが、変数でも可。

書き方
window.alert('メッセージ');


  • 確認ダイアログボックス

ファイル名
 確認ダイアログボックスを表示させるには、windowオブジェクトとconfirmメソッドを用いる。
 confirmメソッドには、alertメソッドには無い特徴があり、ダイアログボックスを表示するだけでなく
 リターンを返すこと -リターンを返すとは、そのメソッドが実行結果を報告してくるようなもの- をし、
 確認ダイアログボックスの[OK]をクリックすればtrueが、[キャンセル]の時は、falseという
 値を返すという実行結果を報告する。

書き方
window.confirm('メッセージ');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Angular のコンセプトを理解する / Angular勉強会 #1


Angular講座の概要(予定)

  • 1. Angularのコンセプトを理解する
  • 2. Angularの代表的なライブラリを知る
  • 3. Angularにおける設計手法を考える

フレームワークを理解する上で大切なこと

フレームワークは、ただのツールではなく、その背景に一貫した「コンセプト」があることが多い。例えば。

  • Ruby on Rails: 「設定より規約」 ⇒ 細かいルールを定めるのではなく、最適な設定がビルドインされており、開発者が特別な設定を行うのはそれを破りたいときだけだ
  • React: 「宣言的/関数的」 ⇒ 全てのReactコンポーネントは、データを引数にとりレンダリング情報を出力する関数であり、これにより宣言的かつ単一方向のレンダリングを実現している。
  • Vue: 「親しみやすい」 ⇒ ReactやAngularと重複する機能が多いが、それらをより簡潔に、簡単に書けるようになっている。

ツールとしての機能は、ググれば出てくることが多く、またかなり移ろいやすい。しかし、そのフレームワークのコンセプトはバージョンを重ねても変わることは少なく、また各種ライブラリを使う上でも助けになる。


Angularのコンセプトとは

Angularのコンセプトは、一言で言えば「大規模設計の扱いやすさ」といえる。そしてそれは以下の3つのメインコンセプトにより担保される。

  1. モジュール
  2. コンポーネントとバインディング
  3. サービス依存性の注入(DI)

これらのコンセプトを使いこなすことがAngularを最大限に活かすための必要条件と言ってよいし、逆にこれらが不要だ、重たいと感じる場合はReactやVueのほうが向いている可能性が高い。

以下、一つずつ確認していく。


Angularのコンセプト①: モジュール

AngularはJavascript(ES2015)で定義されているモジュールとは独立したNgModuleというモジュール体型をもつ。NgModuleはアプリケーションの機能やビューの単位を表し、またそれらを結びつける役割をもつ。

またモジュールは自作のものだけではなく、外部のライブラリもモジュールとして参照することができる。

全てのAngularアプリケーションは、 AppModule というモジュールを持ち、アプリケーション内で必要となるモジュールは全てこの AppModule に含まれる必要がある。つまりモジュール同士の依存関係をすべて明示的に指定することがAngularアプリケーションでは求められる

これは、プロセスを重たくしがちな側面もあるが、モジュールの再利用性やルール上の制約を管理する上で非常に助けとなる。まさにエンタープライズな設計を行いやすい側面と言える。

image.png


Angularのコンセプト②: コンポーネントとテンプレート

コンポーネントとは、Angular内で唯一ビューを定義できる要素である。構成要素は大きく分けて2つあり、HTMLテンプレートやCSSなどのレンダリングを表すものと、そのビューに表示するデータや表示要素を取得したり加工するロジックを表すクラスである。

ただし、Angular内での主体はクラスの方であり、そのクラスにHTMLテンプレートやスタイルを紐付ける形をとる。
(その紐付けにはTypescriptのデコレータを利用する)

AngularにおけるHTMLテンプレートは、通常のHTMLにAngular独自の拡張を加えたものとなり、クラス内のデータやロジックをバインディングを通じてやり取りする。


Angularのコンセプト②: コンポーネントとテンプレート - バインディングによる紐付け

コンポーネント内で、テンプレートの要素をバインディングによって紐付けるのが、Angularの大きな特徴である。バインディングには、イベントバインディングとプロパティバインディングが存在する。

イベントバインディング

ユーザーのアクションに応じてアプリケーションのデータを更新する、ビュー → クラス 方向のやり取り。一般的にはメソッドをユーザーアクションに対してバインディングする形で行う

プロパティバインディング

システム内の状態変更をビューに対して反映する、 クラス → ビュー 方向のやり取り。一般的には、クラスプロパティやGetterメソッドの戻り値を、表示部に対してバインディングする形で行う

また、バインディングの際の値を加工する pipe という仕組みも存在する。

image.png


Angularのコンセプト③: サービスの依存性注入(DI)

ビューに依存しないデータやロジックは、再利用性からもメンテナンス性からも特定のビューとは独立した領域で保持される必要がある。そういったものを保持するのが「サービス」となる。

サービスクラスをコンポーネントや他のサービスに紐付ける際には、単純にコンポーネントから参照を行うのではなく、依存性の注入(Dependency Injection:DI)を用いる。

image.png


依存性の注入: DI とは

DIとはもともとはJavaなどのサーバーサイドの分野でよく用いられてきた、疎結合を保ったまま依存関係を構築する手法である。

DIを実現する仕組みをDIコンテナといい、Angularにもその仕組は組み込まれている。主な挙動としては以下のようになる。

  1. サービスを利用するクラス側が、どのサービスが必要かを記述する。
  2. DIコンテナにサービスを登録する
  3. DIコンテナが起動時に依存性を解決し、生成したサービスをクラスに注入する

image.png


Angularのコンセプト

Angularのコンセプトは、エンタープライズを見据えた大規模設計の行いやすさである。

AngularはJavascriptのそれとは独立した独自のモジュール構造を持ち、システムの構造を明示的に記述する。そしてそれらのモジュール群を、ビューを管理するコンポーネントとロジックを管理するサービスに分離することにより疎結合な設計を担保し、DIを用いて疎結合を保ったまま依存性を解決する。ビューはAngularにより拡張されたHTMLをテンプレートとし、イベントバインディングとプロパティバインディングという双方向のバインディングで、クラスロジックと接続される。

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

アロー関数 =>

今まで何気なくこのアロー関数を使ってきたのですが、やっぱりJSの基礎がなっていないので気になります
私気になります(?)

アロー関数とは

=>を使ってfunctionを使わずに関数を作成することができる

具体的に見ていきましょう

function createGreeting(name) {
  return "Hello" + name;
}

こういう記述を

const createGreeting = name => "Hello" + name;

こんなに簡単に記述できる様になるんですね〜〜
ただこれは引数が1個で、処理が1行で済む場合です
2個以上2行以上になると

function createGreeting(name, greeting) {
  console.log(name);
  return greeting + "" + name;
}

こんな処理を

const createGreeting = (name, greeting) => {
  console.log(name);
  return greeting + "" + name;
}

こうやって書き換えます

ポイントは引数2個以上の場合はしっかり()の中に引数を記述すること!
後、2行以上の記述になると、{}の中に処理を記述すること!
感覚的にこの記号=>わかりやすいですよね

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

JSでおみくじアプリを作る!

あいさつ

初めての人は初めまして!知っている人はこんにちは!

今回はJSを使って「おみくじアプリ」を作ってみたいと思います!
では!

環境

・windows10
・Node.js 12.19.0

仕様設計

まずはコードを書く前に使用を決めていきます
まず中央にボタンを配置しそれを押すことでボタンの上に運勢を表示することにします
ボタンの色は赤でボタンはcssで押し込めるようにします
あとはカーソルをポインターにしたり細かくしていきます

運勢はこの三つにします
「大吉」「中吉」「小吉」

では仕様設計だけでは楽しくないので作っていきます

コーティング

自分はエディターにVScodeを使っています
DLリンク張っておきます!
https://code.visualstudio.com/Download
すごく使いやすくまた「Linux」でも使えるのがいいですね!

最後にコード解説をします
まずはHTMLとCSSを使ってボタンを作っていきます!

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>おみくじ</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div id="message">運勢</div>
        <div id="btn">おみくじ</div>
    </body>
</html>

次にcssを書きます

style.css
#btn {
    width: 200px;
    height: 200px;
    background: #ef454e;
    border-radius: 50%;
    margin: 30px auto;
    text-align: center;
    line-height: 200px;
    color: white;
    font-weight: bold;
    font-size: 41px;
    cursor: pointer;
    box-shadow: 0 10px #d1483e;
    user-select: none;
}

#btn:hover {
    opacity: 0.9;
}

#btn:active {
    box-shadow: 0 5px #d1483e;
    margin-top: 35px;
}
#message {
    text-align: center;
    padding-top: 30px;
    font-size: 30px;
}

次にjsです

main.js
'use strickt';

{
    const btn = document.getElementById('btn');
    const message = document.getElementById('message');

    btn.addEventListener('click', () => {
        let n = Math.random();

        if (n < 0.05) {
            btn.textContent = 'おみくじ'; //5%
            message.textContent = '大吉';
        } else if (n < 0.2) {
            btn.textContent = 'おみくじ'; //15%
            message.textContent = '中吉';
        } else {
            btn.textContent = 'おみくじ'; //それ以外
            message.textContent = '小吉';
        }
    });
};

最後にhtmlにjsを」読み込ませます

タグの最後の行に以下の一行を追加してください
index.html
<script src="main.js"></script>

あとは動作させるのですがVScodeの」拡張機能の「Live Server」を使っていきます
これはいちいちブラウザにファイルをコーピーせずにブラウザに表示ができます
さらに開発中にコードを保存すると自動的にブラウザにも反映されます
url貼っておきます
https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer
web系プログラマーの方なら「必須」といっても過言ではない拡張機能です!

コードの解説

コードの解説をしていきます
しかし
HTMLやCSSは基本のことばかりなので解説しなくてもわかると思います
なのでJSのコードの解説をしていきます
もう一度JSのコードを見てみましょう

main.js
'use strickt';

{
    const btn = document.getElementById('btn');
    const message = document.getElementById('message');

    btn.addEventListener('click', () => {
        let n = Math.random();

        if (n < 0.05) {
            btn.textContent = 'おみくじ'; //5%
            message.textContent = '大吉';
        } else if (n < 0.2) {
            btn.textContent = 'おみくじ'; //15%
            message.textContent = '中吉';
        } else {
            btn.textContent = 'おみくじ'; //それ以外
            message.textContent = '小吉';
        }
    });
};

こう見ると少しごちゃごちゃしていますが上から見ていきましょう

main.js
'use strickt';

まずはエラーチェックをしています
次に波かっこで今後コードを拡張しやすいように囲んでおきました
こうすることで拡張するときに変数のスコープのエラーが出にくくなります
では波かっこの中身を見ていきましょう

main.js
const btn = document.getElementById('btn');
const message = document.getElementById('message');

これはconstでbtnという定数定義をしそれにHTMLのid=btnを代入しています
この「document.getElementyById()」がHTMLからIDを取ってきます
そして引数にHTMLのidを指定します
二行目も同じです
constでmessageという定数定義をしHTMLからmessageというidを取って代入します

main.js
btn.addEventListener('click', () => {

このコードはbtnがクリックされた時のイベントを設定します
このbtn.addEventListener()は

main.js
HTMLのidを代入した変数/定数.addEvenListener('idに対してのアクション',アクションが起きた時の処理)

このように使います
今回は引数にアロー関数を使い
クリックされたときにそのアロー関数内の処理が実行されるようにしました
次にそのアロー関数の処理の中を見ていきましょう

main.js
let n = Math.random();
if (n < 0.05) {
        btn.textContent = 'おみくじ'; //5%
        message.textContent = '大吉';
    } else if (n < 0.2) {
        btn.textContent = 'おみくじ'; //15%
        message.textContent = '中吉';
    } else {
        btn.textContent = 'おみくじ'; //それ以外
        message.textContent = '小吉';
    }
});

まず変数nにランダムな数字を代入します
そしてその変数nに代入された値によって以下のif文分岐が行われます
実はこれ確率操作をしているのですよね
上から順に「5%」「15%」「80%」になっています
これについては後で解説します
なのでまずはif文の分岐処理を見ていきましょう

main.js
btn.textContent = 'おみくじ';
message.textContent = '中吉';

これは中吉の場合ですね
まずtextContentで表示されているテキストを変えます
しかしbtnはどの処理でも「おみくじ」と表示されます
HTMLにも

index.html
<div id="btn">おみくじ</div>

としっかり書いてあるので無駄な処理ですね
なので消します

main.js
'use strickt';

{
    const btn = document.getElementById('btn');
    const message = document.getElementById('message');

    btn.addEventListener('click', () => {
        let n = Math.random();

        if (n < 0.05) { 
            message.textContent = '大吉'; //5%
        } else if (n < 0.2) {
            message.textContent = '中吉'; //15%
        } else {
            message.textContent = '小吉'; //それ以外
        }
    });
};

ずいぶんすっきりしました
こうやって余計な処理を消していくのもバグを防ぐことにつながります
話がそれましたがもう一度if文の分岐処理を見てみます

main.js
message.textContent = '大吉';

これは大吉の場合の処理ですがmessage要素を大吉に書き換えています

では次に確率操作のほうを見ていきましょう

main.js
if (n < 0.05) {

if文の一番上です
もし変数nが0.05より小さかったら一番上の処理(このコードの場合は大吉の処理)をします
もし確率を変えたいなら0.05のところを変えることで確率が変わっていきます
全部同じように確率を変えています
最後はelseでそれ以外(80%)になっています
もしかしたらゲームのガチャなどはこのようなアルゴリズムで確率操作しているかもしれませんね

ここまでですべてのコードを解説してきました
この「おみくじゲーム」はアルゴリズムも簡単でコードも簡単に書くことができるので「JSを学んだから何かアウトプットしたい!」という人こそ作ってみてはどうでしょうか?
もちろんHTMLとCSSを使わずにJSだけでコンソール上に結果を出すこともできます
その場合はconole.logに変えるなりしてアレンジしてみてください

最後に

みなさんお疲れさまでした
記事を読んでいただきありがとうございます!

今回は比較的簡単なJSのゲームの作り方を解説しました
本当に簡単なので皆さんも作ってみてください!

質問などは僕のTwitterへお願いします
https://twitter.com/Atie44675100

それではまた日曜日にお会いしましょう!

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

jQueryプロジェクトでもReact風に書ける?

jQueryを使ったシステム開発で、ES6+Webpack形式でコーディングしていました。

その際にjQuery.html()メソッドで追加する目的のHTML文字列を管理していたのですが、分かりやすくするために調整していたら自然とReactのJSX風になってビックリ?というお話です。

基本的な例

ファイル名と定数名は大文字で始めます。

こうすることで、hogeTemplateとかhogeElement等の命名をしなくて良くなります。
なおかつ他に大文字の定数が無いので、HTMLの塊であることは一目瞭然になるメリットがあります。

UserList.js
// 引数でinnerHTMLが変わる
const Hello = (username) => {
  return `<p>Hello, ${username}!</p>`;
};
ErrorFeedback.js
// 引数でクラス名とinnerHTMLが変わる
const ErrorFeedback = (error) => {
  return `
    <div class="${error ? 'text-error' : 'text-success'}">
      ${error ? error.description : 'OK'}
    </div>
  `;
};

連続データの例

Reactでお馴染みのmapを使っても書けます。
まずは単純な例です。

const UserListItems = (users) => {
  return users.map((user) => `<li data-id=${user.id}>${user.username}</li>`);
};

$("ul").html(UserListItems(users));

$(selecter).html(htmlString)htmlString引数に配列を渡しても良いのかと思うかもしれませんが...実は正常に表示されます!
配列のカンマが画面に入るなんてことにはなりません。

これをもう少し詳しく見ていくと、とある注意点があります。

htmlStringが配列

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <ul></ul>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script>
      const users = [
        { id: 1, username: 'foo' },
        { id: 2, username: 'bar' },
        { id: 3, username: 'buz' },
      ];

      const UserListItems = (users) => {
        return users.map((user) => `<li data-id=${user.id}>${user.username}</li>`);
      };

      $('ul').html(UserListItems(users));
    </script>
  </body>
</html>

結果はOKです。

スクリーンショット 2020-11-04 2.05.36.png

htmlString<親要素>配列</親要素>

これはReactで頻出パターンだと思いますが、上記と同じノリでやろうとすると上手く動きません。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script>
      const users = [
        { id: 1, username: 'foo' },
        { id: 2, username: 'bar' },
        { id: 3, username: 'buz' },
      ];

      const UserList = (users) => {
        return `<ul>${users.map((user) => `<li data-id=${user.id}>${user.username}</li>`)}</ul>`;
      };

      $('#root').html(UserList(users));
    </script>
  </body>
</html>

結果... コンマも出力されます。
スクリーンショット 2020-11-04 2.09.52.png

ではどうすれば解決するのかというと、コンマを無くしてしまえばOKです。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script>
      const users = [
        { id: 1, username: 'foo' },
        { id: 2, username: 'bar' },
        { id: 3, username: 'buz' },
      ];

      const UserList = (users) => {
        return `
          <ul>
            ${users.map((user) => `<li data-id=${user.id}>${user.username}</li>`).join('')}
          </ul>
        `;
      };

      $('#root').html(UserList(users));
    </script>
  </body>
</html>

結果は無事に表示できました。

スクリーンショット 2020-11-04 2.11.07.png

おわりに

これは残念ながら一人で担当している開発のため、チームで作業する場合はアプローチが有効かは分かりませんが...
jQueryと、React風の宣言的なUI記法の狭間で彷徨っている場合には現実的な解の一つになるような気がします。

また、jQuery開発の際にhtmlをJSで管理する場合
どうやっているか等コメント頂けましたら嬉しいです!

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