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

JavaScriptでブラウザのコンソールにconsole.log出力する時は参照型に注意しましょう

概要

ブラウザでJavaScriptを実行している時にconsole.logに参照型のデータを渡して出力すると期待通りに出力されない場合があります。その再現手順と回避策を記します。

ソースコード

今回使用するソースコードは以下の2ファイルです。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>console-log-sample</title>
</head>
<body>
  <script src="./main.js"></script>
</body>
</html>
main.js
'use strict';

class Name {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
class Human {
  constructor(name, age, address1, address2) {
    this.name = name;
    this.age = age;
  }
}

const name = new Name('太郎', '佐藤');
const human = new Human(name, 25);
console.log(human); // firstName が 太郎, lastName が 佐藤, age が 25 と出力して欲しい

human.name = new Name('次郎', '鈴木');;
human.age = 30;
console.log(human); // firstName が 次郎, lastName が 鈴木, age が 30 と出力して欲しい

ブラウザ(Chrome)で実行した場合の出力結果

index.html をブラウザで表示したときのコンソール出力結果は画像の通りです。

chrome_console_01.png

ageは期待通りに出力できていますね。nameの中身を確認するために展開してみましょう。

chrome_console_02.png

展開してみると、1つ目のconsole.log結果と2つ目のconsole.log結果が同じ値になっています。どうやら後から代入した値が出力されているようですね。

なぜこんな現象が発生するのでしょうか。これについてはMDNのドキュメントに記載がありました。

console.log() - Web API | MDN

出力する JavaScript オブジェクトのリスト。各オブジェクトの文字列表現が記述順で出力されます。
Chrome や Firefox の比較的新しいバージョンを使っているなら注意が必要です。
これらのブラウザーで記録されるのはオブジェクトへの参照です。そのため、 console.log() を呼び出した時点でのオブジェクトの「値」が表示されるのではなく、内容を見るために開いた時点での値が表示されます。

つまり、console.logを呼び出した時点の値ではなく、ブラウザで展開した時の値を出力しているということになります。

回避策

回避策についてもMDNのドキュメントに記載がありました。

console.log(obj) を使わず、 console.log(JSON.parse(JSON.stringify(obj))) を使用してください。
これにより、ログを記録した瞬間の obj の値を確実に見ることができます。こうしないと、多くのブラウザーでは値が変化したときに常に更新されるライブビューになります。これは望むことではないかもしれません。

それでは、MDNのドキュメント通りにmain.jsを書き換えてみましょう。
(赤色の部分を削除し、代わりに緑色の部分で挿入します。)

main.js
'use strict';

class Name {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
class Human {
  constructor(name, age, address1, address2) {
    this.name = name;
    this.age = age;
  }
}

const name = new Name('太郎', '佐藤');
const human = new Human(name, 25);
- console.log(human); // firstName が 太郎, lastName が 佐藤, age が 25 と出力して欲しい
+ console.log(JSON.parse(JSON.stringify(human)));

human.name = new Name('次郎', '鈴木');;
human.age = 30;
- console.log(human); // firstName が 次郎, lastName が 鈴木, age が 30 と出力して欲しい
+ console.log(JSON.parse(JSON.stringify(human)));

main.jsを書き換えた後に、再度ブラウザで開いてみます。

chrome_console_03.png

期待通りの出力結果になりましたね。

まとめ

参照型データをconsole.logで出力する時は、console.log(JSON.parse(JSON.stringify(obj)))を使うと期待通りの出力結果が得られます。

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

【実務未経験】初学者が0から学ぶTypeScript①

学習しようと思ったきっかけ

これまで、HTML/CSS → JavaScript → Reactの順で学習してきました。
Reactで作ったポートフォリオも完成して「就活するぞー!」と本格的に企業へのエントリーを始めてみると

【必須条件】
・Webフロントエンド開発の業務経験2年以上
・モダンFW(Angular, React, Vueなど)を用いた開発経験
・TypeScriptを用いた開発経験

TypeScriptでの開発経験を求めている企業がめちゃくちゃ多かったです。
Reactと相性も良いらしく、JSフレームワーク×TypeScriptはフロントエンド開発の現場では主流の手法だったようで、身に着けるべき技術だと感じたので学んでいきます。
※もし私の記事の内容に誤りがあればご教授いただけると幸いです。

参考:TypeScriptの型入門

1.TypeScriptって?

型がついたJavaScriptのこと(JavaScriptの上位互換)。

const a: number = 5;

基本はこんな風に書いていくみたい
const 定数名: 型名 = 値;

2.型って?

プリミティブ型とリテラル型がある

プリミティブ【primitive】→ 原始的、素朴な
リテラル【literal】 → 逐語的、ありのままの

直訳だけ掻い摘んで見ると素朴もありのままも同じ気がするけど、何より
プリミティブ型はリテラル型を内包している型
リテラル型はプリミティブ型を細分化している型
っていう概念を抑えておけばいいみたい。
お魚(プリミティブ)型とマグロ(リテラル)型みたいな感じかなぁ

プリミティブ型

TypeScriptにおける基本の型

const a: number = 5;

numberの部分を
string(文字列)
number(値)
boolean(真偽値)
symbol(ユニークな識別子)
bigint(とても大きな整数値)
null
undefined

に置き換えて型を定義できるみたい。
定義した型と違う型のデータが定数に代入されるとエラーになる。
Javascript - シ、シンボル(Symbol)って何?
JavaScriptのBigIntを勉強してみた

リテラル型

プリミティブ型を細分化した型

const a: "hoge" = "hoge";
const b: 5 = 5;
const c: true = true;

"hoge"という型をもつ定数aには"hoge"というstring(文字列)のみ代入できる。
5という型をもつ定数bには5というnumber(値)のみ代入できる。
trueという型をもつ定数cにはtrueというboolean(真偽値)のみ代入できる。

プリミティブとリテラル型の関係性

リテラル型はプリミティブ型を細分化した型なので、これもOK

const tuna :"maguro" = "maguro";
const fish :string = tuna;

"maguro"型を持った定数tunaは、string(文字列)型を持った定数fishを細分化したものなので定数fishに代入できる。逆はNG。

型推論

型名(型注釈)の宣言は省略できる。その場合、

constで宣言された定数はリテラル型が定義されるため、

const a = "apple";
const b = 1;

定数a"apple"
定数b1型をもつことになる。

対してletやvarで宣言された変数はプリミティブ型が定義されるため、

let c = "orange";
var d = 2;

変数cstring
変数dnumber型をもつことになる。

今日はこれでおしまい。また明日!

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

jQueryを使ってIDに対して個別のIDを設定する方法

備忘録として残しています。

chart.jsで一画面に複数のチャートをおきたい時に使いたいなと思って書きました。

index.html
<!DOCTYPE html>
<html>
    <body>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
        <canvas id="bar-chart-1" data-data-id="1" class="chart"></canvas>
        <canvas id="bar-chart-2" data-data-id="2" class="chart"></canvas>
        <script>
            $(function(){
                $('.chart').each(function(){
                    console.log(document.getElementById('bar-chart-' + $(this).data('data-id')))
                })
            })

        </script>

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

(javascript) データ型と参照型

はじめに

今回は、Javascriptの"超基本"
データ型と参照型 について記録します。

過去に4点ブジェクトについて、記事を投稿しました。

(Javascript) オブジェクト指向 と オブジェクト&配列 の違い
(Javascript) インスタンス化 と コンストラクター
(Javascript) 静的プロパティと静的メソッド
(Javascript) 組み込みオブジェクトとnew演算子によるインスタンス化

データ型とは

javascriptのデータの種類のことです。
主なデータは以下↓になります。

abc → 文字列
123 → 数値
true(真)false(偽) → 論理値

データ型への意識

このデータ型への意識は、javascriptは若干緩めです。
データ型を強く意識する物は JavaやC# があります。
しかし、javascriptが緩めだからと言って意識しなくていい訳ではないのでご注意ください。
厳密な演算・比較を行う場合は、データ型の概念を意識しないといけません。

javascriptの主な種類

javascriptのデータ型には 基本型参照型 の二種類があります。

データ型

オブジェクト 概要 バージョン
数値型(number) 数値
文字列(string) シングル・ダブルクォートで囲まれた文字の集合
真偽値型(boolean) true(真)false(偽)
シンボル型(symbol) シンボル ES2015
特殊型(null/undefined) 値が空か未定義

参照型

オブジェクト 概要 バージョン
配列(array) データの集合(インデックス番号で参照可)
オブジェクト(object) データの集合(名前でアクセス可)
関数(function) 一連の処理の集合

データ型と参照型の違い

この2つの違いは 値を変数に格納する方法 になります。

データ型
number・string・boolean・symbol の様な基本型は変数に値がそのまま格納されます。

参照型
array・object・functionの様な参照型は 参照値を格納します。
※参照値→値を実際格納しているメモリ上のアドレス

データ型・参照型 によりスクリプトの挙動が異なるのでご注意ください!

あとがき

以上が、データ型と参照型 でした。
補足ですが、データ型はnew演算子によるインスタンス化が不要だったりします。

いかがでしたか?
役に立っていれば嬉しいです。
では!

Myリンク

また、Twitter・Portfolio のリンクがありますので、気になった方は
ぜひ繋がってください。プログラミング学習を共有できるフレンドが出来るととても嬉しいです。

Twitter
Portfolio
Github
Note

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

【初心者でもわかる】jQueryを使って、aタグを含むdivをクリックしてもリンクさせる方法(biggerlink)

どうも7noteです。aタグのクリックできる範囲を広げる方法について。

jQueryって何?という方はこちらの記事をどうぞ。

今回はjQueryで自作する方法と、HTML5だけで書く方法の2種類を紹介。

開発の環境

