20200103のJavaScriptに関する記事は16件です。

簡単なカウントアプリを作ってみる

簡単なカウントアプリを作ってみる。

株式会社パーソンリンクにエンジニア未経験入社の高島です。入社して2ヶ月が経ちました。
今後は、保守・運用業務がメインになっていくのですが、業務以外でインプットできる分野を増やそうと思いました。社内ではvue.jsを扱えるエンジニアが多数在籍しています。今後、自分も開発業務に参画した際に幅広く対応できるように、まずはjavascriptにて簡単なカウントアプリを作ってみました。
アップボタンを押すと数字が1ずつ増え、ダウンボタンを押すと数字が1つずつ減っていくという簡単なものです。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .wrapper {
            text-align: center;
        }
    </style>
</head>

<body>
    <div class="wrapper">
        <h1>カウンターアプリ</h1>
        <div id='counter'>0</div>
        <button id='count-up'>アップ</button>
        <button id='ten-up'>10アップ</button>
        <button id='count-down'>ダウン</button>
        <button id="ten-down">10ダウン</button>
    </div>

    <script>
        let counter = document.getElementById('counter');
        let up = document.getElementById('count-up');
        let down = document.getElementById('count-down');
        let tu = document.getElementById('ten-up');
        let td = document.getElementById('ten-down');
        count = 0;

        up.addEventListener('click', function () {
            count = count + 1;
            counter.innerHTML = count;
        });

        tu.addEventListener('click', function () {
            count = count + 10;
            counter.innerHTML = count;
        });

        down.addEventListener('click', function () {
            count = count - 1;
            counter.innerHTML = count;
        });

        td.addEventListener('click', function () {
            count = count - 10;
            counter.innerHTML = count;
        });

    </script>
</body>

</html>

解説

wrapperクラス内に、id属性でcounter,count-up(ボタン),count-down(ボタン)を作成する。
counter,count-up,count-downをjavascriptで扱いやすくするため、

let counter = document.getElementById('counter');
let up = document.getElementById('count-up');
let down = document.getElementById('count-down');

と記述する。

document.getElementById('id名');

で、引数にhtmlで記述したid名を指定することによって、要素を取得することができる。

up.addEventListener('click', function () {
            count = count + 1;
            counter.innerHTML = count;
        });

は、upという要素に対して、「クリック」というイベントが行われた時に、
countという要素に1をプラスする。
ただ1をプラスしていくだけだと画面上に表示されないので、

counter.innerHTML = count;

とすることによって、htmlのid属性で記述したcounter内に、countを代入することができる。つまり、画面上に1をプラスした結果を表示することができる。

対象要素.addEventListener(イベントの種類.function(){})
 down.addEventListener(click, function () {
            count = count - 1;
            counter.innerHTML = count;
        });

これも、upの時と同じで、downという要素に対して、「クリック」というイベントが行われた時に、countという要素から1をマイナスする。

完成形はこのようになります。
922abfea70beee4d5b63c4df58121af5.png

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

verとletの違いは2つある

はじめに

「javascriptの変数宣言は"let"で行え!」と言われているので素直に従っていましたが、
改めてなぜ"var"を使わない方が良いかをまとめてみます。

(1)letは変数の重複を許可しない

varで同名の変数を宣言してみます。
var.js
var name = "hoge"
var name = "fuga"

console.log(name);
>>fuga

上書きされて二つ目の宣言の"fuga"が出力されました。

同じくletでも同名の変数を宣言してみます。
let.js
var name = "hoge"
var name = "fuga"

console.log(name);
>>Uncaught SyntaxError: Identifier 'name' has already been declared

既に宣言済みだよとのエラーメッセージが出力されました。

※letは変数の再宣言ができないため、変数の重複が起こらないという利点があります。

(2)letはブロックスコープを認識する。

大前提として、javascriptはブロックスコープを認識しません。

下記をご覧ください。

var.js
if(true){
    var i = 5;
}

console.log(i);
>>5

JAVAやC#などのプログラミング言語を学習してきた方からすれば違和感を覚えるかもしれませんが、javascriptのvar命令はブロックスコープを認識しません。

そして、一般的にプログラミングのルールとして「スコープはできる限り限定すべき」というものがあります。そのルールに即した命令がlet命令です。

let.js
if(true){
    let i = 5;
}

console.log(i);
>>usestrit.html:11 Uncaught ReferenceError: i is not defined

まとめ

①変数の重複を防ぐため
②スコープの範囲を限定するため

上記2点を理由にvarよりletを使う方が好ましいです。

注)限定的にvarを使った方が良い場面もありますが、あまり登場しないのでとりあえずletを使う方が良いでしょう。

参考

改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで

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

varとletの違いは2つある

はじめに

「javascriptの変数宣言は"let"で行え!」と言われているので素直に従っていましたが、
改めてなぜ"var"を使わない方が良いかをまとめてみます。

(1)letは変数の重複を許可しない

varで同名の変数を宣言してみます。
var.js
var name = "hoge"
var name = "fuga"

console.log(name);
>>fuga

上書きされて二つ目の宣言の"fuga"が出力されました。

同じくletでも同名の変数を宣言してみます。
let.js
var name = "hoge"
var name = "fuga"

console.log(name);
>>Uncaught SyntaxError: Identifier 'name' has already been declared

既に宣言済みだよとのエラーメッセージが出力されました。

※letは変数の再宣言ができないため、変数の重複が起こらないという利点があります。

(2)letはブロックスコープを認識する。

大前提として、javascriptはブロックスコープを認識しません。

下記をご覧ください。

var.js
if(true){
    var i = 5;
}

console.log(i);
>>5

JAVAやC#などのプログラミング言語を学習してきた方からすれば違和感を覚えるかもしれませんが、javascriptのvar命令はブロックスコープを認識しません。

そして、一般的にプログラミングのルールとして「スコープはできる限り限定すべき」というものがあります。そのルールに即した命令がlet命令です。

let.js
if(true){
    let i = 5;
}

console.log(i);
>>usestrit.html:11 Uncaught ReferenceError: i is not defined

まとめ

①変数の重複を防ぐため
②スコープの範囲を限定するため

上記2点を理由にvarよりletを使う方が好ましいです。

注)限定的にvarを使った方が良い場面もありますが、あまり登場しないのでとりあえずletを使う方が良いでしょう。

参考

改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで

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

Firebase+React+Reduxで多機能チャットを実装しよう【リアルタイムチャット~React編~】

Firebase&React&Reduxで多機能チャットを実装しよう【リアルタイムチャット編】

今回から本格的にReactとReduxを触っていきます。
一気に難易度が上がるので覚悟してください!笑

ガイド

ディレクトリ構成

前回までのおさらい。
ディレクトリ構成は現在以下の通りです。

要らないファイルを削除して、ディレクトリを追加作成します。

Before
react-chat
 │- build/
 │- public/
 │   │- favicon.ico
 │   │- index.html
 │   │- logo192.png <-- REM
 │   │- logo512.png <-- REM
 │   │- manifest.json
 │   └  robots.txt
 │- src/
 │   │- firebase/
 │   │   │- config.js
 │   │   └  index.js
 │   └  index.js
 │- node_modules/
 │- .firebaserc
 │- database.rules.json
 │- firebase.json
 │- package.json
 │- package-lock.json
 └  storage.rules.json
After
react-chat
 │- build/
 │- public/
 │   │- favicon.ico
 │   │- index.html
 │   │- manifest.json
 │   └  robots.txt
 │- src/
 │   │- components/ <-- ADD
 │   │- containers/ <-- ADD
 │   │- firebase/
 │   │   │- config.js
 │   │   └  index.js
 │   │- templates/ <-- ADD
 │   │- index.js 
 │   └  style.css <-- ADD
 │- node_modules/
 │- .firebaserc
 │- database.rules.json
 │- firebase.json
 │- package.json
 │- package-lock.json
 └  storage.rules.json

React基本ファイルの準備

create-react-appで作成した開発環境の基本を簡単に説明しておきます。

  • public:雛形となるhtmlファイルを格納するディレクトリ
  • src:コンパイル前のソースコードを格納するディレクトリ
  • npm run buildsrc配下のファイルをコンパイルするコマンド
  • build:コンパイルされたファイルを格納するディレクトリ

雛形となるHTMLファイル

雛形となるHTMLファイルには以下を記載します。

  • サイトのタイトルやメタ情報
  • Firebaseに必要なスクリプト
  • Reactコンポーネントをrenderするルートdiv
public/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Firebase React Chat!</title>
    <!-- update the version number as needed -->
    <script defer src="/__/firebase/7.5.1/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/7.5.1/firebase-auth.js"></script>
    <script defer src="/__/firebase/7.5.1/firebase-database.js"></script>
    <script defer src="/__/firebase/7.5.1/firebase-messaging.js"></script>
    <script defer src="/__/firebase/7.5.1/firebase-storage.js"></script>
    <!-- initialize the SDK after all desired features are loaded -->
    <script defer src="/__/firebase/init.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

Reactコンポーネントをindex.jsファイル

npm run buildでコンパイルする際に参照されるjsファイルです。
細かい説明は省きますが、後ほど登場するファイルたちの情報をまとめておきます。

src/index.js
import React from 'react';
import * as ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {RootContainer} from './containers';
import {configureStore} from './modules';
import './style.css'

ReactDOM.render(
    <Provider store={configureStore()}>
        <RootContainer />
    </Provider>,
    document.getElementById('root')
);