  • jQueryのバージョン ⇒ おそらくなんでもOK
  • プラグイン等 ⇒ 利用なし(自作)

何ができるの?

リンクエリアを拡大。(div要素にもaタグのような動きをつける。)

movie.gif

ソース

index.html
<div class="box biggerlink"> <!-- 任意のクラス名を付ける(例:biggerlink) -->
  <p>ココはpタグですよ。</p>
  <div><img src="sample.png"></div>
  <a href="https://www.google.com/">ここはaタグですよ。</a>
</div>
style.css
/* 装飾だけなので、CSSはなくてもOK */
.box {
  width: 300px;
  padding: 10px;
  background: #eeecda;
}

.box:hover {
  cursor: pointer;    /* ホバー時にカーソルを指?の形に変更 */
}

.box p {
  background: #f08a5d;
}

.box a {
  color: #eeecda;
  background: #b83b5e;
}
script.js
$(function(){
  $(".biggerlink").click(function(){
    window.location = $(this).find("a").attr("href");
  });
});

使い方は簡単

javascriptをコピペして、使いたいところの要素にbiggerlinkのクラスを指定するだけ。
その要素の中にあるaタグのとび先と同じになります。

解説

script.jsの$(".biggerlink")の部分がセレクタを指定しているので、biggerlinkというクラス名を変えることで、biggerlink以外のクラスにも同じ処理を入れることができます。

つぎにwindow.locationがWEBページの表示先を表すものです。
そして、$(this).find("a").attr("href");がクリックされた要素の中にあるaタグのhref属性の値を示しているので、日本語に直すと「クリックされた要素(biggerlink)のaタグのhrefのリンク先に飛びなさい」という命令になります。

別タブで開きたい時

script.jsを以下のように変更してください。

$(function(){
  $(".biggerlink").click(function(){
    /* 変更ここから */
    const url = $(this).find("a").attr("href");
    window.open(url, '_blank');
    /* 変更ここまで */
  });
});

実は・・・HTML5ではjsを使わなくても実装できる。

HTML5ではjsを使わずに、インライン要素(a)の中にブロック要素(div)を書くだけで実装できてしまいます。

index.html
<a href="https://www.google.com/">
  <div class="box biggerlink">
    <p>ココはpタグですよ。</p>
    <div><img src="sample.png"></div>
  </div>
</a>

HTML5からはaタグの中にブロック要素を入れてもOKになりました。というよりは、ブロック要素・インライン要素という分け方が少し古い考え方であり、HTML5の公式的な記述としては、インタラクティブ・コンテンツ(Interactive content)さえ含まなければaタグの中にはどのタグを書いても良いのです。

インタラクティブ・コンテンツ(Interactive content)
一言でいうなら、ユーザーが何かしらの入力やアクションを起こすような要素。
⇒ 「a audio* button details embed iframe img* input* keygen label menu* object* select textarea video*」※[*]は条件付き

まとめ

HTML5でaタグの範囲を大きくできるようになったので、正直biggerlinkのjsを使う機会はかなり減りました。
ですが、知識として知っておいて損はないかと思います!

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】WEB制作のちょいテク詰め合わせ

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

SapperもTypeScript�を公式サポートしたので試してみる!

はじめに

Svelteに引き続き、SvelteのフレームワークであるSapperもTypeScriptを公式サポートしたので試してみたいと思います!
Sapper 0.28でTypeScriptをサポートしたようです。

SvelteのTypeScript対応記事はこちら
SvelteがTypeScriptを公式サポートしたので試してみる!

Sapperとは

image.png

Sapper公式サイト

  • ReactでいうNext、VueでいうNuxtにあたるSvelteのフレームワーク
  • SSRをサポートしていたり、ルーティングをサポートしてるのですぐに開発ができる

VSCodeの拡張機能をインストールする

VSCodeを使って開発している方は、公式の拡張機能をインストールすることでTypeScriptの恩恵をより受けられるのでインストールしましょう!
Svelte for VS Code

Sapperプロジェクトを作成し、TypeScriptの設定をする

# テンプレートからsapperプロジェクトを作成
npx degit "sveltejs/sapper-template#rollup" my-app

# 作成したプロジェクトのディレクトリに移動
cd my-app

# TypeScriptの設定をするスクリプトを実行する
node scripts/setupTypeScript.js

# パッケージをインストール
npm install

# 開発サーバーを起動
npm run dev

これでSapper+TypeScriptで開発できる準備が整いました。

実際書いてみる

componentsフォルダ配下にHeader.svelteを作成

Header.svelte
<script lang="ts">
  export let userName: string
</script>

<header>
  <h1>Hello {userName}</h1>
</header> 

_layout.svelteにHeaderコンポーネントをimportして、型が効いているか試す為にNumberを渡す

_layout.svelte
<script lang="ts">
    import Nav from '../components/Nav.svelte';
    import Header from '../components/Header.svelte';

    export let segment: string;
</script>

<style>
    main {
        position: relative;
        max-width: 56em;
        background-color: white;
        padding: 2em;
        margin: 0 auto;
        box-sizing: border-box;
    }
</style>

<Header userName={24} /> 

<Nav {segment} />

<main>
    <slot></slot>
</main>

スクリーンショット 2020-10-06 20.30.12.png

VSCode上でエラーが確認できました!

コマンドラインで確認する

$ npx svelte-check

Loading svelte-check in workspace: sapper-typescript
Getting Svelte diagnostics...
====================================

sapper-typescript/src/routes/_layout.svelte:19:9
Error: Type 'number' is not assignable to type 'string'. (ts)

<Header userName={24}/> 


====================================
svelte-check found 1 error, 0 warnings and 0 hints

コマンドラインでもエラーが確認できました!

さいごに

SapperでもTypeScriptが公式サポートしたので更に開発しやすくなりましたね!

では、良いSvelte,Sapperライフを〜:triangular_flag_on_post:

参考

TypeScript support

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

(Javascript) 組み込みオブジェクトとnew演算子によるインスタンス化

はじめに

今回は、Javascriptの"超基本"
オブジェクト指向をテーマに 組み込みオブジェクトとnew演算子によるインスタンス化 について記録します。

過去に2点ブジェクトについて、記事を投稿しました。

(Javascript) オブジェクト指向 と オブジェクト&配列 の違い
(Javascript) インスタンス化 と コンストラクター
(Javascript) 静的プロパティと静的メソッド

組み込みオブジェクト とは

javascriptが公開しているオブジェクトのことです。
その中でも基本が 組み込みオブジェクト (Built-in Object) になります。
また 組み込み の意味は javascriptで標準に組み込まれた というモノです。

特徴

ブラウザーオブジェクト が 特定の環境でしか動かないことに対して 組み込みオブジェクトはjavascriptが動作する全ての環境で利用できることが、特徴です。
・組み込みオブジェクトは、自分でオブジェクトを定義できますが、特に 宣言・定義 をする必要はありません

主な組み込みオブジェクト

オブジェクト 概要 バージョン
(Global) jsの基本機能にアクセスする手段を提供
Object 全てのオブジェクトの雛形となる機能を提供
Array 配列を操作するための手段を提供
String 文字列を操作するための手段を提供
Boolean 真偽知を操作するための手段を提供
Number 数値を操作するための手段を提供
Function 関数を操作するための手段を提供
Math 数値演算を行うための手段を提供
Date 日付を操作するための手段を提供
RegExp 正規表現に関わる機能を提供
Error / XxxxxError エラー情報を管理
Map/WeakMap キー/連想配列を操作するための手段を提供 ES2015
Set/WeakSet 値の集合を管理する手段を提供 ES2015
Symbol シンボルを操作するための手段を提供 ES2015
Proxy オブジェクトの挙動をカスタマイズする手段を提供 ES2015
Promise 非同期処理をするための手段を提供 ES2015

Number・String・Boolean・Symbol は、Javascrptの主なデータ型に対応しています。

サンプルコード

以下では、Stringオブジェクトlengthプロパティと、オブジェクトを使用していないものの出力結果を比較しています。

// 文字をコンソールに出力
let greeting = 'Hello World';
console.log(greeting);
//出力結果→ Hello World

// Stringオブジェクトのlengthプロパティを使用
let text = 'Hello World';
console.log(text.length);
//出力結果→ 11

オブジェクトのインスタンス化の注意

先のコマンドを実行して疑問に思うことはありませんか?
そうです。Stringオブジェクトを使用しているのにインスタンス化をしていません。

しかし、javascriptでは リテラル(プログラムに直接記述するデータ値{データ型}) に対応する組み込みオブジェクトはインスタンス化をする必要がありません。

データ型ではインスタンス化は不要

リテラルに対応する組み込みオブジェクトはインスタンス化が不要と先に述べました。

しかし、データ型でもインスタンス化をしてオブジェクトを生成することは可能です。
ですが、↓のソースコードの様に

・記述が長くなる
・出力がおかしくなる

という問題が発生します。

new演算子によるオブジェクトの出力

let item = new Boolean(false); 

if(item) {
    console.log('itemはtrueです');
}
//出力結果→ itemはtrueです

※ falseを Boolean で設定しているのに、consoleが実行されてしまってます。

インスタンス化していないオブジェクトの出力

let item = false;

if(item) {
    console.log('itemはtrueです');
}
//出力結果→ not available

インスタンス化していない、出力が正しい挙動をしています。

その理由は、リテラル(データ型)にインスタンス化を行うと

null以外のオブジェクトはtrue とみなしてしまうからです。
もう一度!
null以外のオブジェクトはtrue とみなしてしまうからです。

なので、この記述は避けるべきです。
以上から、基本データ型をインスタンス化するのは避けてください。

あとがき

以上が、組み込みオブジェクトとnew演算子によるインスタンス化 でした。
いかがでしたか?
役に立っていれば嬉しいです。
では!

Myリンク

また、Twitter・Portfolio のリンクがありますので、気になった方は
ぜひ繋がってください。プログラミング学習を共有できるフレンドが出来るととても嬉しいです。

Twitter
Portfolio
Github
Note

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

ゲームを作りながら学ぶ!JavaScript レベルアップ講座 part3

こんにちは、Yuiです。

今回は前回に引き続きJavaScript道場で学んだthisについて解説をしていきます。

thisとは何かを理解する

まずは前回使ったこちらのコードを見てください。

var personA = {
  name: 'Bob',
  greeting: function() {
    alert('Hi! I\'m ' + this.name + '.');
  }
}

この中のthisが何なのかを確認する前に、thisを抜いたらどうなるのかを確認してみましょう

var personA = {
  name: 'Bob',
  greeting: function() {
    alert('Hi! I\'m ' + name + '.');
  }
}

この状態でpersonA.greeting()を見てみると、アラートにはHi! I'm .と表示されます。
つまり、Bobという名前が引き継がれていないことがわかります。
それはなぜかというと、thisがないとグローバル変数を参照してしまうからです。

もう少しわかりやすく書きます。

var name = 'Sam';
var personA = {
  name: 'Bob',
  greeting: function() {
    alert('Hi! I\'m ' + name + '.');
  }
}

この状態で再度personA.greeting()を見てみます。
すると、アラートがHi! I'm Sam.となるのが確認できたでしょうか。

つまりthisを使わないとpersonA内のnameではなく、その外側の全体に対して定義されているnameが表示されてしまうということです。

もう少しわかりやすく言うと、thisは今のスコープのオブジェクト自体を指します。

今回はpersonAというスコープ内のnameを使いたかったので、thisが必要だったということですね。
そこまでわかると、以下のコードでなぜthisが入っていたかもわかるのではないでしょうか。

function Person(name) {
  this.name = name;
  this.greeting = function() {
    alert('Hi! I\'m ' + this.name + '.');
  };
}

let personA = new Person('Bob');
let personB = new Person('Sarah');

Allow関数を理解する

thisについてもう少し深堀りするついでに、Allow関数に関しても解説をしたいと思います。

まずはこちらのコードを見てください。

var name = 'Sam';
var personA = {
  name: 'Bob',
  greeting: function() {
    console.log('Hi! I\'m ' + this.name + '.');
  },
  IntervalGreeting: function() {
    setInterval(this.greeting, 1000)
  }
}

アラートだと見てて鬱陶しいのでconsole.log()に変えました。これで1000ミリ秒ごとにコンソール上では繰り返しHi! I'm 〇〇.と出力されるようになります。

*setInterval()というのはデフォルトで設定されている関数でsetInterval(関数名, 指定のミリ秒)と書くことで、指定したミリ秒ごとに関数が繰り返されるというものです。

そこでpersonA.IntervalGreeting()を実行して見ると、うまく表示されないのがわかるでしょうか。
Hi! I'm Sam.となってしまい、nameの部分がグローバル関数であるSamになってしまっていることがわかります。ただ、personA.nameだと正しくnameはBobになっていますよね。

この違いは何でしょうか。

そこで重要になるのが、thisは今のスコープのオブジェクト自体を指すということです。
personA.nameの場合、今のスコープのオブジェクトは当然personAになりますよね。
ただ、personA.IntervalGreeting()内のthis.greetingが表す今のスコープのオブジェクトはIntervalGreetingということになります。

このままでは少しわかりにくいかもしれませんが、setInterval()部分は以下のようにも書けるということを考えるとわかりやすいのではないでしょうか。

var personA = {
  name: 'Bob',
  greeting: function() {
    console.log('Hi! I\'m ' + this.name + '.');
  },
  IntervalGreeting: function() {
    setInterval(
      function() {
        console.log('Hi! I\'m ' + this.name + '.');
      }, 1000
    )
  }
}

つまり、関数の中に関数が入っている入れ子状態なので、一番内側となる入れ子の中でthisをしたところで何を取得すれば良いのか、このコードでは判断がつかないということですね。

それではどうすれば良いのかというと、Allow関数を使えば解決します。
Allow関数を使う場合、このように書きます。

() => {}

そしてAllow関数はthisを持ちません。つまり、Allow関数を使った場合、一つ外側にthisを探しにいく(普通にスコープチェーンを辿る)ということです。

なので、今回は下記のように書けば問題は解決します。

var name = 'Sam';
var personA = {
  name: 'Bob',
  greeting: function() {
    console.log('Hi! I\'m ' + this.name + '.');
  },
  IntervalGreeting: function() {
    setInterval(() => { this.greeting() }, 1000)
  }
}

再度personA.IntervalGreeting()を実行してみると、グローバルで指定しているnameのSamではなく、正しくHi! I'm Bob.と表示されることがわかりますね。

まとめ

以上のことをまとめると以下のことになります。

  • thisはオブジェクトを指すが、どのオブジェクトを指すかはどこで関数が呼ばれるかによって決定する
  • Allow関数は自身でthisを持たない

Allow関数に関しては毎回使うべきというわけではなく、メソッドでない場合には最適の方法になります。(メソッドでは使わない方がいい場合が多い)

まだ少し混乱があるかもしれませんが、もし迷ったら、とりあえずconsole.log()でログを出力してみるということも重要な方法になるので、実践しつつ感覚を掴んでいってもらえればよいかと思います。

もしもっと学びたいという方はぜひJavaScript道場にご参加ください。
次回はcanvasを使って背景やモンスターを描画する方法を書いていきます。

それでは最後までお読みいただきありがとうございました。

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

Google Colabの制限を突破する話

忙しい人向け

・Google Colabの90分問題をどうにかするよ
・自動リロードはできないからコンソールからJavascriptで直接"再接続"を押すよ

function ClickConnect(){
    console.log("更新")
    document.querySelector("colab-connect-button").click()
}
setInterval(ClickConnect,3600*1000)

Google Colabの制限とは

google colaboratory、通称Google Colabでは無料でGPUが使えるなどの豊富なメリットがありますが、その代償としていくつかの制限が設けられてます。GPUのリソース制限、未操作90分による接続切れ、12時間の連続運転による接続切れ。

今回はそのうちの未操作90分による接続切れ、いわゆる90分問題をどうにかします

過去にできたこと

「自動リロードによるページの再読み込み→自動接続」による接続切れ回避を前まではできるようになってましたが、現在は変更の破棄をするかどうか聞かれるダイアログにより行えなくなりました。
Colab90-0.png

ちなみに同じようにリロードするとでてくるダイアログにあるチェックボックスにチェックを入れても無意味なようです。
Colab90-1.png

ローカル上のスクリプト経由による自動リロードについては未検証です。

今回やること

なので今回は別の手法としてページ上でなにかしらの作業を定期的にさせるようなスクリプトを置きます。

実行中のセルに影響が出なければ問題はないはずですが、万が一ネットワークの不調などで接続が切れてしまった時のために右上の再接続ボタンを押すようにします。
Colab90-2.png

コードは上記の通り。あとはブラウザのコンソールを立ち上げ(やり方はブラウザごとに違うので各自検索をお願いします)、コードを入力すれば完了です。

60分間に一回ボタンを押し、コンソール上のログに”更新”と出力します。

ちなみに、普段は再接続ではなく接続済みとなってるはずなので問題が起きなければアクティブなセッションの一覧が定期的に表示されるはずです。

自動リロードが通らない原因

どうやらセルのログが更新されるたびにページに変更が発生するようで、それをブラウザ側がありがた迷惑に確認してくるのが原因のようです。

参考元

https://qiita.com/enmaru/items/2770df602dd7778d4ce6
https://qiita.com/Gimina_Graph/items/4bdf3e3c3658eeb0eba8
https://flat-kids.net/2020/07/28/google-colab-%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%87%E3%82%8C%E3%82%92%E9%98%B2%E6%AD%A2%E3%81%99%E3%82%8B/

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

使用したメソッドまとめてみた

アプリ制作の際、使用したメソッドを忘れないように都度追加していこうと思います。

● order
orderメソッドとは、レコードを並び替える事ができるメソッドです。
.order(id: "DESC")のように記述すると、idをDESC(降順)で並び替えるという意味になります。
DESC(降順)の逆はASC(昇順)です。orderメソッドを使用しないデフォルトでは、ASCになっています。

メモの順番を降順にする場合。
orderメソッド.png

これで、メモを降順で表示できるようになりました。

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

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

ゲームを作りながら学ぶ!JavaScript レベルアップ講座 part2

こんにちは、Yuiです。

今回は前回に引き続きJavaScript道場で学んだオブジェクト指向について解説をしていきます。

オブジェクト指向とは

まずオブジェクトとは、オブジェクトとは、名前と値をもつプロパティを格納する入れ物にすぎません。
つまり、オブジェクト=データの入れ物です。

それではオブジェクト指向とは何かというと、簡単にいうとある一連の動作をまとめたものを指します。

例えば、AさんとBさんの「歯を磨く動作」をプログラミングするとして考えると、以下の動作はAさん、Bさん関係なく共通のものですよね。