ReactDOM.render(element, container)で、Reactコンポーネント(element)を雛形HTMLのDOM(container)にレンダーして表示させています。

先ほど作成したHTMLファイル内の、idがrootのdiv要素にレンダーさせているということですね。

それでは、レンダーされるコンポーネントを作っていきましょう。

Reactコンポーネントの作成

ReactをReduxと共に用いる場合、コンポーネントは2つに分けるのが一般的です。

  1. Presentational Components --> 見た目(View)を担当する。「コンポーネント」と呼ばれる
  2. Container Components --> ロジック(振る舞い)に関わる。「コンテナー」と呼ばれる。

より詳細な定義はコチラの記事を参考にしてください。

上記を踏まえて、ディレクトリ構成で追加したディレクトリに格納するファイルの役割は以下の通りです。

  • components: 最小構成要素(パーツ)となるPresentational Components、子コンポーネントです。
  • templates: components内のパーツを組み合わせたPresentational Components、親コンポーネントです。
  • containers: templatesPresentational Componentsと"状態"を紐づけるための中継役、Container Componentsです。

ログインページを例にすると
componentsは「ボタン」や「テキストボックス」
templatesは「ボタン」や「テキストボックス」を組み合わせた「ログインフォーム」
containersはログイン処理後の見た目(View)がどのように変化するのか定義

少し難しいですよね。

「コンポーネントは見た目(View)のみを持ち、状態(State)と分離させる」というReduxの設計思想を実現するための構成だと思ってください。

それでは、実際にリアルタイムチャットを実装するためのファイルを書いていきます。

componentsファイルの作成

チャットを実装するためには以下のパーツが必要だと考えました。

  1. メッセージ表示エリア
  2. メッセージ入力エリア
  3. メッセージ送信ボタン

この記事では2と3について解説します。

当記事ではMaterial-UIを使っていますが、詳しい解説を割愛します。
デザインのカスタマイズには癖があるけど、それっぽいUIが簡単に作れて便利だゾ。

また、デモページを作った都合上、独自CSSを使っています。
CSSはGithubのソースコードから確認してください。(src/style.css)に記述しています。

src/components/Chat/TextInput.js
import React from 'react';
import TextField from '@material-ui/core/TextField';

const TextInput = (props) => {
    return (
        <form className="p-chat__textarea c-grid-center" noValidate autoComplete="off">
            <TextField
                id="standard-text"
                className="c-grid-full"
                margin="normal"
                label="メッセージを入力..."
                multiline
                rowsMax="4"
                onChange={e => props.onChange(e.target.value)}
                value={props.value}
            />
        </form>
    );
};

export default TextInput;
src/components/Chat/SendButton.js
import React from 'react';
import Button from '@material-ui/core/Button';
import SendIcon from '@material-ui/icons/Send';

const SendButton = (props) => {
    return (
        <Button
            variant="contained"
            color="primary"
            className="p-chat__button-send"
            onClick={() => props.onClick(props.value, props.roomId, props.fromId, props.toId, props.userIds)}>
          <SendIcon />
        </Button>
    );
}

export default SendButton;

それぞれのポイントを解説します。

両コンポーネントは、Stateless Functional Componentsという形式で宣言されています。

初期のReactでは、Classを用いてコンポーネントを宣言していました。
近年のReactでは、Stateless Functional Components、つまり"関数"として宣言することが推奨されています。

Stateless Functional Componentsには以下のメリットがあります。

  • constructorを使わなくて良い
  • thisが必要ない
  • 状態を持たない(stateless)コンポーネントにできる

つまり、コードがシンプルになってハッピーってことですね☆

また、ES6のアロー関数を使ってさらにシンプルに書いています。

そして重要なのが、関数の引数に渡しているpropsです。
この部分const TextInput = (props) => {です。
この部分const SendButton = (props) => {ですよ。

propsは親コンポーネントから渡された引数を一挙に受け取ります。
親コンポーネントからvalueとして渡した引数を、子コンポーネントでprops.valueのように参照することができます。

props.onClickprops.onChangeは、状態を変更するためのActionsを呼び出します。
Actionsについては[次の記事]で解説します。

templatesファイルの作成

templatesディレクトリ配下にChat.jsを作成します。
Chat.jsは親コンポーネントとして、先ほど作成したコンポーネントをまとめます。

ファイルが長いので部分ごとに解説します。

import

ファイル冒頭のimport文です。

src/templates/Chat.js
import React, {Component} from 'react';
import {Chat, Common} from '../components';
import {database} from '../firebase/index'

components/index.jsexportした"Chat"と"Common"のコンポーネントをimportします。

constructorとrender

src/templates/Chat.js
class ChatTemplate extends Component {
    constructor(props) {
        super(props);
    }

    /* 中略 */

    render() {
        return (
             <div className="p-chat">
                <Common.NavBar
                    value={this.props.messages}
                    actions={this.props.actions.messages}
                    back={this.props.actions.messages.backToRooms}
                    configure={this.props.actions.messages.configure}
                    signOut={this.props.actions.messages.signOut}
                />
                <div className="p-chat__area" id="scroll-area">
                    {this.props.messages.msgs.map((m, i) => (
                        <Chat.AlignItemsList key={i} msgs={m} />
                    ))}
                </div>
                <div className="c-grid__row">
                    <Chat.TextInput
                        onChange={this.props.actions.messages.change}
                        value={this.props.messages.value}
                    />
                    <Chat.SendButton
                        onClick={this.props.actions.messages.submit}
                        value={this.props.messages.value}
                        roomId={this.props.messages.roomId}
                        fromId={this.props.messages.userId}
                        toId={this.props.messages.partnerId}
                        userIds={this.props.messages.userIds}
                    />
                </div>
            </div>
        );
    }
}

export default ChatTemplate

親コンポーネントではcomponentDidMount()などのライフサイクルを使いたいので、Stateless Functional ComponentsではなくClass Componentsで宣言します。

constructorの宣言をします。
this.propsでreduxのStoreに保存されているGlobal Stateを参照できます。
(ごめんなさい、Storeについても[次の記事]で解説します...!)

src/templates/Chat.js
constructor(props) {
    super(props);
}

続いて、render部分です。
return()のなかにDOMやコンポーネントを記述します。

render() {
    return (
        /*
            レンダーするDOMを記述する。
            作成した子コンポーネントもこの中で宣言して使う。
        */
    )
}

"Chat"としてimportした子コンポーネントを呼び出す方法は以下の通りです。
例...TextInputコンポーネントを呼び出す。

return(
    <Chat.TextInput />
)

さらに、引数を渡してみましょう。

src/templates/Chat.js
return(
    <Chat.TextInput
        onChange={this.props.actions.messages.change}
        value={this.props.messages.value}
    />
)

少し分かりづらいかもしれませんが

  • this.props.actions.messages.changeというActionsonChangeとしてTextInputコンポーネントに渡している
  • this.props.messages.valueというGlobal StateをvalueとしてTextInputコンポーネントに渡している

これによって、TextInputコンポーネントではprops.onChangeprops.valueとして渡された値を参照できる。

ライフサイクルメソッド

ReactのClass Componentsではライフサイクルメソッドが使えます。
ライフサイクルメソッドが分かりやすく図解されている記事はコチラ

代表的なメソッドは以下。

  • componentWillMount() --> 現在は非推奨なので使わない。
  • render() --> Viewを描画する。
  • componentDidMount() --> API連携など、通信が必要な処理はここで。
  • componentDidUpdate() --> stateが変更されて再render()が走った後に
  • componentWillUnmount() --> componentDidMount()で確保したリソースを解放する

src/templates/Chat.jsでは各メソッドで以下の処理を実行している。

  • componentDidMount() --> Firebase DBにメッセージのデータが追加されたらViewを再描画するようにリスナーを仕掛ける。
  • componentDidUpdate() --> メッセージ一覧の最下部にスクロールする。
  • componentWillUnmount() --> Firebase DBへのリスナーを解除する。

ソースコード載せると長〜いので、Githubを確認してください?

リスナーの設定はfirebase.database().ref().on()で。
逆にリスナーの解除はfirebase.database().ref().off()です。

リスナー設定/解除の詳細はFirebaseクライアントSDKのドキュメントを参照。
データの取得|Firebase Realtime Database

containersファイルの作成

最後にContainer Componentsを作成します。

src/containers/Chat/Main.js
import ChatTemplate from '../../templates/Chat';
import {bindActionCreators, compose} from 'redux';
import {connect} from 'react-redux';
import {actions} from '../../modules/chat/index';

const mapStateToProps = state => {
  return {
    messages: state.messages,
  };
};

const mapDispatchToProps = dispatch => {
  return {
    actions: {
      messages: bindActionCreators(actions.messages, dispatch),
    },
  };
};

export default compose(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )
)(ChatTemplate);

すでに説明した通り、Container Componentsの役割は中継役です。

ReactとReduxを繋げるreact-reduxというライブラリのconnectメソッドを使います。

connectメソッドは、以下2つをReactコンポーネントで参照できるようにしているイメージです。

  • mapStateToProps: Reduxで管理しているstate(状態)
  • mapDispatchToProps: state(状態)を変更するためのReduxのActions

上記のContainer Componentsの書き方は汎用的に使えるはずです。

まとめ

Reactだけならまだしも、Reduxが絡むと途端にややこしくなります。

特にContainer Componentsconnectするあたりは、最初何をやっているのか全く理解できませんでした。

分からなくてもコピペしておきましょう。
この記事で少しでも理解を深めていただければ幸いです。

Reactは公式のドキュメントが充実しているので、(自戒も込めて)よく読んだ方がいいですね。

[次の記事]でははいよいよReduxのActions, Reducers, Storeなどを解説していきます。

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

ブラウザが非表示になった時にマイク入力を解放する

困っている事

webAudioAPIを利用してマイク入力データを利用している中で

  • 別タブに切り替えた時に、タブの録音中マークが消えない(PC)
  • ホームボタンを押した時に画面上に赤い帯が残る(iOS)

タブはこういう丸いやつ↓
スクリーンショット 2020-01-03 19.00.22.png

赤い帯はこういうやつ↓
S__107831356.jpg

環境

PC

  • MacBook Pro 2016
  • macOS Mojave 10.14.6
  • Chrome 79.0.3945.88(Official Build) (64 ビット)

スマホ

  • iPhone 6s
  • iOS13.3
  • safari

手順

  1. タブがアクティブじゃなくなった事を検知して
  2. マイクの入力トラックを解放する

タブがアクティブじゃなくなった事を検知

コード

function setEventListener() {
  let hidden, visibilityChange;
  if (typeof document.hidden !== "undefined") { // Opera 12.10 や Firefox 18 以降でサポート
    hidden = "hidden";
    visibilityChange = "visibilitychange";
  } else if (typeof document.msHidden !== "undefined") {
    hidden = "msHidden";
    visibilityChange = "msvisibilitychange";
  } else if (typeof document.webkitHidden !== "undefined") {
    hidden = "webkitHidden";
    visibilityChange = "webkitvisibilitychange";
  }
  document.hiddenStatus = hidden;

  if (hidden === undefined) {
    console.log("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.");
  } else {
    document.addEventListener(visibilityChange, browserBlorFunction, {once: true});
  }
}

async function browserBlurFunction() {
  if (document.hidden) {
    // 非表示状態になった時の動作
    await stopRecording();
  }
}

解説

この記事に全て書いてあります!
[iOS/Android]ブラウザでページが非表示になったことを検知する方法

今回はタブがアクティブではない時の体験を損なわない事を目的としていたので、
Page Visibility APIのみの利用をしています。

簡単に言うと、「ページが表示されているかどうか」を判定してくれるAPIです。
EventListenerに登録して利用可能です!

この機能によって追加されたプロパティであるdocument.hiddenで現在のブラウザの表示状態を取得できるので、
document.hidden === trueの時に、マイク入力解放関数を実行します。

ちなみに

同じような動作をさせたい時のグローバルイベントハンドラーとしてwindow.onblurがあります。
これはウィンドウの切り替えを意味するものでありページの表示を検知するものではありません。
そのためwindow.onblurにマイク入力解放のコードをセットしても、
iPhoneでホームボタンを押した後は冒頭で紹介した赤い帯が出るし、
タブを切り替えてもブラウザの丸いマークは残ったままです。

マイクの入力を解放する

コード

//streamsには、mediaStreamそのものが入っています。
function stopRecording() {
  let tracks = streams.getTracks();
  tracks.forEach(function (track) {
    track.stop();
  });
}

//streamsへの格納
navigator.mediaDevices.getUserMedia({audio: true})
    .then(mediaStream => {
      gotStream(mediaStream);
    }).catch(e => {
    alert('Error getting audio');
  });
}

let streams = null;
function gotStream(stream){
  streams = stream;
  //以下音声の処理...
}

解説

マイク入力はmediaDevices.getUserMediaより取得しており、その際に取得したmediaStreamTrackを停止させる事により、入力を解放します。

trackの停止

mediaStreamTrack.readyStateと言うプロパティの値が"ended"になる時、入力デバイスからのデータを受け取らなくなります。

"ended"は入力デバイスがこれ以上データを提供することがなく、新しいデータも一切提供されないことを示します。
引用元:mediaStreamTrack.readyState

そのための関数として、

MediaStreamTrack.stop()
トラックに関連付けられたソースの再生を停止し、ソースとトラックの関連付けを解除します。トラックの状態はendedに設定されます。

こちらが用意されているので、それを使いましょう!
mediaStream.getTracks

function stopRecording() {
  let tracks = streams.getTracks();
  tracks.forEach(function (track) {
    track.stop();
  });
}

これでmediaStreamTrack.readyStateがendedになるため、記事冒頭で紹介した動作は起こらなくなります。

最後に

人生初Qiita記事書いてみました!
ちなみにsafariのアプリを完全に落とせば同じ事は達成できます()

今年は頑張ってアウトプットしていくぞぉ

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

Google スプレッドシートで送金可能なNEMアドレス帳を作る方法

Google スプレッドシートを使った送金可能なアドレス帳をGAS(Google Apps Script) & NEM Catapultで実現しましたのでその方法を紹介します。

GASとNEMの連携については先日投稿した、Google Apps Script(GAS)でNEM Catapultを使うを参考にしてください。今回はさらに一歩進展させて、GoogleスプレッドシートとNEM Catapultの連携を行います。

なお、今回もWEBIRD PROGRAMMING.TECHさんの記事、【GAS】WebアプリやAPIも作れちゃう!HtmlServiceについて、できることをまとめてみた。 ~ その③ スプレッドシートのUI ~を参考にさせていただきました。

画面イメージ

今回作成したツールの画面イメージです。

スクリーンショット 2020-01-03 13.00.17.png

スプレッドシートに送信先、モザイクID(トークン)、送信量を一覧形式で記述しておきます。どんな用途に使用するのかのメモ書きも残しておくことができます。スプレッドシートはメニューからサイドバー表示を選択することができ、送信IDを選択してメッセージwぽ追加してモザイクを送信することができます。

プログラム構成

今回紹介するツールは2つのプログラムで構成されています。

  • コード.gs
  • sidebar.html

コード.gs

  • メニューへの追加
  • サイドバーコンテンツの表示
  • 一覧データを取得しサイドバーへデータ転送

sidebar.html

  • サイドバーコンテンツ
  • NEM送信

プログラム詳細

スプレッドシートを開き1行目に
- ID
- 送信先
- モザイクID
- 送信量

と入力します。以降の列は無視されるのでメモ書きに利用してください。
次にメニューからツール->スクリプトエディタを選択し、Google Apps Script編集画面を開き、以下に紹介する2つのファイルを作成します。ファイルを作成したら、公開->ウェブアプリケーションとして導入を選択して、ビルドしてください。(スプレッドシートのアドオンとして配置などもう少し適切な方法があるかと思いますが短時間では探しきれませんでした)

gsスクリプト

コード.gs
//メニューに追加
function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('UI表示')
    .addItem('サイドバーを表示', 'showSidebar')
    .addToUi();
}

//サイドバーコンテンツ表示
function showSidebar() {
  var sidebar = HtmlService
    .createTemplateFromFile('sidebar.html')
    .evaluate()
    .setSandboxMode(HtmlService.SandboxMode.IFRAME)
    .setTitle('サイドバー')
  SpreadsheetApp.getUi().showSidebar(sidebar);
}

//IDリストの取得
function getIds() {
  var values = getData();
  var ids = [];
  for(var i = 1, l = values.length; i < l; i++) {
    ids.push(values[i][0]);
  }
  return ids;
}

//送信データの取得
function sendNEM(id) {

  var prop = {};
  var values   = getData();
  prop.address = values[id][1];
  prop.mosaicId = values[id][2];
  prop.amount    = values[id][3];

  return prop;
} 

//データ取得
function getData() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var value = sheet.getDataRange().getValues();
  return value;
}

htmlコンテンツ

sidebar.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
  </head>
  <body>
    <label for="send-id">送信ID</label>
    <br>
    <select id="send-id" name="send-id" style="margin-bottom: 10px;" >
      <?
        var ids = getIds();
        for(var i = 0, l = ids.length; i < l; i++) {
          output.append('<option value="' + ids[i] + '">' + ids[i] + '</option>');
        }
      ?>
    </select>
    <br>

    <label for="message">メッセージ</label><br>
    <textarea id="message"></textarea><br>

    <button class="action" onclick="sendNEM()">送信!</button><br>
    <div id="send-status"></div>

<script src="https://s3-ap-northeast-1.amazonaws.com/xembook.net/nem2-sdk/nem2-sdk-0.16.1.js"></script>
<script>
const NODE = 'https://jp5.nemesis.land:3001';
const nem = require("/node_modules/nem2-sdk");
const alice  = nem.Account.createFromPrivateKey('DF1A2101A6BA32F33C60B6A59AA72170BB0AC5A4E1057FBA706DC2B999E902C4', nem.NetworkType.TEST_NET);      
const GENERATION_HASH = "CC42AAD7BD45E8C276741AB2524BC30F5529AF162AD12247EF9A98D6B54A385B";

function sendNEM() {

    var id = $("#send-id").val();

    google.script.run
    .withFailureHandler(function(err) {
          $("#send-status").html(err);
    })
    .withSuccessHandler(function(res) {

        const tx = nem.TransferTransaction.create(
            nem.Deadline.create(),
            nem.Address.createFromRawAddress(res.address),
            [
            new nem.Mosaic(
                new nem.MosaicId(res.mosaicId),
                nem.UInt64.fromUint(res.amount)
            )
            ],
            nem.PlainMessage.create($("#message").val()),
            nem.NetworkType.TEST_NET,
            nem.UInt64.fromUint(100000)
        );

        const signedTx = alice.sign(tx,GENERATION_HASH);
        const txHttp = new nem.TransactionHttp(NODE);
        console.log(NODE + "/transaction/" + signedTx.hash + "/status");

        txHttp
        .announce(signedTx)
        .subscribe(_ => console.log("ネットワーク通知"), err => console.error(err));

        console.log(res);
        $("#send-status").html('送信しました!');
    })
    .sendNEM(id);  // GAS側のsendNEM関数を呼び出し
}
    </script>
  </body>