  • 歯ブラシを手に取る
  • 歯磨き粉をつける
  • 歯ブラシを口に入れる
  • 歯ブラシを口の中で動かす
  • 歯ブラシを口から出す
  • うがいをする

JavaScriptで言うなら一つの動作は関数で表すことができますが、共通で使う複数の関数があるならまとめたほうが楽ですよね。
それをまとめて書くようにしたのがオブジェクト指向プログラミングです。

実際にオブジェクト指向プログラミングで書いてみよう

それでは実際にオブジェクト指向で書いてみましょう。
MDNから引用)
例えばAとBの人物を用意して、Hi! I'm 〇〇.とアラートを出すだけの簡単なプログラムを書いてみます。

var personA = {
  name: 'Bob',
  greeting: function() {
    alert('Hi! I\'m ' + this.name + '.');
  }
}

var personB = {
  name: 'Sarah',
  greeting: function() {
    alert('Hi! I\'m ' + this.name + '.');
  }
}

この状態でpersonA.greeting()と入力すれば、Hi! I'm Bob.というアラートが出るという簡単なプログラムです。

ただ、これ2人だけならこのままでも良いかもしれませんが、100人、200人となると毎回この共通の部分をコピペするのはあまり好ましくないですよね。
そこで共通の部分をオブジェクト指向で書いてみます。

function Person(name) {
  this.name = name;
  this.greeting = function() {
    alert('Hi! I\'m ' + this.name + '.');
  };
}

let personA = new Person('Bob');
let personB = new Person('Sarah');

上記のように簡単に書くことができました。そうすることで今後人物を追加する際はlet 〇〇 = new Person('name')とすれば良いだけなので非常に簡単ですね。

ちなみに今回は簡単な例だったのですが、これから作るゲームではオブジェクト指向で書かないとコードがかなり冗長になってしまうので、今後学んでいけばよりオブジェクト指向の便利さを理解できるかと思います。

また、今回さらっと書いた上記のコードに中にthisがありますが、これがわからない人も多いのではないでしょうか?
thisは慣れれば感覚的に使えるのですが、JavaScriptで書く上では欠かせないものになるので、次回はthisとは何かについて解説をしていきたいと思います。

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

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

ゲームを作りながら学ぶ!JavaScript レベルアップ講座 part1

こんにちは、Yuiです。

今回x-hackさんのJavaScript道場でゲーム作りを勉強しました。
そこで、今回からJavaScriptでフルスクラッチのRPGを作っていく流れを連載形式で書いていきたいと思います。

x-hackさんのJavaScript道場への申込みはこちら

今回の連載の流れ

以下の流れで書いていきたいと思います。

1、なぜ設計は大事なのか
2、オブジェクト指向を理解しよう
3、thisとは?なぜthisを使う?
4、canvasとは?canvasを使って背景を描画、モンスターを配置する
5、モンスターを動かしてみる
6、メッセージ表示エリアを配置する
7、プレーヤーのHPを表示する
8、プレーヤーのコマンドを表示する
9コマンドをクリックしたら、敵に対してなにかの処理を行う
10、敵が攻撃したら、プレーヤーのHPをへらす
11、敵のHPか、プレーヤーのHPが0になったら戦闘終了

今回の記事では1、なぜ設計は大事なのかの部分について書きます。

なぜ設計は大事なのか

まずアプリ作りにおいて基本となるのが設計です。
「設計」とは何をすれば良いのかというのも後ほど解説しますが、それ以前に「なぜ」設計しないといけないのかを解説したいと思います。

はじめてプログラミングをする方などは考えるより先にコーディングしたほうが良い大体の案が決まったのなら作りながら設計をしていったほうが効率的だ、そう考える人もいるのではないでしょうか?

実は私自身そのように考えて、大体の方向性だけ考えたら特に必要なDBとか何も考えずに先に進めていた時期がありました。
すると、何が起こるかというと、その後で上手くいかないことがあった場合に逆戻りしにくくなります。

どういうことかというと、途中で以下のことが起こります。

  • 「ここまで書いたのに」という気持ちもあって元に戻せなくなる
  • どこを完成にするかがわからなくて永遠に完成しない
  • 理想と違う部分が出てくる
  • そもそも何を目指していたかわからなくなる

人間誰しも自分が書いたコードには愛着がわきますよね?設計が甘かったら、その愛情持って育てた愛着のあるコードを全消し、なんてこともありえます。
そして恐ろしいのは、全消ししたところで完成するのか見通しが立たないので消すのも怖くなります。
また、設計が甘いということは、目標とする部分がないということなので、どこまでで完成かもわからなくなります。
そして結局最後まで完成できずに時間だけが無駄になったという負のスパイラルが起こります。

設計とは何か

それでは、設計の重要性がわかったところで、設計を進めていきたいのですが、設計とはそもそも何をすれば良いのでしょうか。

仕事レベルの設計という観点で考えると(内部設計、外部設計、それに伴う調査など色々とあるので)少し語弊があるのですが、個人開発の観点で言えば簡単に言うと以下の流れで組めれば十分だと思います。

  • MVP(Minimum Viable Product)を決める
  • +αで追加できると良い機能を決める
  • 必要となるDBを設計する(今回は不要)
  • ターゲット層を決める
  • デザインを決める
  • ゴールを決める

MVPとは

MVPというのは「必要最低限の機能」のことです。
これを決める必要がなぜあるのかというと、どんなプロダクトでも作れば作るほどつけたい機能がどんどん出てきます。そのすべてを実装しようと思うと、かなりの時間がかかりますね。

時間をかけることが悪だというわけでは無いのですが、もしかしたらその時間をかけている間に他の人が同じようなサービスをリリースするかもしれませんし、「旬」が終わってるかもしれません。

例えば絶対に売れると思ったサービスを時間をかけてリリースしたものの思った以上に伸びず、せっかくリリースしたのにすぐにサービス終了にする、なんてこともありえます。(私は一度経験しました。)

何でも美味しいものには旬があるようにサービスにも旬があります。
なので、一番良いのはとりあえず必要最低限の機能でリリースをして、随時バージョンアップなどで追加の機能をつけていくことが望ましいかと思います。

ただ、MVPとは言ってもサービスによりけりです。
なので、まずは目標となるサービスの大枠を考えて必要な機能を全て出します。
そしてその機能がMVPなのか、それとも+αで追加できるとより良い機能なのかを見極めます。

例えば今回のPRG風ゲームだと、以下がMVPにあたります。

  • モンスター/プレイヤーを表示
  • ステータスバーを表示
  • メッセージ表示エリアを表示
  • プレイヤーを動かせるように
  • 攻撃を受けたらステータスバーが減るように
  • HPが0になったらゲームオーバーを表示

全体の設計はこちらを見てもらえればわかりやすいかと思います。

上記のMVPだけだと正直物足りないと感じることもあるでしょうが、とりあえず上記の実装ができれば最低限のゲームとしては問題ないかと思います。

デザインを組む

そして大体の機能の洗い出しが完了したらデザインを組みます。
デザインの組み方が人それぞれだと思うので、何でも良いのですが、大体のイメージをもっておくことは大事です。

私の場合はいつもターゲット層を決めてからそれに合わせてフォントやテーマカラーを選びます。

カラーの選定はMATERIAL DESIGNをいつも私は使っていますが、何でも大丈夫です。

デザインはPinterest等で色々参考になるものを見ることができます。

DB設計

今回DBは不要なので割愛しますが、必要であればDB設計をきちんと設計することも重要です。

DBが必要なアプリなのに必要なDBを洗い出していないと、後々になってカラムの追加に追われたり整合性がとれなくなるので、必ず最初にしておきましょう。

今回は割愛します。

そして設計が大体できたところで、実際に組んでいきたいと思います。
今回はオブジェクト指向で組みたいので、次回はオブジェクト指向についての解説をします。

もしもっと詳細に知りたい!勉強したい!という方はぜひJavaScript道場にお越し下さい。

それでは最後までお読みいただきありがとうございました。

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

ワンクリックでタイトルとURLをいい感じにコピーする

やりたいこと

Webページを共有するときに、URLだけじゃなくてタイトル付きでコピーしたい。

これじゃなくて
https://qiita.com/
これをコピーしたい
Qiita
https://qiita.com/

結論

以下のコードをブックマークレットとして保存して実行する。

bookmarklet
javascript:(function(){const e = document.createElement('textarea');e.value = `${document.title}\n${location.href}\n`;document.querySelector('body').append(e);e.select();document.execCommand('copy');e.remove();})();

前提

  • いちいち Console 開いてコード書いたりSnippet実行はしたくない。クリックでどうにかしたい。

   -> ブックマークレットを使う。

   -> textareaを使う。

結果

こんなコードを実行すれば解決!

(function() {
    const e = document.createElement('textarea');
    e.value = `${document.title}\n${location.href}\n`;
    document.querySelector('body').append(e);
    e.select();
    document.execCommand('copy');
    e.remove();
})();

ブックマークレットにコピーするためのコードはこちら。

bookmarklet
javascript:(function(){const e = document.createElement('textarea');e.value = `${document.title}\n${location.href}\n`;document.querySelector('body').append(e);e.select();document.execCommand('copy');e.remove();})();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bolt for JSでワークフロービルダーのオリジナルステップを実装する

はじめに

Bolt for JSでは、Slackのワークフロービルダーで使えるWorkflow Stepsを実装することができます。(以下、Custom Stepと呼びます。)

リファレンスがありますが日本語化がまだのため、私が実装した経験から解説していきます。

事前設定

前提としてSlackアプリを作成するのですが、アプリの作成は様々な記事があがっているのでそちらを参照してください。

Custom Stepを作成するために必要な設定は以下の2点です。

ワークスペースでWorkflow stepsの利用を許可する

ワークスペースの設定 > 権限 > アプリからのワークフロービルダーステップ
「ワークフロービルダーで、このワークスペースにインストールされたアプリのステップを表示する」にチェック。

設定1

アプリのWorkflow Steps

アプリのWorkflow Stepsの「Turn on workflow steps」をONの状態にします。
ON状態にすると、workflow.steps:executeのスコープが必ず追加されるため、アプリのReinstallが促されます。
Reinstallしておきましょう。

設定2

Stepsの「Add Step」から追加するStepを登録します。

設定3.png