</html>

ツールの実行

スプレッドシートを表示しメニューからUI表示->「サイドバーを表示」を選択するとサイドバーが表示されます。送信したいIDをリストボックスから選択し、メッセージを追加して送信ボタンをクリックするとスプレッドシートに記載されたデータを元にNEMブロックチェーンの送信処理が行われます。

終わりに

NEMブロックチェーンのREST API機能を利用してGoogleスプレッドシート上のデータから送信する機能を実装してみました。この機会にNEMブロックチェーンの可能性を感じていただければ幸いです。

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

CodePen + Three.js でルービックキューブ(モドキ)つくってみた

3D描画するだけならThree.jsでかなり簡単にできる。(参考サイト 1. 参照)

つくってみた

今回、ルービックキューブっぽくするために箱を複数(8個)使っている。
箱を回転させるのはrotation.x,y,zを指定するだけだが、箱の位置も回転させないといけないため、cos,sinで自力で回転させている。
操作できるUIは今回実装してないので、見るだけのルービックキューブです。

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

参考サイト

  1. WebGL、three.jsでワクワクしてみた
  2. Three.jsのさまざまなマテリアル
  3. Three.jsのBoxGeometryでそれぞれの面に別々の色を指定する方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2020年1月】令和だし本格的にVSCodeのRemote Containerで、爆速の"開発コンテナ"始めよう

VSCode の Remote Conainer で"開発環境+プロジェクト全部入りのコンテナ"から開発をスタートダッシュをキメませんかッ!?

開発でVS Code の Remote Conainer使っていますか?単に既存のコンテナに入るだけなら Remote SSH でも構いませんが、"ローカル開発環境の一部"として、いやむしろローカルの開発環境=Remote Containerとして、ビンビンにRemote Container使っていきましょう。令和だし!(すでに2年だけどね・・・?)

特にMacを使っていると最初からPythonやらPHPやらRubyやらが入ってしまっているので開発環境があるのですが、これらは割とmacOSのエコシステムに組み込まれているので不要にパッケージの追加削除、できないのですよ。brewとか意外とあっさり壊れますしね・・・。特にバージョンアップなんてもってのほかです。全然、余裕でおかしくなります。
そんなわけでMacに入っているPythonやRubyでプログラミングをバリバリしていると・・・ふと、後戻りできない状況になったりするわけです。
そんなことにならないためにも、"Remote Containerでの開発"に入門しましょう~!

"Dev Container" 機能のご紹介

Remote Container の本家サイトと本家Githubで"Try a dev container"と言う項目とリポジトリがあるの、ご存知でしょうか?

"dev container"は開発環境入りコンテナが付属したプロジェクトのサンプルで、以下の各言語向けにdev-containerのサンプルが用意されています。

  • Node.js, Javascript
  • Python
  • Go
  • Java
  • .Net Core
  • PHP
  • Rust
  • C++

例えば、node.js、Javascript用のサンプルは以下のようなツリーになっております。

% git clone https://github.com/Microsoft/vscode-remote-try-node nodejs-dev-sample
% cd nodejs-dev-sample
% tree -a -I ".git"
.
├── .devcontainer
│   ├── Dockerfile
│   └── devcontainer.json
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── .vscode
│   └── launch.json
├── LICENSE
├── README.md
├── package.json
├── server.js
└── yarn.lock

treeコマンドで.gitだけ除外して全て表示すると上記のようになります。
ここで気になるのが・・・.devcontainerですよね?!
中身のDockerfiledevcontainer.jsonは以下のようになっております。

#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------

FROM node:10

# The node image includes a non-root user with sudo access. Use the "remoteUser"
# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs
# will be updated to match your local UID/GID (when using the dockerFile property).
# See https://aka.ms/vscode-remote/containers/non-root-user for details.
ARG USERNAME=node
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive

# Configure apt and install packages
RUN apt-get update \
    && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ 
    #
    # Verify git and needed tools are installed
    && apt-get -y install git iproute2 procps \
    #
    # Remove outdated yarn from /opt and install via package 
    # so it can be easily updated via apt-get upgrade yarn
    && rm -rf /opt/yarn-* \
    && rm -f /usr/local/bin/yarn \
    && rm -f /usr/local/bin/yarnpkg \
    && apt-get install -y curl apt-transport-https lsb-release \
    && curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \
    && echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update \
    && apt-get -y install --no-install-recommends yarn \
    #
    # Install eslint globally
    && npm install -g eslint \
    #
    # [Optional] Update a non-root user to UID/GID if needed.
    && if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \
        groupmod --gid $USER_GID $USERNAME \
        && usermod --uid $USER_UID --gid $USER_GID $USERNAME \
        && chown -R $USER_UID:$USER_GID /home/$USERNAME; \
    fi \
    # [Optional] Add add sudo support for non-root user
    && apt-get install -y sudo \
    && echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME \
    #
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=dialog

上記の解説は後に回して、続いてdevcontainer.jsonの中身は・・・

devcontainer.json
{
    "name": "Node.js Sample",
    "dockerFile": "Dockerfile",

    // Use 'appPort' to create a container with published ports. If the port isn't working, be sure
    // your server accepts connections from all interfaces (0.0.0.0 or '*'), not just localhost.
    "appPort": [3000],

    // Comment out the next line to run as root instead.
    "remoteUser": "node",

    // Use 'settings' to set *default* container specific settings.json values on container create. 
    // You can edit these settings after create using File > Preferences > Settings > Remote.
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash"
    },

    // Specifies a command that should be run after the container has been created.
    "postCreateCommand": "yarn install",

    // Add the IDs of extensions you want installed when the container is created in the array below.
    "extensions": [
        "dbaeumer.vscode-eslint"
    ]
}

と、コンテナの設定とvscodeのsettings.jsonの混ざったような形式のファイルとなっております。
この設定がなんなのかはだいたい、想像がつくかと思いますが、このフォルダをVSCodeで開くと何が起こるのでしょうか…試してみましょう!
その前に、Dockerデーモンが立ち上がってない方は事前にDockerデーモンを立ち上げてください。WindowsやMacの方はDocker for Desktopを起動しておいてください。

Dockerが起動したらVSCodeを立ち上げて nodejs-dev-sample を開いてみます。
すると・・・
スクリーンショット 2020-01-03 14.10.20.png

dev container configurationが見つかったからコンテナで開くか?と言う問い合わせが出ました!
そして Reopen in Containerをクリックすると・・・
スクリーンショット 2020-01-03 14.22.25.png
しばらく時間が経って…開きました!左下のステータスバーがグリーンに変わってRemote接続中であることと、"Dev Container: Node.js Sample"の文字が眩しいですね!
そうなんです。.devcontainerフォルダとdevcontainer.jsonの設定と然るべきDockerfileが揃っていればプロジェクトフォルダ内のファイルを丸ごとマウントしたコンテナの自動生成とプロジェクトをVSCodeで開くのを勝手にやってくれるのです!

また、ここでのDockerfileの特筆するべき点はdockerに問題の"root"ユーザー問題を宜しく解決してくれている点です。
"コンテナの中で開発する"のは聞こえはいいですが、大抵のイメージはそのまま実行するとユーザーが"root"になってしまうのが多いので、コンテナの中で更新されるファイルのオーナーが"root"になってしまってウザい問題がありました。自前でDockerfile書けばなんとでもなるのですがいちいち書いてられないし、いちいちDockerfile書くくらいなら開発環境汚れても別にいいじゃん会社のPCだし、みたいなことになってますよね!?
このDockerfileでは丁寧にそこのところをサポートしてくれているので、rootユーザー問題が無事に解決されています。

ついでにコンテナの管理も VSCode からできるので…
スクリーンショット 2020-01-03 14.23.29.png
このようにどのプロジェクトでどのコンテナ使っているかは、VSCodeから管理できます。
特にDockerではどのフォルダのDockerfileで立ち上げたコンテナかがわからない(と言うかイメージを作ってコンテナ起動するのでどこのフォルダのDockerfileで作成したイメージかどうかは本来は関係ないハズ)のでこれは便利です。

どうやって使うのか?

まずは本家リポジトリのご紹介をしておきましょう。
リポジトリ名からどの言語向けのサンプルプロジェクトかわかると思います。

実際には.devcontainerフォルダとdevcontainer.jsonの設定と使用されるDockerfileの3つが揃っていれば自動的にやってくれますので、これらのファイルのみを本家リポジトリからコピペで作成するのもアリです。
とは言え毎度コピペするのも面倒なので、以下のように手順を整えてしまいましょう。

1. 雛形としてクローン

上記のリポジトリを以下のように"プロジェクトの名前"でクローンしてきます。

$ git clone https://github.com/microsoft/vscode-remote-try-php my-first-php

リポジトリ名の後ろの引数が作成されるフォルダ名で、別名でクローンするのが第1のミソです。

2. 履歴の削除と再作成

当然、cloneしたばかりでは本家リポジトリのコミット履歴を全て含んでおります。
このまま再利用しても全然、良いのですが・・・大抵の場合は気になるでしょう。
そこで .git 以下をざっくり削除します。

$ rm -rf .git

これで過去のコミット履歴は綺麗さっぱり忘れました。
ついでに不要なファイルは削除しておきましょう。
なんなら.devcontainer以外は不要です。.vscodeはお好みにお任せいたします。

で、お掃除が完了したところで新規のリポジトリとして初期化します。

$ git init
...

これで新たな開発コンテナプロジェクトとして第一歩が始まりました!

3. Dockerfile のカスタマイズ

実際の案件ではDBやAngular、Aws、Firebase、AzureなどのCLIなどなどなどを入れる必要があったりするのでDockerfileがそのまま使えることはほぼないです。
よってDockerfileに予め必要なツールをインストールするコマンドを入れておきます。

Dockerfileの記述に関してはここでは割愛させていただきます。。。

4. devcontainer.json のカスタマイズ

devcontainer.jsonではプロジェクト名や転送するポート、必要なVSCodeのプラグインなどの設定ができ、とても重要なファイルです。
設定値のリストは以下の箇所にあります。

サンプルリポジトリの中で使われている設定をいくつか抜き出してみますと・・・

  • settings … これはコンテナの中で使用される VSCodeのsetting.jsonの設定値となります。
  • appPort … 転送するポートです
  • postCreateCommand … コンテナ作成後に実行されるコマンド
  • extensions … リモート側にインストールされるVSCodeプラグイン

あたりが重要なカスタマイズするポイントでしょうか。

また、リファレンスによると docker-compose.ymlも利用可能な模様です。

これらを設定後に、VSCodeからフォルダを開けば、即座に開発環境込みのプロジェクトのスタートです!

まとめ

VScode Remoteコンテナの機能の入門編を書いてみるにあたって、公式ドキュメントを見直しましたが…ボリュームが半端ないですね。
これをどこまで深堀りするべきか悩みましたが、入門編ということでほとんど触れないようにいたしました(笑)
本文中に飛び飛びでリンクを貼っていますが、Remote Containerの公式ヘルプは驚愕の1ページです。

これだけで本書けそうなボリュームですよ・・・

というところで、今回はここまでといたします!

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

Javascriptで階層を持った名前空間を定義する関数

function namespace(ns) {
    var names = ns.split('.');
    var parent = window;

    for (var i = 0, len = names.length; i < len; i++) {
        parent[names[i]] = parent[names[i]] || {};
        parent = parent[names[i]];
        console.info(parent);
    }
    return parent;
}

// 実行確認
var aaa = namespace('Hoge.Fuga');
aaa.Piyo = function () {};
var bbb = new aaa.Piyo;
console.info(bbb instanceof Hoge.Fuga.Piyo);

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

Svelete module 挙動 メモ

image.png

App.svelte
<script>
    import { onMount } from 'svelte';

    import Box, {moduleVariable} from './Box.svelte';
    console.log(moduleVariable);

    let boxone;
    let boxtwo;

    onMount(async () => {
        console.log("boxone.text", boxone.text);
        console.log("boxone.moduleVariable", boxone.moduleVariable);
        console.log("boxone.getModuleVariable()", boxone.getModuleVariable());
        console.log(Box.moduleVariable);
    });

</script>

<Box bind:this={boxone} text="Box one" moduleVariable="aaa"/>
<Box text="Box two" moduleVariable="aaa"/>
Box.svelte
<style>
    .box {
        border: solid 1px black;
    }
</style>

<script context="module">
    let count = 0;
    export let moduleVariable = "a default variable in a module context";
</script>

<svelte:options accessors={true}/>

<script>
    import { onMount } from 'svelte';

    export let text = "Empty";

    onMount(async () => {
        count += 1;
        console.log(text, count);
    });

    export function getModuleVariable() {
        return moduleVariable;
    }

    function buttonClick() {
        console.log("buttonClick");
    }
</script>

<div class="box">
    {text}, {moduleVariable}
</div>
<button on:click={buttonClick}>
    button
</button>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript:クイックソートを末尾再帰にしてみる

クイックソートを末尾再帰にしようと思ったのだが、「あれ、これ無理じゃん」と思ったので調べてみた。
継続渡し、というものがあってそれを使うとできるらしい。

元記事:再帰クイックソートの可視化

詳しくは元記事を参照してください。
ここでは、アロー関数を使って今風にしてみたらどうなるか?をやってみます。

// 先頭の要素との大小で配列を二つに分割する関数
const divByHead = xs =>
  xs.slice(1).reduce(
    (acc,e)=>
      e <= xs[0] ? {...acc, le:[...acc.le, e]}
      : {...acc, gt:[...acc.gt, e]}
    , {le:[], gt:[]}
  )

// 末尾再帰でないクイックソート
const qs = xs =>{
  if(xs.length === 0) return []
  const divided = divByHead(xs)
  return  [...qs(divided.le), xs[0], ...qs(divided.gt)]
}

//継続渡しスタイルのクイックソート
const qs_cps = xs => k =>{
  if(xs.length === 0) return k([])
  const divided = divByHead(xs)
  return qs_cps(divided.le)(
    v1 => qs_cps(divided.gt)(
      v2 => k([...v1, xs[0], ...v2])
    )
  )
}

// 使いかた:
const qs2 = xs => qs_cps(xs)(v => v);
qs2([9,4,5,7,2,5,1]);   // [ 1, 2, 4, 5, 5, 7, 9 ]

qs([9,4,5,7,2,5,1]);    // [ 1, 2, 4, 5, 5, 7, 9 ]

できた!
どうしてうまくいくのか?説明はできないが、ふわっとは理解できたし、末尾再帰への変換もできそう。

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

pickadate.jsを使ってフォームにカレンダーを表示させる

こんにちは!スージーです!
久し振りにrailsでアプリ開発を開始したのでその際に使ったdatepickerについてまとめ

datepickerとは

スクリーンショット 2020-01-02 18.25.33.png

日付入力フォームを選択した時に表れるカレンダー

参考記事

結局、どのdatepickerが一番使い勝手がよいのかhttps://qiita.com/knt45/items/6d74f6785cd4547ae53b

Rails Application Build Guides
https://rails.densan-labs.net/form/datetime_register_form.html

pickadate.js
https://amsul.ca/pickadate.js/

pickadate-rails
https://github.com/veracross/pickadate-rails

準備

Gemfile
gem 'pickadate-rails'

bundle install

application.js
//= require pickadate/picker 追記
//= require pickadate/picker.date 追記
//= require pickadate/picker.time 追記
//= require jquery_ujs
//= require_tree .
application.scss
@import "pickadate/classic";
@import "pickadate/classic.date";
@import "pickadate/classic.time";

cssレイアウトはdefault又はclassicが選べるようになっている

Jsファイル

datepicker.js
$(function() {
  $( "#datepicker" ).pickadate(); // カレンダー表示のイベント
});

Jsはこれだけで使えるようになる。リファレンスには色々なレイアウトのカレンダーが用意されている。

turbokinksでイベント発火しない

jQueryあるあるですがturbolinksをtrueのままにしているとページをリロードしないとイベント発火しないので、今回は不要なので削除。application.js,application.html.erbのturbolinksに関するコードは削除。gem 'turbolinks'も削除してbundle installします。

contorollerのアクション作成

datepickers_controller.rb
class datepickersController < ApplicationController

  def new
    @datepicker = Datepicker.new
  end

  def create
    @datepicker = Datepicker.create(datepicker_params)
    if @datepicker.save
      redirect_to :root
    else
      render :new
    end
  end

  private

  def datepicker_params
    params.require(:start_day).permit(:start_day).merge(user_id: current_user.id)
  end
end

この辺はお決まりのnew/createアクションです

完成

カレンダーで日付を選択して登録できている事をパラメータを確認

ターミナル
Started POST "/start_days" for ::1 at 2020-01-03 12:50:13 +0900
Processing by StartDaysController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"4Ug39AI0MM0azF2XqNEcTOSQNjpNWQ+ovCSI7PFrPPatvEM8avbMZ6MII5JdxZnni+Hssbo7x6NTAjA0udiH7Q==", "datepicker"=>{"datepicker"=>"3 January, 2020"}, "commit"=>"登録"}

パラメータの値がちゃんとDBに保存されています

まとめ

日付の選択は何かを登録する場面で使う事が多いかと思いますし、年月日をそれぞれ入力するのはUI/UX的に煩雑になってしまいますが、datepickerを使えば便利になりますし、簡単に実装もできました。

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

React/Next.jsアプリケーションを作成し、AWS EC2を使って本番環境にデプロイするまで

対象

  • Next.js 等、Node.js で動かすアプリをローカル環境で作成することはできるが、それを本番環境で動かす方法と仕組みがわからない人
  • Heroku や Zeit Now を使うと簡単にリリースできるが、その仕組がさっぱりわかっていない人

この記事の存在意義

初心者が掲題のことをやろうと思ったときに、全体感を把握できる記事が見当たらなかったので、こういう記事があっても良いかなと思った。

流れ

以下のような手順で進める。
1. ローカル環境で動く Next.js を用いたサンプルアプリの作成
2. EC2 インスタンスの作成
3. EC2 インスタンス上でのサンプルアプリの起動&接続

1. ローカル環境で動く Next.js を用いたサンプルアプリの作成

  • 事前に npm をインストール