  • Step name
    • 追加するCustom Stepの名前
  • Callback ID
    • あとで実装するCallback IDと一致させる

Create up to 10 custom steps for your app to be used within Workflow builder.

Custom Stepは1つのアプリに対して10こまでしか実装できないみたいですね。

実装していく

実装の基本の形は

app.js
const { App, WorkflowStep } = require('@slack/bolt');

const app = new App({
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  token: process.env.SLACK_BOT_TOKEN,
});

const wsEdit = ({ ack, step, configure }) => {};
const wsSave = ({ ack, view, update }) => {};
const wsExecute= ({ step, complete, fail, client }) => {};

const ws = new WorkflowStep('custom_step', {
  edit: wsEdit,
  save: wsSave,
  execute: wsExecute,
});
app.step(ws);

です。
'custom_step'の部分が事前設定にあるCallback IDと一致するようにします。

edit(wsEdit), save(wsSave), execute(wsExecute)のそれぞれの役割とサンプルコードを解説します。

edit(wsEdit)

ワークフロービルダーで、Custom Stepを追加したり、編集したりするときに実行されます。
カスタムステップの設定をするモーダルを定義し、開きます。

サンプルコード
app.js
const wsEdit = ({ ack, step, configure }) => {
  await ack();

  const blocks = [
    {
      type: 'input',
      block_id: 'title_input',
      element: {
        type: 'plain_text_input',
        action_id: 'title',
      },
      label: {
        type: 'plain_text',
        text: 'タイトル',
      }
    },
    {
      type: 'input',
      block_id: 'message_input',
      element: {
        type: 'plain_text_input',
        action_id: 'message',
        multiline: true,
      },
      label: {
        type: 'plain_text',
        text: 'メッセージ',
      },
    }
  ];

  const { inputs } = step;
  const title = inputs.title;
  const message = inputs.message;

  if (title !== void 0) {
    blocks[0].element.initial_value = title.value;
  }
  if (message !== void 0) {
    blocks[1].element.initial_value = message.value;
  }

  await configure({ blocks });
};

順に説明します。

await ack();

応答です。とりあえず返しておきましょう。

const blocks = [
  // 省略
];

モーダルに表示するBlockを定義します。
Block Kit Builderを使うと便利です。(Modal Previewで!)

const { inputs } = step;
const title = inputs.title;
const message = inputs.message;

if (title !== void 0) {
  blocks[0].element.initial_value = title.value;
}
if (message !== void 0) {
  blocks[1].element.initial_value = message.value;
}

この部分は、編集モードで開いたときの初期値を定義しています。
これがないと、編集で開いたときに前回保存した内容が表示されません。

inputsはこの後にでてくるsave(wfSave)で定義しています。
(この部分はなんかもっとうまいやり方があるかもしれません。)

await configure({ blocks });

最後にconfigureblocksを渡すことで、モーダルが表示されます。

save(wsSave)

Custom Stepで、表示されたモーダルの「保存する」をクリックした時に実行されます。
モーダルからの値を受け取って、利用します。

サンプルコード
app.js
const wsSave = async ({ ack, view, update }) => {
  await ack();

  const { values } = view.state;
  const title = values.title_input.title;
  const message = values.message_input.message;
  const inputs = {
    title: { value: title.value },
    message: { value: message.value },
  };

  const outputs = [
    {
      type: 'text',
      name: 'title',
      label: 'タイトル',
    },
    {
      type: 'text',
      name: 'message',
      label: 'メッセージ',
    },
  ];

  await update({ inputs, outputs });
}

順に説明します。

await ack();

応答です。とりあえず返しておきましょう。

const { values } = view.state;
const title = values.title_input.title;
const message = values.message_input.message;
const inputs = {
  title: { value: title.value },
  message: { value: message.value },
};

view.state.valuesにはモーダルに入力した値がわたってきます。

それをもとにinputsを構成します。
それぞれvalueに値を設定すればよいです。

このinputsはedit(wsEdit)でstep.inputsにわたります。

inputsを詳しく知りたい方はリファレンスを参照ください。
※私にはvariablesを使うシーンがよくわかりませんでした:frowning2:

const outputs = [
  {
    type: 'text',
    name: 'title',
    label: 'タイトル',
  },
  {
    type: 'text',
    name: 'message',
    label: 'メッセージ',
  },
];

outputsには後続のワークフローステップにわたす変数を定義します。
(変数を挿入するをクリックしたときにでてきます。)
変数を挿入する

typeにはtext,channel,userのいずれかが入ります。

実際に渡す値はexecute(wsExecute)で設定します。

await update({ inputs, outputs });

最後にupdateinputsoutputsを渡すことで、モーダルが表示されます。

execute(wsExecute)

ワークフローが実際にCustom Stepに到達した際に実行されます。

サンプルコード
app.js
const wsExecute = async ({ step, complete, fail, client }) => {
  const { inputs } = step;

  const outputs = {
    title: inputs.title.value,
    message: inputs.message.value,
  };

  try {
    await client.chat.postMessage({
      channel: '###',
      blocks: [
        {
          type: 'header',
          text: {
            type: 'plain_text',
            text: inputs.title.value,
            emoji: true,
          }
        },
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: inputs.message.value,
          }
        },
      ]
    });
    await complete({ outputs });
  }
  catch (e) {
    fail({ error: { message: 'fail...' } });
  }
}

順に説明します。

const { inputs } = step;

const outputs = {
  title: inputs.title.value,
  message: inputs.message.value,
};

save(wsSave)で定義したoutputsに対して、次のステップにわたす値を設定します。

try {
  // 省略
  await complete({ outputs });
}
catch (e) {
  fail({ error: { message: 'fail...' } });
}

tryで処理を実行します。
サンプルコードでは、Custom Stepで設定したタイトルとメッセージを固定のチャンネルにポストしています。

最後に

await complete({ outputs });

でoutputsを次のステップにわたします。

エラーがでた場合は

fail({ error: { message: 'fail...' } });

でエラーメッセージを返します。
おそらく、ワークフローのアクティビティにエラーメッセージが表示されるのだと思います。(未確認)

さいごに

これでCustom Stepが実装できました!
ワークフロービルダーでちょっと手の届かないところだけサクッと作れるので、大変重宝しています。

アプリのショートカットはabc順で並ぶので、探すのが大変ですが、ワークフローは一番上に表示されるので、その点でもワークフロービルダーでサクッと作ってしまいたいですよね。

汎用的に使えそうなCustom Stepを複数作ってみて、Slack Lifeを充実させましょう!

最後になりましたが、間違っているところなどありましたら、ばしばしご指摘くださいませ。

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

[JavaScript]DOM操作のまとめ

こんにちは!
JavaScriptのDOM操作を使って画面操作を行う事があるのですが、よく忘れてしまうのでまとめてみました。

このまとめはDOM操作の使い方と個人的によく使うDOM操作の覚え書きになっています。
DOM操作がよくわからん!という方は最初から読んで下さい。

DOMとは

  • DOMとは、Document Object Modelの略で、HTMLやXML文書のためのプログラミングインターフェイスです。

  • DOMではHTML、XMLドキュメントをオブジェクトのツリー状の集合として取り扱います。
    このツリーをDOMツリーと呼びます。

DOM操作をする事で何が出来るの?

  • 文書構造、スタイル、内容を変更することが出来るので、ユーザーに視覚的なフィードバックを与える事が出来ます。 Javascriptで動的にウィンドウを閉じてみたり、クリックすると色を変えるなど色々出来ます。

やり方

大きい流れは以下の通りです。

  1. ドキュメント内の要素をメソッドで取得する。
  2. 取得した要素をプロパティで変更する。

実際にやってみよう

だらだら説明を続けてもよくわからないので、実際にDOM操作でh1要素のタイトルを取得して変更する作業をしてみましょう。

要素取得とプロパティの変更

まず以下のhtmlを用意します。

<html>
  <head>
    <title>DOMの操作練習</title>
  </head>
  <body>
    <h1 id="title">DOMの操作練習</h1>
  </body>
</html>

以下のようにh1のテキストが表示されていますね。

スクリーンショット 2020-10-05 0.10.30.png

以下のようにJavaScriptのコードを追加してみます。

<html>
  <head>
    <title>DOMの操作練習</title>
  </head>
  <body>
    <h1 id="title">DOMの操作練習</h1>
  </body>
  <!-- 以下を追加 -->
  <script>
    // 1.DOM操作する対象の選択
    const title = document.getElementById('title');
    // 2.要素の変更
    title.textContent = 'タイトルを変更しました';
  </script>
</html>

すると以下のようにh1要素のテキストが変更されます。

スクリーンショット 2020-10-05 0.09.59.png

一体何が行われたのか?

1.DOM操作する対象の選択
2.要素の変更
について詳しく見ていく事にします。

1.DOM操作する対象の選択

まずは、操作したい対象を選択しています。以下のコードを見て下さい。

const element = document.getElementById('title');

Documentオブジェクト

ここで出てくるdocumentは、Documentオブジェクトというものです。
Documentオブジェクトは、そのHTMLドキュメント全体を表現するオブジェクトになります。
JavaScriptでDocumentオブジェクトは、documentというグローバル変数でアクセス出来ます。

要素の取得

getElementById('title')でtitleというIDを持つ要素を取得します。
IDはDOMツリーの中で一意でなければなりません。

2.要素の変更

先程取得した要素のテキストを変更します。
textContentというプロパティを使えば、指定した要素のテキスト部分のみを取り出すことができます。

element.textContent = "タイトル変更";

1行でも書けるよ

説明しやすいので2行で書きましたが1行でつなげて書く事も出来ます。

document.getElementById('title').textContent = 'タイトルを変更しました';

イベントを使ったDOM操作

実際の業務では、イベントと組み合わせて使う事が多いです。
イベントとは「ボタンが押されたら」とか「送信されたら」といった動作の事です。
例えば以下の例は「取得した要素がクリックされたらlogを出力してね」というコードになります。

document.getElementById('title').onclick = function() {
  console.log('ログ出力'); 
}

getElementByIdで取得したtitleをクリックするとログ出力する事が出来ました。

スクリーンショット 2020-10-05 22.56.26.png

よく使うDOM操作のまとめ(随時更新)

ドキュメント全体からspan要素だけを取得

document.getElementByTagName('span');

ドキュメント全体から全ての要素を取得

document.getElementByTagName('*');

取得した要素のclassを追加

document.getElementById(id).classList.add(class);
document.getElementById(id).classList.add(class1, class2);

classを削除

document.getElementById(id).classList.remove(class);

classをもっているか判定

document.getElementById(id).classList.contains(class);