npm install -g npm

2. EC2 インスタンスの作成

公式に従ってやっていく。
自分の環境から ssh できるようにしておき、かつ http 通信はできるようにしておく。

3. EC2 インスタンス上でのサンプルアプリの起動&接続

3.1 外部アクセスをローカルホストにつなげるようにする

外部からアクセスがあったときに、そのアクセスをサンプルアプリが受け取って結果を返さなければならない。
しかしここまでのところ、localhost:3000 でアプリケーションを起動することしかできていない
そこで、「外部からのアクセスを受けたらそれをローカルホストにつなげるような仕組みを用意する」ことにする。

nginx を使ってそれを実現する。すなわち、「nginx をリバースプロキシとして用い、ポート80へのアクセスをlocalhostの3000ポートに振り向ける」ようにする。

  • ssh で EC2 インスタンスに入る。
ssh -i <key.pem>  <public ip address>
  • nginx の install
sudo su - root
yum update -y
yum install nginx -y

↑これを実行すると、nginx の install 時に Amazon Linux 2だとエラーになる。

Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
No package nginx available.
Error: Nothing to do


nginx is available in Amazon Linux Extra topics "nginx1.12" and "nginx1"

To use, run
# sudo amazon-linux-extras install :topic:

Learn more at
https://aws.amazon.com/amazon-linux-2/faqs/#Amazon_Linux_Extras

エラーメッセージの指示に従う。

sudo amazon-linux-extras install nginx1.12
  • nginx の起動
sudo systemctl start nginx.service

この時点で、ブラウザからアクセスすると以下のような状態になっているはず。

スクリーンショット 2020-01-03 4.09.47.png

  • nginx の設定変更(リバースプロキシの設定)
# 〜省略〜
http {
    # 〜省略〜
    server {
        # 〜省略〜
        location / {
            # 以下の行を追加
            proxy_pass http://localhost:3000;
        }
        # 〜省略〜
    }
}

この1行を追加することにより、ローカルホストの port:3000 に向けることができるようになる。ただし、nginx の設定を反映させるために nginx を再起動させることを忘れてはならない。

  • nginx の再起動
sudo systemctl restart nginx

この時点で、ブラウザからアクセスすると以下のような状態になっているはず。
スクリーンショット 2020-01-03 4.18.37.png

これはまだサンプルアプリを起動しておらず、振り向けた先から応答が返ってこないから。

3.2 サンプルアプリの起動

  • ssh で EC2 インスタンスに入る(既に入っていればそのままでOK)。
ssh -i <key.pem>  <public ip address>
  • git の install
sudo yum -y install git
vim ~/.ssh/id_rsa  # 自分のローカルのものをコピーしてくる
chmod 400 ~/.ssh/id_rsa
git clone https://github.com/<your sample app repository>
  • npm/node の install(公式のダウンロード方法はこちら)
sudo su - root
curl -sL https://rpm.nodesource.com/setup_13.x | bash -
yum install -y nodejs
  • 確認
$ npm -v
6.13.4
$ node -v
v13.5.0
  • アプリケーションのビルド
cd <your repository>
npm install
npm run build
  • アプリケーションの実行
npm run start

ブラウザから見てみると、以下のように適切に表示されているはず!

スクリーンショット 2020-01-03 4.19.03.png

まとめ

個人開発の際、なるべく早めに本番環境へのデプロイができることを確認しておくと、(不慣れな方は特に)不確実性を(そして心理的不安を)大きく減らすことができるかと思います。
まだ問題の切り分けを行いやすい最初のうちにこそ、本番環境でのデプロイを一度試してみることをおすすめします。

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

[連載]スーパーマリオ的なゲームをjavascriptで作ってみる 初級編 〜2章〜 簡単なページ作ってみる

概要

  • とりあえず簡単なページ作ってみる
  • キャラ画像の表示したり、入力キーに応じて表示内容変えるとかはこの後
  • 以下3ステップ
    • その1 〜html作ってページ開いてみるよの巻〜
    • その2 〜jsも組み込んでみるよの巻〜
    • その3 〜フレーム処理を組み込むよの巻〜

補足事項

  • 各ステップごとに実際のソースをQiita上に記載しています。コピペしてファイル作れば動かして確認できるはずです。
  • 上記と同じくソースの実態を保存しているgitのリポジトリも記載しています。
    リンクにアクセスして実際のソースをダウンロードすることができます。
    ぜひダウンロードして動かしながら試してみてください!

その1 〜html作ってページ開いてみるよの巻〜

ゴール

  • ゲームを動かす大元のwebページを作って、Chromeなどのブラウで開けることを確認する

前提

  • 主にwebページは以下の要素から成り立ちます
    • html : 何を表示するか (どんな文字や画像を表示するかなど)
    • css : どう表示するか (太文字や文字色や位置、背景をどうするかなど)
    • js : どのように動かすか (クリックした際やフォーカスした際にどうするかなど)
      ※ 詳細は省きますが、気になる方は後でググってみてください。(あとで参考リンク貼る予定)
  • ここでは上記の内の主にhtmlcss部分を1ファイルにまとめて作成します
  • 実際にはjsの部分がゲームの動きを決める要となります

やること

  • 以下記載のソースコードをコピってメモ帳やテキストエディタなどに貼り付けてファイル名を index.html で保存する
  • 作成したファイル index.html をChromeなどのwebブラウザで開く
  • 中央にグレーのエリアが表示されていることを確認する
    ※ コピーだと不要な書式情報が付与されてペーストされ適切に動かない場合があります。
     その場合は、以下のいづれかの方法を試してみてください
      ・ コピペしないで直接入力をする
      ・ リンクからダウンロードしてからファイル開いて修正する

▼ソースコード

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>スーパーマリオ</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }
      canvas {
        background: #eee;
        display: block;
        margin: 0 auto;
      }
    </style>
  </head>
  <body>
    <canvas id="maincanvas" width="960" height="640">
    <script>
      // JavaScriptのコードがここに入ります
    </script>
  </body>
</html>

※ 実際のソースコードは こちら からダウンロードできます

codepenでの確認は以下から


See the Pen
mario-game-tutorial-01-02-01
by taku7777777 (@taku7777777)
on CodePen.


説明

  • ソースの構成は以下
    • <head>〜</head> の中の <meta> : いろんな情報を指定することができる部分、ここでは文字コードを指定しています
    • <head>〜</head> の中の <title> : タブに表示されるタイトルを指定する部分
    • <head>〜</head> の中の <style> : 背景色などのcssにあたる部分
    • <body>〜</body> : 実際にwebページに表示する内容を定義する部分
    • <body>〜</body> の中の <canvas> の部分が実際にキャラクターなどを画面を表示する要素を記載
    • <body>〜</body> の中の <script> : 動き担当のjsを記載する
  • アレンジどころ
    • <title>スーパーマリオ</title>スーパーマリオ の部分を 俺様のゲーム などに変えるとタブに表示されるタイトルが変わる
    • background: #eeeeee の部分を 1E90FF などに変えると背景色が変わる 色の参考
    • <canvas id="maincanvas" width="960" height="640"></canvas>960 の部分を変えるとエリアの横幅が変わる
    • <canvas id="maincanvas" width="960" height="640"></canvas>640 の部分を変えるとエリアの縦幅が変わる

その2 〜jsも組み込んでみるよの巻〜

ゴール

  • js のファイルを追加して js ファイルに記載した内容が実行されることを確認する

前提

  • 実現方法はいくつかありますが、今回は htmlファイル(index.html)jsのファイル(index.js) の2ファイルの構成で進めていきます

やること

  • index.html があるのと同じ階層に index.js を作成する
  • index.html<script src="./index.js"></script> を追加する
  • index.js の中身を以下に記載している内容をコピペする
  • index.html をChromeなどのwebブラウザで開く
  • 画面上に start js alert が記載された簡易ダイアログが表示されることを確認する
  • (Chromeの場合) ディベロッパー・ツールのコンソールに start js console が記録されることを確認する (確認方法はこちら)

▼ソースコード

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>スーパーマリオ</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }
      canvas {
        background: #eee;
        display: block;
        margin: 0 auto;
      }
    </style>
  </head>
  <body>
    <canvas id="maincanvas" width="960" height="640"></canvas>
    <script src="./index.js"></script>
  </body>
</html>
index.js
// js の処理が開始されたことを確認するためのもの
alert(`start js alert`);
console.log(`start js console`);

※ 実際のソースコードは こちら からダウンロードできます

説明

  • <script src="./index.js"></script> は同階層の index.js を参照するよって宣言
  • ソースが実行される流れとしては以下
    • index.html が読み込まれる
    • index.html に記載の <script src="./index.js"></script> を確認
    • index.js が読み込まれる
  • index.htmlindex.js を参照するよって宣言を記載してあげないと index.js を作っても読み込まれません

  • alert(XXXXX)XXXXX の内容が画面上に簡易ダイアログで表示されます

  • console.log(XXXXX)XXXXX の内容がディベロッパー・ツールのコンソールにログが表示されます

  • alertconsole.log は意図通りに動いているか、変数に何が入っているかなどを簡易確認したい場合などに使えます
    サンプルソースは以下記載の内容で

    • come in if branch が表示され、記載のif文の分岐に入ることが確認できる
    • test is 2 が表示され、test の変数に 2 が入っていることが確認できる
index.js
var test = 1;
if(test === 1){
  alert('come in if branch');
  console.log('come in if branch');
  test++;
}

alert('test is ' + test);
console.log('test is ' + test);

その3 〜フレーム処理を組み込むよの巻〜

ゴール

  • スーパーマリオに必須のフレーム処理ができる
  • マリオやクリボーを動かすために、一定間隔ごとに画面を更新できるようにする

前提

  • リアルタイムにキャラクターや敵を動かしていく必要がある
  • 一定時間ごとに描画する必要があり、アニメーションのフレーム処理に使われるメソッドを用いて実現する
  • 右ボタン押されてたら右に少しづつ動き、ジャンプ中だったら次第に勢い失って落下していくことを実現するために必要
  • 右ボタン押されてたら0.1秒後には◯◯だけ右に動いていて、ジャンプ中だったら0.1秒後には速度が△△まで落ちてて、地面から□□のとこにいるみたいな
  • これを実現するためには一定間隔で次の状態はどうなっているかを決める処理を実行する必要がある

やること

  • index.js を以下に修正する
  • start js alert の簡易ダイアログまたはコンソールが表示されることを確認する
  • start update alert, cnt = 1 の簡易ダイアログまたはコンソールが表示されることを確認する
  • start update alert, cnt = 2 の簡易ダイアログまたはコンソールが表示されることを確認する
  • start update alert, cnt = 3 の簡易ダイアログまたはコンソールが表示されることを確認する
index.js
// js の処理が開始されたことを確認するためのもの
alert("start js alert");
console.log("start js console");

// ロード時に処理が実行されるようにする
window.addEventListener("load", update);

// update処理が実行された回数を記録するためのもの
var cnt = 1;

// ロード時に実行する関数の定義
function update() {
  // update 関数の処理が実行されたことを確認するためのもの
  alert("start update alert, cnt = " + cnt);
  console.log("start update alert, cnt = " + cnt);

  // 回数を1つ増やす
  cnt = cnt + 1;

  // 繰り返し処理を実行するよの定義
  window.requestAnimationFrame(update);
}

説明

  • window.addEventListener("load", XXXXXX)
    XXXXX で指定した関数を、ページロードのタイミングで実行するよっ登録することができる
  • function XXXXX(...) {....}
    XXXXX という名前の 関数 を定義することができる
    引数を (...) の部分で定義し、処理内容を {....} の部分で定義する
    ここでは関数の書き方の詳細は省略します
  • window.requestAnimationFrame(XXXXX)
    XXXXX で指定した 関数 を一定間隔後に実行する
  • 処理の流れとしては以下
    • index.js が読み込まれる(上から順に)
    • alert("start js alert") が実行される
    • console.log("start js console") が実行される
    • window.addEventListener("load", update) が実行され、loadをトリガーにupdate処理を実行するよってことが登録される (このタイミングでは処理が実行されない)
    • cnt変数 が1で指定される
    • update関数 が定義される (このタイミングでは処理が実行されない)
    • index.js が読み込みが完了する
    • load が実行される
    • 初めて update 処理が実行される
    • alert("start update alert, cnt = " + cnt) が実行され start update alert, cnt = 1 が表示される
    • console.logもそんな感じ
    • cnt が2になる
    • window.requestAnimationFrame(update) が実行され一定間隔後(基本1/60秒後)にupdate処理を実行するよってことが登録される (このタイミングでは処理が実行されない)
    • 一定間隔が経過する
    • 2回目の update 処理が実行される
    • alert("start update alert, cnt = " + cnt) が実行され start update alert, cnt = 2 が表示される
    • console.logもそんな感じ
    • cnt が3になる
    • window.requestAnimationFrame(update) が実行され一定間隔後(基本1/60秒後)にupdate処理を実行するよってことが登録される (このタイミングでは処理が実行されない)
    • 3回目の update 処理が実行される
    • ... 以下同様
  • 基本的にはソースの上から順に実行される
  • ただし記載の処理がそのタイミングで実行されるものと、実行されないものがあるので注意
  • 関数の定義や、特定のタイミングで指定の処理の実行を登録することなどは処理内容がその場で実行されるわけではないことに注意
  • 実行タイミングがわかりづらい場合などはconsole.logなどを仕込んで処理順序を確認してみると良いです
    サンプルソースは以下記載の内容で
    • update関数の中身は最後に繰り返し実行されることが確認できる
    • update関数の中身以外はは上から順に実行されていることが確認できる

▼サンプルソース

index.js
// js の処理が開始されたことを確認するためのもの
alert("start js alert");
console.log("start js console");

// ロード時に処理が実行されるようにする
console.log("start define addEventListener");
window.addEventListener("load", update);
console.log("end define addEventListener");

// update処理が実行された回数を記録するためのもの
console.log("start define cnt");
var cnt = 1;
console.log("end define cnt");

// ロード時に実行する関数の定義
console.log("start define update");
function update() {
  console.log("start update function, cnt = " + cnt);

  // 回数を1つ増やす
  cnt = cnt + 1;

  // 繰り返し処理を実行するよの定義
  window.requestAnimationFrame(update);
  console.log("end update function, cnt = " + cnt);
}
console.log("end define update");

実際のソースコードは こちら からダウンロードできます

▼出力結果

start js console
start define addEventListener
end define addEventListener
start define cnt
end define cnt
start define update
end define update
start update function, cnt = 1
end update function, cnt = 2
start update function, cnt = 2
end update function, cnt = 3
update function, cnt = 3
end update function, cnt = 4
start update function, cnt = 4
end update function, cnt = 5
...

終わり

次に進もう!(まだ作製中)

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

連絡先の電話番号やメールアドレスを並び替える(Google Apps Scriptで)

やること: 連絡先の電話番号やメールアドレスの並び替え

Google Apps Scriptで連絡先の電話番号やメールアドレスの並び替えをしてみたいと思います。
連絡先(人)の並び替えではなくて、1人の連絡先の中に複数の電話番号(携帯、家電、職場…)があったり、複数のメールアドレス(キャリア、Gmail、Yahoo、会社…)があったりするわけですが、これらの電話番号やメールアドレスを一定のルールのもと、並び替えようという試みです。

背景

何故かGoogle連絡先には電話番号やメールアドレスの項目順を入れ替える機能がないんですよね…加えて、僕が使っているMacOSの連絡先やiOSの連絡先にもそういった機能がなくて。サードパーティのアプリも探してみたものの、探し方が悪いのかヒットしなくて…

皆さん、あまり項目の順序なんて気にされないんですかね?
よく使う電話番号がよく使わない番号より下にあったら不便だと思うのですが。
手作業で直したり、エクスポート→Excelとかで加工→インポートしたりとかされているんでしょうか。

僕の場合、別の投稿でも言ったように、これまでに使用したメールエイリアスの大半を自分の連絡先に蓄えているという事情もあり、(項目数が多いので)手作業で切り貼りしての並び替えは避けたかったという事情もあります。

必要な知識

Google Apps Scriptで扱う連絡先(Contact)のクラスやメソッドは下記を参考にしてください。
https://developers.google.com/apps-script/reference/contacts/contact

中でも、

  • Contact
    • addEmail(label, address)
    • getEmails() : EmailField[]
  • EmailField
    • getLabel() : Object ( String OR Enum Field )
    • getAddress()
    • deleteEmailField()

辺りが肝になります。

やってみよう

手元で動かしているロジックは、もっといろんなことをやっているので、本質的な部分を切り出して紹介します。
(間違いを見つけられましたら、指摘してもらえると嬉しいです。)
それから、タイトルは「項目の並び替え」としていますが、見ていただくとわかるとおり「重複項目の削除」も行っています。

言うまでもありませんが、自己責任でお願いします。電話番号やメールアドレスを削除するメソッドが含まれますので、バックアップを取るとるとか、削除(と追加)の部分はコメントアウトして動かすなど、各自対策していただければ。

function sortFields() {
  // 0.1. 通知用
  const slackApp = new SlackApp();

  ContactsApp.getContacts().forEach(function (contact) {
    // 0.2. 会社(削除時通知用)
    const companies = contact.getCompanies();
    // 0.3. フルネーム(削除時通知用)
    const fullname = 
      (companies.length ? companies[0].getCompanyName() : "") + 
      contact.getFamilyName() + contact.getGivenName();

    // 1. 同じメールアドレスがいくつも登録されていたら削除する

    // 1.1. メールフィールド配列(登録状態)
    const emailFieldsOriginal = contact.getEmails();
    // 1.2. メールフィールド配列(ユニークにする)
    const emailFieldsUniq = emailFieldsOriginal.uniq(function (field1, field2) {
      return field1.getAddress() === field2.getAddress();
    });
    // 1.3. メールフィールド配列(重複分 = 登録状態 - ユニーク化したもの)
    const emailFieldsDiff = _.difference(emailFieldsOriginal, emailFieldsUniq);

    // 1.4. 重複削除
    emailFieldsDiff.forEach(function (field) {
      // 1.4.1. Slackで自分に通知
      slackApp.sendAttachments(
        fullname + "の連絡先から重複フィールド(" + field.getLabel() + ": " + field.getAddress() + ")を削除します。");
      // 1.4.2. 重複フィールドの削除
      field.deleteEmailField();
    });

    // 2. 並び替え

    // 2.1. メールフィールド配列(並び替え用)
    const emailFieldsSort = emailFieldsUniq.concat();
    // 2.2. 並び替え(並び替えのアルゴリズムは比較関数(Comparator)で提供)
    emailFieldsSort.sort(function (field1, field2) {
      return  // -1 OR 0 OR +1;
    });
    // 2.3. 並び替えで変更があったら、並び替えを連絡先に適用
    if (JSON.stringify(emailFieldsUniq) != JSON.stringify(emailFieldsSort)) {
      emailFieldsSort.forEach(function (field) {
        // 2.3.1. 新しい項目として連絡先に追加
        contact.addEmail(field.getLabel(), field.getAddress());
        // 2.3.2. 元からあった項目は消す
        field.deleteEmailField();
      });
    }
  });
}