指定したクラスをもっていれば消す、もっていなければ追加。

document.getElementById(id).classList.toggle(class);

取得された要素がクリックされたタイミングで起動

document.getElementById('title').onclick = function() {
  // 処理内容
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React用語まとめ

今回はReactの基本的な用語をまとめます。
Reactで出てくる用語が分からない時の参考になれば幸いです。

SPA(シングルページアプリケーション)

これは、単一のHTMLページでアプリケーションの実行に必要なすべてのアセット(JavaScriptやCSSファイルなど)をロードすることができるアプリケーションです。ページのリロードが発生しないため、ユーザーが快適に使用することができます。
また、全てをシングルページアプリケーションで構築せずとも、既存のウェブサイトの一部分だけを拡張し使用することも可能です。

ES6、ES2015、ES2016 など

これらの頭字語は全て、ECMAScript言語の標準仕様の最近のバージョンのことを指しています。JavaScript言語とはこれらの仕様に対する実装です。また、アロー関数やクラス、テンプレートリテラル、let, constステートメントなどはES6バージョンで追加されており、できることの幅が大きく変化しています。

コンパイル

プログラミング言語を、動作する機械が理解できるように翻訳する作業のことです。

コンポーネント

ページに表示されるReact要素を返す、小さく再利用可能なコードのことです。再利用できることは、プログラミングにおいてとても重要です。

種類

class component

クラスによって定義されたコンポーネント

  • React.conponentを継承する。
  • ライフサイクルやstateをもつ。
  • propsにはthisが必要である。
  • renderメソッド内でJSXをreturnする。

functional component

関数型で定義されたコンポーネント

  • ES6のアロー関数で記述する。
  • stateをもたない。
  • propsを引数に受け取る。
  • JSXをreturnする。

props

Reactコンポーネントへの入力のことです。親コンポーネントから子コンポーネントへと順番に渡されるようなデータです。
また、読み取り専用なため、変更されるべきではありません。

state

コンポーネント内で管理する変数のことです。
あるコンポーネントが時間とともに変化するデータと関連付けられている場合はstateが必要です。
stateとpropsの大きな違いは、propsは親コンポーネントから渡されますが、stateはコンポーネント自身によって管理されるということです。コンポーネントは自身のpropsを変更することができませんが、state は変更することができます。

ライフサイクルメソッド

コンポーネントの様々なフェーズにおいて実行される特別な関数です。
コンポーネントの時間の流れは、生まれて、成長して、消えていくまでの循環です。それぞれの段階で必要な処理を記述します。

Mounting→Updating→Unmounting

これをなぜ使うのかと言いますと、関数の外に影響を与える関数を記述するためです。

最後に

Reactを学習する上で登場する基本的な用語を自分なりにまとめてみました。また、Reactの特徴の一つであるJSXについては別の記事にて書かせていただいておりますので、そちらも見て頂けたら幸いです。

URL : https://qiita.com/Taisei-rgb/items/137f24d6256d71bab90a

ここまで記事を読んでいただきありがとうございました。React学習中の方の少しでも参考になれば幸いです。今後もフロントエンドをメインにアウトプットをしていきますので、今後ともよろしくお願い致します。

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

今更ながらJavascriptでSOAPを通してxmlをparseする

GETALLUSERS.gif

やりたいこと

タイトルの通りで申し訳ないのですが、webserviceを呼び出そうとしたらRESTではなくSOAPだったんですね。。
RESTやJSONだと簡単にできてたところがなかなか難しかったりなので備忘録です。

投げるところ

今回はYellowfinのGETALLUSERS(クリックで技術仕様ページ)というサービスに投げます。SOAPでのリクエストやレスポンスが載っているのですが、基本的にはJavaでアクセスさせたいらしく、WSDLはあるもののJAVAのサンプル中心でどうしたらいいのかよくわからないです。

前準備

ボタンとイベントリスナーを作るために、widgetからボタンを設置し、nameをGETALLUSERSにしときます。

突然ですが全ソースです

見てもらえばわかると思うのですが、リクエストのXMLを文字列で構築して、XMLHttpRequestで投げているだけです。
リクエストが成功した時だけ、useridがある分アラートで表示している感じですね。

ここでのハマリポイントはparseの仕方だったのですが、基本的にはfind()でタグを取得して内容が1つならそのまま処理、複数ならeachで個別処理という流れになります。タグの要素名でも検索できるようなのですが、割愛します。

あとはヘッダーの設定をしっかりするということでしょうか。アプリケーションの設定かもしれないのですが、投げるのがテキストのxmlなのでContent-Typeにtext/xmlを設定して、SOAP通信であるというheaderのSOAPActionを設定しないと通信できませんでした(中身は空で大丈夫らしいです謎)

@htsign さんからのご指摘で簡略化することができました。ありがとうございます。

JSタブ
    let button = this.apis.canvas.select('GETALLUSERS');
    button.addEventListener('click', () => {

        const xhr = new XMLHttpRequest();
        xhr.open('POST', 'http://localhost:8922/services/AdministrationService');
        var sr = '<?xml version="1.0" encoding="utf-8"?>' +
        '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://webservices.web.mi.hof.com/">' +
        '<soapenv:Header/>' +
        '<soapenv:Body>' +
        '<web:remoteAdministrationCall>' +
        '<arg0>' +
        '<loginId>admin@yellowfin.com.au</loginId>' +
        '<password>test</password>' +
        '<orgId>1</orgId>' +
        '<function>GETALLUSERS</function>' +
        '</arg0>' +
        '</web:remoteAdministrationCall>' +
        '</soapenv:Body>' +
        '</soapenv:Envelope>';
        xhr.responseType = 'document';
        xhr.onload = () => {
            xhr.response.querySelectorAll('multiRef').forEach(el => {
                const text = el.querySelector('userId')?.textContent;
                if (text) alert(text);
            });
        };
        // Send the POST request
        xhr.setRequestHeader('Content-Type', 'text/xml');
        xhr.setRequestHeader('SOAPAction', '');
        xhr.send(sr);

ちなみに生responseXMLはこんな感じです

やっぱり見づらいですよね、RESTをくれ・・・。Yellowfin9.3ではadminサービスはRESTでほぼ網羅されるという話を聞いたのですがどうなんでしょうね。

response.xml
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
    <ns1:remoteAdministrationCallResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://webservices.web.mi.hof.com/">
      <remoteAdministrationCallReturn href="#id0" />
    </ns1:remoteAdministrationCallResponse>
    <multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns2:AdministrationServiceResponse" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns2="http://service.web.mi.hof.com">
      <binaryAttachments xsi:type="ns2:ArrayOfReportBinaryObject" xsi:nil="true" />
      <binaryData xsi:type="soapenc:string" xsi:nil="true" />
      <client xsi:type="ns2:AdministrationClientOrg" xsi:nil="true" />
      <clients xsi:type="ns2:ArrayOfAdministrationClientOrg" xsi:nil="true" />
      <contentResources xsi:type="ns2:ArrayOfContentResource" xsi:nil="true" />
      <contentType xsi:type="soapenc:string" xsi:nil="true" />
      <datasources xsi:type="ns2:ArrayOfAdministrationDataSource" xsi:nil="true" />
      <entityId xsi:type="soapenc:int" xsi:nil="true" />
      <errorCode href="#id1" />
      <fileName xsi:type="soapenc:string" xsi:nil="true" />
      <group xsi:type="ns2:AdministrationGroup" xsi:nil="true" />
      <groups xsi:type="ns2:ArrayOfAdministrationGroup" xsi:nil="true" />
      <importIssues xsi:type="ns2:ArrayOfImportIssue" xsi:nil="true" />
      <loadedDataSource xsi:type="ns2:AdministrationDataSource" xsi:nil="true" />
      <loginSessionId xsi:type="soapenc:string" xsi:nil="true" />
      <messages soapenc:arrayType="soapenc:string[2]" xsi:type="soapenc:Array">
        <messages xsi:type="soapenc:string">Successfully Authenticated User: admin@yellowfin.com.au</messages>
        <messages xsi:type="soapenc:string">Web Service Request Complete</messages>
      </messages>
      <parentDashboard xsi:type="ns2:ParentDashboard" xsi:nil="true" />
      <parentDashboards xsi:type="ns2:ArrayOfParentDashboard" xsi:nil="true" />
      <parentReportGroups xsi:type="soapenc:Array" xsi:nil="true" />
      <people soapenc:arrayType="ns2:AdministrationPerson[2]" xsi:type="soapenc:Array">
        <people href="#id2" />
        <people href="#id3" />
      </people>
      <person xsi:type="ns2:AdministrationPerson" xsi:nil="true" />
      <personfavourites xsi:type="ns2:ArrayOfPersonFavourite" xsi:nil="true" />
      <queryResults xsi:type="ns2:ArrayOfReportRow" xsi:nil="true" />
      <report xsi:type="ns2:AdministrationReport" xsi:nil="true" />
      <reportGroups xsi:type="ns2:ArrayOfAdministrationReportGroup" xsi:nil="true" />
      <reportId xsi:type="soapenc:int" xsi:nil="true" />
      <reports xsi:type="ns2:ArrayOfAdministrationReport" xsi:nil="true" />
      <roles xsi:type="ns2:ArrayOfAdministrationRole" xsi:nil="true" />
      <schedule xsi:nil="true" />
      <schedules xsi:type="ns3:ArrayOfAdministrationSchedule" xsi:nil="true" xmlns:ns3="http://schedule.service.web.mi.hof.com" />
      <sessionId xsi:type="soapenc:string">2967dba4dc0843a35428173d9acc390c</sessionId>
      <statusCode xsi:type="soapenc:string">SUCCESS</statusCode>
    </multiRef>
    <multiRef id="id3" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns4:AdministrationPerson" xmlns:ns4="http://service.web.mi.hof.com" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
      <emailAddress xsi:type="soapenc:string" xsi:nil="true" />
      <firstName xsi:type="soapenc:string">celery</firstName>
      <initial xsi:type="soapenc:string" xsi:nil="true" />
      <ipId href="#id4" />
      <languageCode xsi:type="soapenc:string" xsi:nil="true" />
      <lastName xsi:type="soapenc:string">test</lastName>
      <password xsi:type="soapenc:string" xsi:nil="true" />
      <roleCode xsi:type="soapenc:string">PUBLICCONTENTWRITERCOLLABORATORADVANCED</roleCode>
      <salutationCode xsi:type="soapenc:string" xsi:nil="true" />
      <status xsi:type="soapenc:string">ACTIVE</status>
      <timeZoneCode xsi:type="soapenc:string" xsi:nil="true" />
      <userId xsi:type="soapenc:string">admin2@yellowfin.com.au</userId>
    </multiRef>
    <multiRef id="id1" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:int" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">0</multiRef>
    <multiRef id="id2" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns5:AdministrationPerson" xmlns:ns5="http://service.web.mi.hof.com" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
      <emailAddress xsi:type="soapenc:string" xsi:nil="true" />
      <firstName xsi:type="soapenc:string">System</firstName>
      <initial xsi:type="soapenc:string"></initial>
      <ipId href="#id5" />
      <languageCode xsi:type="soapenc:string" xsi:nil="true" />
      <lastName xsi:type="soapenc:string">Administrator</lastName>
      <password xsi:type="soapenc:string" xsi:nil="true" />
      <roleCode xsi:type="soapenc:string">YFADMIN</roleCode>
      <salutationCode xsi:type="soapenc:string" xsi:nil="true" />
      <status xsi:type="soapenc:string">ACTIVE</status>
      <timeZoneCode xsi:type="soapenc:string" xsi:nil="true" />
      <userId xsi:type="soapenc:string">admin@yellowfin.com.au</userId>
    </multiRef>
    <multiRef id="id4" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:int" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">13150</multiRef>
    <multiRef id="id5" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:int" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">5</multiRef>
  </soapenv:Body>
</soapenv:Envelope>

本当は、

レポートのサービスでExcelファイルをバイナリで取得してJavascriptだけでファイルダウンロードしたかったのですが、どうしてもバイナリファイルが壊れてしまってExcelファイルが開けなかったのでこれは誰かやり方を教えて下さい゚゚(:3」∠)
Base64エンコードされたバイナリファイルが落ちてくるんですがなんか壊れてるんですよね。Excelっぽい内容なのはわかるんですが。。

できたので別記事化したらリンクします。

これでバイナリファイルの取り扱い以外のWebserviceのアクセスはできると思うので、JavaやPHPなんかで他にスクリプトくまなくてもある程度のことはできそうです。

参考にさせていただきました

jQuery ajaxでSOAP1.1する
https://qiita.com/logikuma/items/ae8c26b4a00013418711

jQuery.ajax(url[,settings])で、xml形式のデータを読み込みパースし表示
http://alphasis.info/2011/11/jquery-gyakubiki-jquery-ajax-url-xml/

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

【TypeScript】依存関係の逆転の原則(DIP) を考える

主に下記の記事を参考にしました。
よくわかるSOLID原則5: D(依存性逆転の原則)|erukiti|note
Vue Composition API + TypeScriptで DI(依存性の注入), DIP(依存性逆転の原則) を実装してみる - Qiita
DIPについて勉強し直してみた - Qiita

内容

  • 依存するって?
  • 依存性の注入(DI)
  • 依存関係の逆転の原則(DIP)
  • 実装

依存するって?

AというモジュールがBというモジュールを読み込む場合、AはBに依存しているといいます
よくわかるSOLID原則5: D(依存性逆転の原則)|erukiti|note より引用

上記の場合は、Bに変更があった場合は、Aに影響が出てきます。
具体的にいうと、あるクラスが、他のクラス、変数などが入ってしまっている状態です。
特定のクラスをテストしたい場合などに、非常に困ります。
その解決策に、DIなどの手法が存在します。

依存性の注入(DI)

下記記事が非常にわかりやすいです。
猿でも分かる! Dependency Injection: 依存性の注入 - Qiita
「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した  - Qiita

依存関係の逆転の原則(DIP) とは

上位レベルの方針のコードは、下位レベルの詳細の実装のコードに依存すべきではなく、逆に詳細側が方針に依存すべきであるという原則
Clean Architecture 達人に学ぶソフトウェアの構造と設計 より引用

実装

全体像だと下記のようなイメージです。
image.png

上記の図を実現するのが下記の実装になります!

class Human {
  repo: RepositoryInterface;
  public constructor (repo: RepositoryInterface) {
    this.repo = repo;
  }

  inject = (): void => {
    this.repo.inject();
  }
}

// 依存先の interface
interface RepositoryInterface {
  inject(): void;
}

// implements で実装することで、interface に依存するようにしている
class Nurse implements RepositoryInterface {
  inject = (): void => {
    // 処理を書いていく
  }
}

const nurseRepo = new Nurse();
const human = new Human(nurseRepo);
human.inject();

TODO

今度は、DIコンテナを掘り下げていきたいと思います!

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

ポリラインを一定距離ごとにぶった切る turf.lineChunk

緯度経度群(LineString)を、一定距離ごとに分割したいというニッチな要求にも応えてくれる turfjs すごい。

例えば、東京-名古屋-大阪 を結ぶ LineString があるとします。

const line = turf.lineString([[139.76, 35.68], [136.90, 35.17], [135.51, 34.68]]);

これを、turf.lineChunk を使って「100km 毎に分割」します。
typescript
const chunk = turf.lineChunk(line, 100, { units: 'kilometers' }) as FeatureCollection;

100km ごとに分割された図形群は FeatureCollection として返却されます。
Feature 型は少々冗長なので、図形の座標群だけ抜き出すと次のように。

[
  [ 
    [ 139.76, 35.68 ],
    [ 138.67760192668752, 35.49567029653387 ] 
  ],
  [
    [ 138.67760192668752, 35.49567029653387], 
    [ 137.60028816204638, 35.30171921680228] 
  ],
  [ 
    [ 137.60028816204638, 35.30171921680228 ], 
    [ 136.9, 35.17 ], 
    [ 136.54843014074316, 35.04811131423963 ]
  ],
  [ 
    [ 136.54843014074316, 35.04811131423963 ], 
    [ 135.54211990949918, 34.69156812570359 ] 
  ],
  [ 
    [ 135.54211990949918, 34.69156812570359 ], 
    [ 135.51, 34.68 ] 
  ]
]

東京-名古屋-大阪 のポリラインを 100km ごとに分割したら、5つの LineString になった、ということです。
そして、3つ目の要素は経由地である名古屋の緯度経度を含んでいるため、ここだけ要素数が3になっています。純粋に 100km 間隔のポリラインを得たいならば、先頭の点だけ(終点は末端だけ)を抽出すればよいことになります。

次の例は、東京-名古屋-大阪 のポリラインを 300 等分して、それぞれの標高を取得し、断面図を作る処理です(elevationService は、地理院地図の標高タイルを使って標高APIを作る を見てね)。

const line = turf.lineString([[139.76, 35.68], [136.90, 35.17], [135.51, 34.68]]);
const len = turf.length(line, { units: 'kilometers' });

const chunk = turf.lineChunk(line, len / 300, { units: 'kilometers' }) as FeatureCollection;

const points = chunk.features.map((f, index) => {
  const ln = f.geometry as LineString;
  if (index < chunk.features.length - 1) {
    return ln.coordinates[0];
  } else {
    return ln.coordinates.slice(-1)[0];
  }
});

const elevations = await this.elevationService.getElevations(
  points.map(p => ({ lat: p[1], lng: p[0] }))
);

console.log(`elevations`, JSON.stringify(elevations));

elevations を EXCEL などを使ってプロットすると、次のようになります。

これは、地理院地図の 断面図作成 を模したものです。
地理院地図を使って、東京-名古屋-大阪の断面図を作ると、次のようになります。

ほぼ同じ見た目を再現できたんじゃあないかと思います。

ちなみに、「ポリラインの開始点から距離 x 進んだ座標(緯度経度)」を得るには、turf.along が便利です。

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

Vue CLIの導入方法

どうも 最近Vuejsを始めたので
アウトプットしていこうと思います

Vue CLIとは

VueCliはVue.jsで開発するための便利ツールであります
私の解釈で話しますと、
VueCLIはVueを使うときに最初にうつコマンドのところであり
プロジェクト生成するときに使います。
CLIはコマンドインターフェースであります。

なぜこんな説明をしたかと言いますと、
なんと私

Vue.jsと Vue CLIって違うものなの?

とかなんかあたかも対等な関係的な感じで考えていました。
当たり前に違いました。

導入方法

1 Vue CLIをグローバルにインストール

 % npm install -g @vue/cli

2.これでバージョンを確認

 vue --version

3.プロジェクトを作成(hoge-appのところは自分の作りたいプロジェクトの名前)

 vue create hoge-app

4.プロジェクトを作るとこのようなものが出てきます
とりあえず初めてのかたであれば設定をカスタマイズできるManually select featuresを選択します

Default ([Vue 2] babel, eslint) 
  Default (Vue 3 Preview) ([Vue 3] babel, eslint) 
  Manually select features 
  1. これに関しては用途に合わせて選んでください   初めての方の場合は私と同じCSS Pre-processorsとbabelを選んどきましょう
Vue CLI v4.5.6
? Please pick a preset: Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Choose Vue version
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

おそらくこの後に私と同じCSS Pre-processorsを選んだ方は
CSS Pre-processorsの具体的になんのライブラリを使用するか選ぶこととなるでしょう

その後。。。。

6.babelやESLintの記述をどのファイルに記述するか選びます
 専用の設定ファイルを使用する場合はIn dedicated config filesを選びます
私はIn dedicated config files選びます。

Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files 
  In package.json 

7.今回の設定をプリセットするか選択します
yを選択すると今後今までの作業を省略することができます。

? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N)

これでエンターキーを押したらおしまいです:sunny:
お疲れ様でした:shamrock:

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

pay.jpの導入

Pay.jpの導入方法

クレジットカード決済の代行サービスで、簡単なオープンAPIで導入する事ができる

Why

コード量も非常に少なく、Javascriptで簡単にフォーム送信までできるのでご紹介します(割愛しながらの説明になります)

アプリケーションの作成

ターミナル
% cd ~/projects(好きなように)
% rails _6.0.0_ new payjp_practice -d mysql
% cd payjp_practice
% rails db:create

Orderモデル作成

ターミナル
% rails g model order

Ordersテーブル作成

db/migrate/**************_create_orders.rb
class CreateOrders < ActiveRecord::Migration[6.0]
  def change
    create_table :orders do |t|
      t.integer :price  ,null: false
      t.timestamps
    end
  end
end

忘れずに!

ターミナル
% rails db:migrate

バリデーション

app/models/order.rb
class Order < ApplicationRecord
  validates :price, presence: true
end

ルーティング

config/routes.rb
Rails.application.routes.draw do
  root to: 'orders#index'
  resources :orders, only:[:create]
end

ordersコントローラー

ターミナル
% rails g controller orders
app/controllers/orders_controller.rb
class OrdersController < ApplicationController

  def index
  end

  def create
  end

end

ビューを作成

app/views/orders/index.html.erb
<%= form_with  model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %>
  <div class='form-wrap'>
    <%= f.label :price, "金額" %>
    <%= f.text_field :price, class:"price", placeholder:")2000" %>
  </div>
  <%= f.submit "購入" ,class:"button" %>
<% end %>

CSS記述

app/assets/stylesheets/style.css
.card-form{
  width: 500px;
}

.form-wrap{
  display: flex;
  flex-direction: column;
}

.exp_month{
  resize:none;
}

.exp_year{
  resize:none;
}

.input-expiration-date-wrap{
  display: flex;
}


.button{
  margin-top: 30px;
  height: 30px;
  width: 100px;
}

コントローラー編集

app/controllers/orders_controller.rb
class OrdersController < ApplicationController

  def index
    @order = Order.new
  end

  def create
    @order = Order.new(order_params)
    if @order.valid?
      @order.save
      return redirect_to root_path
    else
      render 'index'
    end
  end

  private

  def order_params
    params.require(:order).permit(:price)
  end

end

部分テンプレート

app/views/orders/index.html.erb
<%= form_with  model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %>
  <%= render 'layouts/error_messages', model: @order %>
  <div class='form-wrap'>
<%# 省略 %>

ビュー記述

app/views/orders/index.html.erb
<%= form_with  model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %>
  <%= render 'layouts/error_messages', model: @order %>
  <div class='form-wrap'>
    <%= f.label :price, "金額" %>
    <%= f.text_field :price, class:"price", placeholder:")2000" %>
  </div>
  <div class='form-wrap'>
    <%= f.label :number,  "カード番号" %>
    <%= f.text_field :number, class:"number", placeholder:"カード番号(半角英数字)", maxlength:"16" %>
  </div>
  <div class='form-wrap'>
    <%= f.label :cvc ,"CVC" %>
    <%= f.text_field :cvc, class:"cvc", placeholder:"カード背面4桁もしくは3桁の番号", maxlength:"3" %>
  </div>
  <div class='form-wrap'>
    <p>有効期限</p>
    <div class='input-expiration-date-wrap'>
      <%= f.text_field :exp_month, class:"exp_month", placeholder:")3" %>
      <p>月</p>
      <%= f.text_field :exp_year, class:"exp_year", placeholder:")24" %>
      <p>年</p>
    </div>
  </div>
  <%= f.submit "購入" ,class:"button" %>
<% end %>

turbolinks削除&コード追加

app/views/layouts/application.html.erb
<%# 省略 %>
    <%= stylesheet_link_tag 'application', media: 'all'  %>
    <%= javascript_pack_tag 'application' %>
<%# 省略 %>
app/javascript/packs/application.js
// 省略
require("@rails/ujs").start()
// require("turbolinks").start() // コメントアウトする
require("@rails/activestorage").start()
require("channels")
// 省略

payjp.js読み込み

app/views/layouts/application.html.erb
<%# 省略 %>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <script type="text/javascript" src="https://js.pay.jp/v1/"></script>
    <%= stylesheet_link_tag 'application', media: 'all' %>
    <%= javascript_pack_tag 'application' %>
<%# 省略 %>

トークン化準備

app/javascript/packs/application.js
// 省略
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("../card")
// 省略

イベント発火

app/javascript/card.js
const pay = () => {
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    console.log("フォーム送信時にイベント発火")
  });
};

window.addEventListener("load", pay);

公開鍵設定

app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    console.log("フォーム送信時にイベント発火")
  });
};

window.addEventListener("load", pay);

フォームの情報取得

app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };
  });
};

window.addEventListener("load", pay);

カードの情報トークン化

app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        console.log(token)
      }
    });
  });
};

window.addEventListener("load", pay);

一度pay.jpが用意しているテスト用のカード情報を入力してチェックしておきましょう!

カード番号 4242424242424242(16桁)
CVC 123
有効期限 登録時より未来(04/25など)

トークンの値をフォームに含める

app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} name='token'>`;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
        debugger;
      }
    });
  });
};

window.addEventListener("load", pay);

トークンの値を非表示

app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} name='token' type="hidden"> `;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
        debugger;
      }
    });
  });
};

window.addEventListener("load", pay);

クレジットカードの情報を削除

app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} name='token' type="hidden"> `;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
      }

      document.getElementById("order_number").removeAttribute("name");
      document.getElementById("order_cvc").removeAttribute("name");
      document.getElementById("order_exp_month").removeAttribute("name");
      document.getElementById("order_exp_year").removeAttribute("name");
    });
  });
};

window.addEventListener("load", pay);

フォームの情報をサーバーサイドに送信

app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} name='token' type="hidden"> `;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
      }

      document.getElementById("order_number").removeAttribute("name");
      document.getElementById("order_cvc").removeAttribute("name");
      document.getElementById("order_exp_month").removeAttribute("name");
      document.getElementById("order_exp_year").removeAttribute("name");

      document.getElementById("charge-form").submit();
    });
  });
};

window.addEventListener("load", pay);

ストロングパラメーター編集

app/controllers/orders_controller.rb
#省略

  private

  def order_params
    params.require(:order).permit(:price).merge(token: params[:token])
  end

end

Orderモデルに追記

app/models/order.rb
class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
end

Gem導入

Gemfile
# 省略
gem 'payjp'

決済処理の記述とリファクタリング

app/controllers/orders_controller.rb
class OrdersController < ApplicationController

  def index
    @order = Order.new
  end

  def create
    @order = Order.new(order_params)
    if @order.valid?
      pay_item
      @order.save
      return redirect_to root_path
    else
      render 'index'
    end
  end

  private

  def order_params
    params.require(:order).permit(:price).merge(token: params[:token])
  end

  def pay_item
    Payjp.api_key = "sk_test_***********"  # 自身のPAY.JPテスト秘密鍵を記述しましょう
    Payjp::Charge.create(
      amount: order_params[:price],  # 商品の値段
      card: order_params[:token],    # カードトークン
      currency: 'jpy'                 # 通貨の種類日本円
    )
  end

end

バリデーション

app/models/order.rb
class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
  validates :token, presence: true
end

環境変数(Mac Catalina以降の場合)

ターミナル
% vim ~/.zshrc
# iを押してインサートモードに移行し下記を追記する既存の記述は消去しない
export PAYJP_SECRET_KEY='sk_test_************'
export PAYJP_PUBLIC_KEY='pk_test_************'
# 編集が終わったらescキーを押してから:wqと入力して保存して終了
ターミナル
# 編集した.zshrcを読み込み直して追加した環境変数を使えるようにする
% source ~/.zshrc

秘密鍵代入した環境変数の呼び込み

app/controllers/orders_controller.rb
#省略
def pay_item
   Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
   Payjp::Charge.create(
     amount: order_params[:price],
     card: order_params[:token],
     currency:'jpy'
   )
end

JavaScriptで環境変数の呼び込み

ターミナル
% touch config/initializers/webpacker.rb
config/initializers/webpacker.rb
Webpacker::Compiler.env["PAYJP_PUBLIC_KEY"] = ENV["PAYJP_PUBLIC_KEY"]
app/javascript/card.js
const pay = () => {
 Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
  // 省略

まとめ

簡単と言っておきながら意外と記述は多かったかもしれません。ですが、APIの中でも比較的簡単な決済機能の導入なので抑えておくといいかもしれません。オリジナルでカラム等追加する場合がほとんどだと思いますので、ゆっくり順に書くことをお勧めします!以上!

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

JavaScript 配列の中の重複を省く簡単な方法

配列の重複を省く簡単な方法

プリミティブ値

  • 数字
  • 文字列
  • 真偽値
//重複を省きたい配列
const old_array = [1,1,1,2,2,2,3,4];
const new_array = [...new Set(old_array)]

console.log(new_array);
// [1, 2, 3, 4]

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

Object

やりかた

  1. ソートします。
  2. 隣接するObjectと比較して重複しているなら省く。

以上

実践

const old_array = [{k:3},{k:1},{k:2},{k:3},{k:1},{k:2},{k:3},{k:3}];

//ソート
old_array.sort((o1,o2)=> o1.k - o2.k);

//何を重複とするか決める。
//今回だとkの値が一緒だと重複と見なします。
function compare(o1,o2){
  return o1.k === o2.k
}

//あとは、隣と比較して同じだったら省くだけ!
const new_array = old_array.filter(function(element,index,array){
  if(index === 0) return true;
  return !compare(element,array[index-1])
})
console.log(new_array)
// [{"k":1},{"k":2},{"k":3}]

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