重複項目を削除しよう

全体的にな流れ(アルゴリズム)としては、(メールアドレスで)ユニークにしたものと、する前のものを比較して、その差(=重複分)を削除するものとなります。
重要なのは比較対象がオブジェクト(EmailFiled)なので、「比較」には一工夫が必要ということです。

    // 1.1. メールフィールド配列(登録状態)
    const emailFieldsOriginal = contact.getEmails();
    // 1.2. メールフィールド配列(ユニークにする)
    const emailFieldsUniq = emailFieldsOriginal.uniq(function (field1, field2) {
      return field1.getAddress() === field2.getAddress();
    });

さらっとuniqなるメソッドを呼んでいますが、自前で実装したメソッド(下記参照)です。
引数に比較関数を与え、「何をもって等価、何を持って重複と見なすのか」を提供します。
Array.indexOf()で探したり、(後で出てくる)Undersocreのuniqを使うにしても、オブジェクトのポインタ同士を比較しても意味がないからです。

今回の要件はメールアドレスが同じであれば重複とみなすので、「メールアドレスで比較する」知識を持たせることにしています。

Array.prototype.uniq = function (callback) {
  return this.filter(function (element, i, array) {
    for (var j = 0; j <= i; j++) {
      if (callback.call(this, element, array[j])) {
        return i === j;
      }
    }
    return false;
  });
}
    // 1.3. メールフィールド配列(重複分 = 登録状態 - ユニーク化したもの)
    const emailFieldsDiff = _.difference(emailFieldsOriginal, emailFieldsUniq);

_Undersocoreのメソッドです。
(メールアドレスで)ユニークにしたものと、する前のものを比較して、その差分が重複分なので。
ここはオブジェクト同士の比較で大丈夫ですよね。

    // 1.4. 重複削除
    emailFieldsDiff.forEach(function (field) {
           :
          中略
           :
      // 1.4.2. 重複フィールドの削除
      field.deleteEmailField();
    });

一定のルールで項目を並び替えよう

結論から言うと、各EmailFieldを地道に1個1個、追加と削除を繰り返すことになります。
本当はContactクラスに登録順を入れ替えられたり、任意の位置に挿入できるメソッドがあればいいのですがね。
ですので、流れとしては「並び替えたEmailFieldを新たに新規追加(末尾に追加)」、「追加元のフィールドは削除する」というものになります。

    // 2.1. メールフィールド配列(並び替え用)
    const emailFieldsSort = emailFieldsUniq.concat();

あとでこの配列を並び替えることになるのですが、オリジナルの配列は取って置きたいので、配列を複製します。
後ろにconcat()を付けているのは、「配列を複製」したいからです。(単なる代入では複製にはなりませんよね)

    // 2.2. 並び替え(並び替えのアルゴリズムは比較関数(Comparator)で提供)
    emailFieldsSort.sort(function (field1, field2) {
      return  // -1 OR 0 OR +1;
    });

ここが肝なのですが、肝心なところは書いてないです…
オブジェクト(EmailField)の並び替えなので、自然順序付けは使えません。ここでも順序付けの知識を与える「比較関数」が必要なのです。
sortメソッドには引数に比較関数を渡します。比較関数は引数に2つの要素を受け取り、

  • 引数1 < 引数2であれば-1
  • 引数1 > 引数2であれば+1
  • 引数1 = 引数2であれば0

を返却することで、sortメソッドはオブジェクトの大小関係が理解できるようになり、並び替えを行います。

肝心な実装ですが、並び替えの要件は読み手それぞれなので、ここでは記載してないです。
僕の場合はEmailFieldのラベルと、メールアドレスのドメインを元に点数化して、大小関係を比較しています。ラベルが「廃止」とかだったら下位になるようにしたり、ドメインがキャリアメールだったら/クラウドサービスだったら、上位にする/下位にするとか、そんな具合です。
需要がありそうだったら、実装を公開するかもしれませんが、限りなく個人情報に近い要素があったりするわけで…

    // 2.3. 並び替えで変更があったら、並び替えを連絡先に適用
    if (JSON.stringify(emailFieldsUniq) != JSON.stringify(emailFieldsSort)) {
           :
          後述
           :
    }

後述のEmailFieldの追加・削除はコストがかかるので、並び替えの必要がない(=並び替え前後で変更がない)のなら、並び替えをスキップします。(そのために2.1.で配列をコピーしておきました)
配列の順序が変わっていないか比較するために、(荒っぽいですが)JSON.stringify化して比較しています。

      emailFieldsSort.forEach(function (field) {
        // 2.3.1. 新しい項目として連絡先に追加
        contact.addEmail(field.getLabel(), field.getAddress());
        // 2.3.2. 元からあった項目は消す
        field.deleteEmailField();
      });
}

新規の項目として末尾に追加、既存の項目は削除、これらを繰り返すことで、連絡先のEmailFieldが並び替えた配列順になります。

電話番号の並び替えはどうなるのか

電話番号の並び替えも上記と同じ要領でやるしかなさそうです。
基本的にはEmailPhoneに、getAddressgetPhoneNumberに読み替えることで、実現できます。
並び替えの比較関数の中身はきっとラベル(自宅を優先とか…)や電話番号の市外局番(携帯を優先とか…)にするといいと思います。

最後に

最後まで読んでくださってありがとうございます。
連絡先の中の電話番号やメールアドレスの並び替えの仕方がわからなくて、それなりに調べたのですが、それらしい方法に行き当たらず少々苦労したので公開することにしました。
もしかしたら、調べ方が悪くて、車輪の再発明だったり、より良い方法があるのかもしれませんが。

あとは自分の経験がなさすぎて、記述の粒度というか、何をどの程度まで書けばいいのかが悩みどころです。
今回で言うと比較関数の説明が長過ぎたかも。
記載するか否かを迷ったときは(億劫にならない程度に)記載することにしました。記載しない方に倒すと、そもそもこの投稿自体がいらないということにもなりかねないので…

それでは。

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

[連載]スーパーマリオ的なゲームをjavascriptで作ってみる 初級編 〜1章〜 準備する

概要

  • javascriptでスーパーマリオ的なゲームを作ります
  • プログラミング未経験者またはちょっとかじったことがある人向けです
  • 実際に動かしながら楽しくゲームを作っていけるような記事としたいです
  • 特に環境の準備は不要で、メモ帳があればOK
  • 後半ではリファクタしながら、こういう風に書いたら読みやすいよねまでやりたい

筆者紹介

  • 大学では建築を学ぶ
  • 就職先はなぜかIT (主にjava、サーバーサイド、自社のフレームワークを使用)
  • ITベンチャーに転職する (主にjavascript、フロント & サーバーサイド、AWSを使用)
  • そして今

背景

  • 職場で初学者にパッと進められるjavascriptの参考ページがなかったから
  • 楽しみながら、実際に動かしながら、初期ハードルが低く学べるといいなという思いがあったから
  • 過去にスーパーマリオのゲームを実際に作ってみて楽しく学習した経験があった
  • 将来の自分の子供の教材を自分で作ってみたかった (使うかは不明)
  • 書いてみたかったから

本記事の目的

  • 以下を達成すること
    • 初期ハードルを低く始めることができる
    • プログラミング初心者が楽しんでプログラミングに触れることができる
    • 本記事を元に実際に動くゲームを作ることができる
    • 本記事で記載しているプログラムの概要を理解し、アレンジしを加えることができる

対象者

  • プログラミング未経験者でやる気のある方
  • 年齢は中学生以上を想定(頑張れば小学生以下でも可能、たぶん)

補足

  • 所要時間はトータル5−10時間程度のつもり (内容をどこまで深く理解しようとするかで変動します)
  • 筆者のパソコンはMac
  • プログラムを書くためにしようしているエディタは VSCode

留意事項

  • 本記事は筆者がjavascriptの学習がてら独学で記載していることなので世間一般のお作法や語彙から逸脱している可能性があるかも
  • 特にロジック部分は勝手に考えて作ったものであることはご留意いただければと

全体の流れ

  1. 準備する
  2. 簡単なページ作ってみる
    1. html作ってページ開いてみるよの巻
    2. jsも組み込んでみるよの巻
    3. フレーム処理を組み込むよの巻
  3. 画像を動かしてみる
    1. 画像を表示できるよの巻
    2. 画像が動くよの巻
    3. キーボードの入力が感知できるよの巻
    4. 画像を動かせるよの巻
  4. 落下したら負け
    1. ジャンプさせてみる
    2. 着地判定させてみるその1
    3. 着地判定させてみるその2
    4. 着地判定させてみるその3?
    5. ゲームオーバーの定義をしてみるその1
  5. 敵にあったった負け
    1. あたり判定してみるその1
    2. あたり判定してみるその2
    3. あたり判定してみるその3
    4. ゲームオーバーの定義をしてみるその2
  6. 終わりの言葉 & 次のステージへ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む