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

【2分でイメージをつかむ】テンプレートエンジン・EJS(1/2)

あらすじ

  • 別記事で、「HTMLファイルを読み込んで表示させる」 ということをESオブジェクトでやりました
  • ただ、HTMLの中身(変数とか)を変更したりするのが面倒でした
  • もっと簡単に変数とか埋め込みたい!と思っていたらテンプレートエンジンに出会いました。
  • 色々使えそうなので、記事は2つにしようかと思います。
  • 今回は基本編です

準備するもの

  • npm で EJSをダウンロードしておいてください
  • もしここでnpmって何だろうかと思った方は、この記事を読む前にnpmについての記事を読んでおくことをおススメします。
  • うるせぇ! さっさとEJSダウンロードさせろ!という方は、下記コマンドでインストールできます  
npm install ejs

結局・・・EJSって何よ

<%= 値 %>

↑です。

HTMLファイルに変数渡すことができます!

hello.ejs
<html>
<body>
    <header>
        <h1 id="h1"><%=  %></h1>
    </header>
</body>
</html>

↑こんな感じ

 

EJSは、

  • HTMLファイルの中で「動的に値を変えたい」時に使えます
  • ファイルは「.ejs」という拡張子になります
  • このファイルは、「テンプレートファイル」と呼ばれます
  • テンプレートファイル内にある、「<%- %>」タグなどの特殊なタグが実際に出力されるテキストに変換されます

サンプル

  • hello.ejs
  • ejs.js

 

hello.ejs
<html lang="ja">
<body>
    <header>
        <h1 id="h1"><%=title %></h1>
    </header>
    <div role="main">
        <p><%-content %></p>
    </div>
</body>
</html>

ejs.js
var http = require('http');
// ファイルの読み込み(は、fsオブジェクトが担当する)
var fs = require('fs');
// テンプレートデータのレンダリング(は、ejsが担当する)
var ejs = require('ejs');

// ここは同期処理で読み込みます!
var hello = fs.readFileSync('./hello.ejs', 'utf8');

var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');

// リクエストの処理
function doRequest(req, res) {

    // ここでejsオブジェクトが働きます
    var hello2 = ejs.render(hello, {
        title:"title",
        content:"This is made by sample",
    });
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(hello2);
    res.end();
}

 

結果(ちょっとCSSとかで飾りつけしてます)

スクリーンショット 2019-05-26 23.39.24.png

 

ポイント!

var hello2 = ejs.render(hello, {
    title:"タイトルです",
    content:"これはサンプルで作成したテンプレートです。",
});
ejs.render( テンプレートデータ , オプション );
  • 第1引数――レンダリングする対象データ(=読み込んだテンプレートの文字列)を指定します。
  • 第2引数――テンプレートに渡す変数などの情報を連想配列にまとめたものを指定します。

第2引数を連想配列(ここではオブジェクト)で渡してやることで、テンプレートファイルに変数を渡すことができます。

まとめ

EJSは、サーバサイド(js)からテンプレートファイル(html)に変数を渡すことができる!

次回に続く

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

Vue+Vuetifyで作成したwebページをGitHub Pagesで無料公開

Vuetify.jsを使って作ったwebページをGitHubPagesで公開する機会があったので、環境構築の経緯を備忘録がてら残しておきます。

この記事ではデプロイすることに重きを置いているため、webページの中身には触れません。
私の見た限りでは、vue-cli3系についてビルドの仕方を詳しく書いてある記事が存在しなかった(2系は多くの記事に巡り会えました)ため、vuetifyを使わないという方でも参考になるかと思います。

nodebrewのインストールから書いているので、要らないところは目次を利用してスキップして下さい。

目次

  1. nodebrewのインストール
  2. vue-cli のインストール&プロジェクトの作成
  3. vuetifyのインストール
  4. ビルドする
  5. GitHub Pagesで公開

1. nodebrewのインストール

Homebrewでnodebrewを

ターミナル
$ brew install nodebrew

$ nodebrew setup
Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew
========================================
Export a path to nodebrew:

# ↓これをコピぺ
export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================
# 自分のターミナルに表示された上のやつコピペしてパス通す
$ export PATH=$HOME/.nodebrew/current/bin:$PATH

# 保存
$ source ~/.bash_profile
ターミナル
# とりあえず安定版を入れておく
$ nodebrew install-binary stable

# 使用設定
$ nodebrew use stable

# npmをアップデート
$ npm update -g npm

これでnpmが使えるようになりました。

2. vue-cli のインストール

さっき入れたnodebrewのおかげでvue-cliがインストールできます!

ターミナル
$ npm install -g @vue/cli

プロジェクトの作成をします。プロジェクト名はなんでも良いですが、ケバブケース(大文字アンダーバー無しでハイフンあり)で作成しましょう。

ターミナル
$ vue create project-x

設定を色々聞かれますので、適宜設定してください。サンプルページや小規模なページであればEnter連打で問題ないかと思われます。

設定が完了したら、プロジェクトが作成されます。中に入って、サーバーを起動してみましょう。

ターミナル
$ cd project-x
$ npm run serve
App running at:
  - Local:   http://localhost:8080
  - Network: http://hogehoge:8080

ブラウザでhttp://localhost:8080にアクセスするとサンプルが表示されます。
スクリーンショット 2019-05-26 19.06.39.png

3. vuetifyのインストール

このままvuetifyもインストールしちゃいましょう。

ターミナル
$ vue add vuetify

サーバを起動します.

ターミナル
$ npm run serve
App running at:
  - Local:   http://localhost:8080
  - Network: http://hogehoge:8080

ブラウザでhttp://localhost:8080にアクセスして、先ほどとページが変化しているのを確認しましょう。
スクリーンショット 2019-05-26 19.06.55.png

4. ビルドする

いよいよビルドです。ビルドのコマンドは$ npm run build です。
コマンドを実行するとdistという名前のフォルダが作成され、その中に入っているindex.htmlが本体です。
しかし、ブラウザで開いてみると真っ白なページが表示されるだけでサンプルの姿はどこにもありません。
これはvue.jsがビルドする際にデフォルトでjsとcssを絶対パスで取得するようにdistを作成してしまうことが原因です。ビルドする際の設定は、vue.config.jsという名前のファイルをプロジェクト直下に作成し、そこを編集することで変更できます。
ということで、相対パスに変更しちゃいましょう。

ターミナル
$ touch vue.config.js
$ vim vue.config.js

vue.config.jsに下の4行を追加します。

vue.config.js
module.exports = {
  publicPath: "./",
  assetsDir: ""
}

※vue.config.jsをプロジェクト直下に作成し、上記の4行を追加すれば良いので勿論方法は自由です。

変更できたらビルドしましょう。

ターミナル
$ npm run build

サンプルページが表示されていれば成功です!

5. GitHub Pagesで公開

この記事ではCreate a new repositoryからPublicなリポジトリを作成する所まで割愛します。
GitHubのアカウント作成などのチュートリアルは優良な記事が大量に転がっているのでそちらを参考にしていただければ幸いです。

現状、.gitignore(隠しファイル)にdistが登録されているので、それを削除します。(Finderの場合、cmd+shift+. で隠しファイルの表示/非表示が設定できます。)

ターミナル
$ vim .gitignore
.gitignore
.DS_Store
node_modules
/dist <------- これを削除

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

あとはクローンしてきて、クローンしたフォルダにプロジェクトの中身を全部入れます。(入れるのはdistだけでも表示できます。)

ターミナル
$ git clone https://github.com/アカウント名/リポジトリ名.git

あとはpushして準備完了。

ターミナル
$ git add -A
$ git commit -m "コメント"
$ git push origin master

githubリポジトリのSettingsから
スクリーンショット 2019-05-26 23.06.35.png
SourceをNoneからmaster branchに変更して完成です。
スクリーンショット 2019-05-26 23.07.35.png

https://ユーザ名.github.io/リポジトリ名/dist/index.html にアクセスして、無事ページが表示されればOKです!

最後に

vue-cli2系のconfig/index.jsと異なり、vue.config.jsは自分で作らなくてはならないことに気がつかず、気が狂うほどの時間を浪費してしまいました。公式リファレンスを熟読すれば一瞬でしたね...
余談にはなりますが、Vuetifyの公式リファレンスは英語で見ることを強く推奨します。日本語訳もありますが、なぜか情報の多くが欠落しています。ここも個人的ハマりポイントでした...

この記事に関してですが、自分自身理解の至らぬ点が多いと感じていますので、ミスの指摘や怪しいところがあれば、コメントに書いていただければ幸いです。
読んでいただきありがとうございました。

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

VuetifyのsampleをGitHub Pagesで公開

Vuetify.jsを使って作ったwebページをGitHubPagesで公開する機会があったので、環境構築の経緯を備忘録がてら残しておきます。
nodebrewのインストールから書いているので、要らないところは目次を利用してスキップして下さい。
私の見た限りでは、vue-cli3系についてビルドの仕方を詳しく書いてある記事が存在しなかった(2系は多くの記事に巡り会えました)ため、vuetifyを使わないという方でも参考になるかと思います。

目次

  1. nodebrewのインストール
  2. vue-cli のインストール&プロジェクトの作成
  3. vuetifyのインストール
  4. ビルドする
  5. GitHub Pagesで公開

1. nodebrewのインストール

Homebrewでnodebrewを

ターミナル
$ brew install nodebrew

$ nodebrew setup
Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew
========================================
Export a path to nodebrew:

# ↓これをコピぺ
export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================
# 自分のターミナルに表示された上のやつコピペしてパス通す
$ export PATH=$HOME/.nodebrew/current/bin:$PATH

# 保存
$ source ~/.bash_profile
ターミナル
# とりあえず安定版を入れておく
$ nodebrew install-binary stable

# 使用設定
$ nodebrew use stable

# npmをアップデート
$ npm update -g npm

これでnpmが使えるようになりました。

2. vue-cli のインストール

さっき入れたnodebrewのおかげでvue-cliがインストールできます!

ターミナル
$ npm install -g @vue/cli

プロジェクトの作成をします。プロジェクト名はなんでも良いですが、ケバブケース(大文字アンダーバー無しでハイフンあり)で作成しましょう。

ターミナル
$ vue create project-x

設定を色々聞かれますので、適宜設定してください。サンプルページや小規模なページであればEnter連打で問題ないかと思われます。

設定が完了したら、プロジェクトが作成されます。中に入って、サーバーを起動してみましょう。

ターミナル
$ cd project-x
$ npm run serve
App running at:
  - Local:   http://localhost:8080
  - Network: http://hogehoge:8080

ブラウザでhttp://localhost:8080にアクセスするとサンプルが表示されます。
スクリーンショット 2019-05-26 19.06.39.png

3. vuetifyのインストール

このままvuetifyもインストールしちゃいましょう。

ターミナル
$ vue add vuetify

サーバを起動します.

ターミナル
$ npm run serve
App running at:
  - Local:   http://localhost:8080
  - Network: http://hogehoge:8080

ブラウザでhttp://localhost:8080にアクセスして、先ほどとページが変化しているのを確認しましょう。
スクリーンショット 2019-05-26 19.06.55.png

4. ビルドする

いよいよビルドです。ビルドのコマンドは$ npm run build です。
コマンドを実行するとdistという名前のフォルダが作成され、その中に入っているindex.htmlが本体です。
しかし、ブラウザで開いてみると真っ白なページが表示されるだけでサンプルの姿はどこにもありません。
これはvue.jsがビルドする際にデフォルトでjsとcssを絶対パスで取得するようにdistを作成してしまうことが原因です。ビルドする際の設定は、vue.config.jsという名前のファイルをプロジェクト直下に作成し、そこを編集することで変更できます。
ということで、相対パスに変更しちゃいましょう。

ターミナル
$ touch vue.config.js
$ vim vue.config.js

vue.config.jsに下の4行を追加します。

vue.config.js
module.exports = {
  publicPath: "./",
  assetsDir: ""
}

※vue.config.jsをプロジェクト直下に作成し、上記の4行を追加すれば良いので勿論方法は自由です。

変更できたらビルドしましょう。

ターミナル
$ npm run build

サンプルページが表示されていれば成功です!

5. GitHub Pagesで公開

この記事ではCreate a new repositoryからPublicなリポジトリを作成する所まで割愛します。
GitHubのアカウント作成などのチュートリアルは優良な記事が大量に転がっているのでそちらを参考にしていただければ幸いです。

現状、.gitignore(隠しファイル)にdistが登録されているので、それを削除します。(Finderの場合、cmd+shift+. で隠しファイルの表示/非表示が設定できます。)

ターミナル
$ vim .gitignore
.gitignore
.DS_Store
node_modules
/dist <------- これを削除

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

あとはクローンしてきて、クローンしたフォルダにプロジェクトの中身を全部入れます。(入れるのはdistだけでも表示できます。)

ターミナル
$ git clone https://github.com/アカウント名/リポジトリ名.git

あとはpushして準備完了。

ターミナル
$ git add -A
$ git commit -m "コメント"
$ git push origin master

githubリポジトリのSettingsから
スクリーンショット 2019-05-26 23.06.35.png
SourceをNoneからmaster branchに変更して完成です。
スクリーンショット 2019-05-26 23.07.35.png

https://ユーザ名.github.io/リポジトリ名/dist/index.html にアクセスして、無事ページが表示されればOKです!

最後に

vue-cli2系のconfig/index.jsと異なり、vue.config.jsは自分で作らなくてはならないことに気がつかず、気が狂うほどの時間を浪費してしまいました。公式リファレンスを熟読すれば一瞬でしたね...
余談にはなりますが、Vuetifyの公式リファレンスは英語で見ることを強く推奨します。日本語訳もありますが、なぜか情報の多くが欠落しています。ここも個人的ハマりポイントでした...

この記事に関してですが、自分自身理解の至らぬ点が多いと感じていますので、ミスの指摘や怪しいところがあれば、コメントに書いていただければ幸いです。
読んでいただきありがとうございました。

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

JavaScript配列の空文字を削除

filter()メソッドを使って配列の空文字、nullundefinedを削除する

filter(Boolean)

var arr = ["hoge", "", "123", " ", "0", 123, 0, null, undefined];

arr.filter(Boolean); //["hoge", "123", " ", "0", 123]

// アロー関数式
arr.filter(n => n); //["hoge", "123", " ", "0", 123]

説明

上の使い方をテスト関数を作成して、引数で渡す方式で書くとこうなる

function isTrue(value) {
  return Boolean(value);
}

var arr = ["hoge", "", "123", " ", "0", 123, 0, null, undefined];
arr.filter(isTrue); //["hoge", "123", " ", "0", 123]

filter()はテスト関数がfalseを返す要素は取り除かれる。

この場合は各要素をBooleanに変換すると空文字、nullなどはfalseになるので、取り除かれる。

注意すること

  • Numberの「0」も消えちゃうこと
  • 空白(スペース)は消えないこと

参考

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

JavaScriptの非同期処理はasync/await/Promise

JavaScriptの非同期処理についてまとめた

今週からモダンなJavaScriptを使った開発案件にjoinすることになりました。
JavaScriptなら専門学校でも勉強したしいける!と思っていましたが、最近のJavaScriptは色々と複雑なうえに、そもそも入門レベル(苦笑)しか無かったので改めて勉強しようとおもいました。

今回は同期処理と非同期処理の違い、そして非同期処理の取り扱い方について書いていきます。

同期処理と非同期処理

  • 同期処理(sync)・・・コードを順番に処理していき、ひとつの処理が終わるまで次の処理をしない
  • 非同期処理(async)・・・処理が終わるのを待たずに次の処理をする

同期処理

test.js
const a = () => console.log("a");
const b = () => console.log("b");
const c = () => console.log("c");

a()
b()
c()
実行結果
a
b
c

普通に書くと上から順番に実行されます。これが同期処理です。a→b→cの順番で書いてあるわけですから、実行結果がその順番になるのは当然です。

非同期処理

test2.js
const a = () => console.log("a");
const b = () => {
  setTimeout(() => {
    console.log("b")
  }, 1000);
}
const c = () => console.log("c");

a()
b()
c()
実行結果
a
c
b

次にbの処理を書き換え、実行されて1秒待ってからbを表示するようにしました。

setTimeout関数は、第一引数に関数、第二引数に時間(ミリ秒)をとります。そして第二引数の時間経過後に第一引数を実行します。上のように第一引数の中に関数をぶち込んでもいいし、別で定義しても構いません。

結果a→b→cの順番で呼び出したのに、コンソール上の結果はa→c→bの順番になってしまいました。何が起きたのかというと、bの中にあるsetTimeout関数の結果を待たずに次のcが実行されたのです。
setTimeout関数のように、実行結果を待たずに次の処理が実行されてしまうものを非同期処理といいます。書いた順番と違った順番で処理される可能性があるわけですね。

それでも「俺はこの順番に実行したいんじゃー」という時があるとおもいます。例えばあるデータを外部から取ってきて計算したい場合、データが返ってくる前に計算は不可能ですよね。時間がかる処理があっても決まった順番に処理したい場合は以下のような対策があります。

Promise

test3.js
const a = () => console.log("a");
const b = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("b")
      resolve();
    }, 1000);
  });
}
const c = () => console.log("c");

a()
b().then(c)
実行結果
a
b
c

これで無事解決です。

ポイントはPromiseインスタンスを生成して返却する点です。
Promiseは関数を引数にとりますが、その関数の第一引数にresolve、第二引数にreject(省略可)の2つのコールバック関数をとります。この2つの名前は適当でもいいです。そして処理の成功時はresolveメソッドを実行してあげます。

あとは実行時にthenメソッドを実行し、引数に次に実行したい関数を入れれば完成です。

test4.js
//実行部分
a()
b().then(c).catch(d)

もし「外部にデータを取りに行ったけど、データが取ってこれませんでした」という時には、resolveメソッドではなくrejectメソッドを実行してあげてください。データが取れたかどうかで条件分岐すると良いですね。そしてthenメソッドの次にcatchメソッドを書き加えればOKです。

上の例は成功すればc、失敗ならdを実行します。

async/await

test5.js
const abc = async() => {
  const a = () => console.log("a");
  const b = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("b")
        resolve();
      }, 1000);
    });
  }
  const c = () => console.log("c");

  a()
  await b()
  c()
}

abc();
実行結果
a
b
c

1行目ですが、今までの処理を関数abcでまとめて、asyncを付けました。
asyncを付けた関数は必ずPromiseインスタンスを返します。そしてasync関数内ではawait を使えます。こうすることでawaitが付けられた関数の処理を待ってから次の処理を行います。thenをつなげて書くよりも、見た目が同期処理っぽく書けるのが良い点です。

まとめ

JavaScriptの非同期処理はasync/await/Promise♪(タイトル回収)

参考

js-prime 非同期処理 https://jsprimer.net/basic/async/

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

Snap.svgを使ったフレームアニメーション

動機

画像のフレームなどをもっと自由に動かしてみたいと思いSVGに手を出しました。
Snap.svgなどのライブラリを使えば比較的簡単にアニメーションを実装することができました。

完成品

まずはじめに、今回説明する内容の完成品です。

コード

See the Pen SnapFrame01 by daichi (@da10410) on CodePen.

プレビュー

See the Pen SnapFrame01 by daichi (@da10410) on CodePen.

SVGを作成する

まずはフレームとして使用するSVGを作成します。
illustratorなどがあれば理想ですが、無ければ代替となる何かを使用しましょう。
私の場合は『 Vector 』というオンライン上でSVGを
作成することができるツールを使用して作成しました。

動く前のSVGと、動いた後のSVGの2種類作成してください。また、
動く前と後のアンカーポイントの数は、同一にしておくと後々複雑なSVGを扱う際などに楽になります。

今回作成したSVGのイメージは下記の様なものを右上用と左下用のそれぞれ作成したので
計4枚になります。

Before

beforeSVG.png

After

afterSVG.png

ベジェ曲線の描き方などは、使用するツールによって異なるので今回は省略させていただきます。

HTML

CodePenの例ではPugを使用していますが、分かりやすくするためにHTMLにコンパイルしたもので解説を行います。
また、例はCodePenのJSの設定で下記CDNを読み込んでいます。実際に実装する場合には任意の個所で
下記のCDNを読み込むようにしてください。

Jquery
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Snap.svg
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js"></script>
index.html
<div class="btn">BTN</div>
<div class="img-section">
  <div class="header">Test</div>
  <div class="img-box">
    <div class="img-ttl">dummy image</div>
    <div class="img-dummy"></div>
    <div class="img-frame">
      <svg class="frame" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="none" viewBox="0 0 640 640">
        <defs>
          <path class="bottom-wave" id="cBBFTBwXK" d="M340 264.74C233.33 158.07 120 69.82 0 0L0 640L640 640C546.67 496.49 446.67 371.4 340 264.74Z"></path>
          <path class="top-wave" id="abb2YnZoD" d="M300 340C406.67 446.67 520 546.67 640 640L640 0L0 0C93.33 120 193.33 233.33 300 340Z"></path>
        </defs>
        <g>
          <g>
            <g>
              <use xlink:href="#cBBFTBwXK" opacity="1" fill="#dfe6e9" fill-opacity="1"></use>
            </g>
            <g>
              <use xlink:href="#abb2YnZoD" opacity="1" fill="#dfe6e9" fill-opacity="1"></use>
            </g>
          </g>
        </g>
      </svg>
    </div>
  </div>
</div>

作成したSVGファイルを「 sublimeText 」や「 Visual Stadio Code 」などの
テキストエディタで開きましょう。すると、下記の様なhtml文で表示されるかと思います。

svg.html
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 640 640" width="640" height="640"><defs><path d="M167.52 45.18L283.83 45.18L283.83 145.89L167.52 145.89L167.52 45.18Z" id="i25ErMfTfi"></path></defs><g><g><g><use xlink:href="#i25ErMfTfi" opacity="1" fill="#c1ecad" fill-opacity="1"></use></g></g></g></svg>

この<svg>タグ内を見やすいようインデントを揃えコピーしたものをindex.htmlの
<div class="img-frame">内に貼り付けています。

またSVGを同一svgタグ内で2枚使用する場合、
2枚目の<def>タグ内、<path>タグをコピーして1枚目で作成したhtml文の<def>タグ内に
貼り付けるだけで使用可能です。今回のindex.htmlでも同様にクラス名「bottom-wave」と「top-wave」で
同一svgタグ内に2枚分のSVGを出力するようにしています。

CSS

今回は割愛します。

JS

続いてjsの説明です。

index.js
const $topw = Snap(".top-wave");
const $btmw = Snap(".bottom-wave");

let states = "close";

$('.btn').on('click', function(){
  if(states === "close"){
    $btmw.animate({d:"M40 600C20 580 6.67 553.33 0 520L0 640L120 640C86.67 633.33 60 620 40 600Z"},350, mina.easein);
    $topw.animate({d:"M600 40C620 60 633.33 86.67 640 120L640 0L520 0C553.33 6.67 580 20 600 40Z"},350, mina.easein);
    states = "open";
  }else{
    $btmw.animate({d:"M340 264.74C233.33 158.07 120 69.82 0 0L0 640L640 640C546.67 496.49 446.67 371.4 340 264.74Z"},350, mina.easein);
    $topw.animate({d:"M300 340C406.67 446.67 520 546.67 640 640L640 0L0 0C93.33 120 193.33 233.33 300 340Z"},350, mina.easein);
    states = "close";
  }
});

pathの指定

まず、操作する<path>タグを指定します。
今回はそれぞれのパスにクラスを付けていたのでクラス名で指定します。
Snapという関数の様なもので指定します。指定の仕方はJqueryと
似ているので、比較的使いやすいかと思われます。

pathの指定
const $topw = Snap(".top-wave");
const $btmw = Snap(".bottom-wave");

アニメーション実行

ボタンをクリックした際にアニメーションが実行されるようにします。
現在、SVGがどのような状態になっているかをstatesという変数で管理し
閉じていたら開いているSVGのパスに、開いていたら閉じているSVGのパスにアニメーションが
実行されるようにしています。

アニメーションは、先ほど取得したパスにanimateという関数を実行することで
実行する事ができます。
HTML部分の説明で行ったように、アニメーション後のSVGをテキストエディタで開き、
<pathタグ内のd="~~~~"をコピーして=:に変更したものを
関数の第1引数として指定します。第2引数に何秒間かけてアニメーションを行うか、第3引数にイージングを指定します。

(変更するパス).animate({(d:"~~~~")},アニメーション秒数, イージング);

これでSVGのアニメーションを実行する事ができます。

まとめ

Snap.svgを使用するとSVGのアニメーションをJqueryと同じような感覚で簡単に実装する事ができます。
ループで実行することもできるので、フレームをずっとうねうねさせることも可能です。
CSSのみでは出来ない複雑なうごきもSVGを作成する事が得意であれば簡単に実装できるので
デザインの幅が広がると感じました。

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

14 分で JavaScript 入門

JavaScript はプログラミング入門に最適な言語である

以前、プログラミング初心者にお勧めする入門用言語という記事で自分の考えを詳しく解説したのですが、JavaScript はプログラミングを初めてやる人にとてもお勧めの言語です。ただ具体的にどのようにプログラミングを始めたら良いかという事については、その記事ではあまり触れることはありませんでした。

そんな中、その記事でも少し触れたのですが、JavaScript in 14 minutes という素晴らしいサイトがあります。これは Jeremy Thomas さんという方が作ったコンテンツで、本当の本当に初めてプログラミングに取り組む人がスムーズに学べるように、とてもよく考えられています。JavaScript でプログラミング入門をするのであればこれほど良い最初の教材はないのですが、そこはやはり英語の壁があって、日本語話者に広く学んでもらうのは難しいなと思っていました。

14 分で JavaScript 入門!

その記事で書いた自分の理想をより進めるために、いっその事そのサイトを日本語に翻訳して公開してしまえばいいと考えました。そこで Jeremy さんに連絡を取り、許可をもらって自分のドメインで公開したのがこの 14 分で JavaScript 入門というサイトになります。

このサイトは JavaScript 入門を謳ってはいるものの、実質的にはプログラミング入門といって差し支えない内容です。従って、JavaScript に限らず例えば関数や変数、配列がどういったものか知っていたり、ある程度プログラムを書いたことがある人にはとても退屈な内容だと思います。あくまで人生で初めてプログラミングに触れるような人が対象です。

以前の記事の締めに「この記事をきっかけとして一人でも多くの人がプログラミングを楽しんでくれると嬉しい」と書きましたが、今回の 14 分で JavaScript 入門についてもはやり、このサイトをきっかけとして一人でも多くの人がプログラミングを楽しんでくれると嬉しいなと思います。

サイトの技術的な話

このサイトを作るにあたってまず最初にオリジナルのリポジトリをローカルにクローンしました。そしてざっくりとコードを読んでみたものの、最初はどうやら何らかのテンプレートエンジンを使用して静的サイトを作っているっぽい事以外よく分かりませんでした。

そこでテンプレートファイルの中に含まれている制御構文のうちやや特徴的なキーワードや、恐らくそのテンプレートエンジンの標準に従っているであろうディレクトリ名などを検索して、Jekyll を使っていることを突き止めました。これは Ruby で書かれた静的サイトジェネレータで、そこまで分かったら、ローカルのリポジトリ上で改変したサイトを動かすまではすぐでした。

ここで技術的な面では翻訳を進める目処が付いたので Jeremy さんに許可を取り付けて、そこから先、長〜いなが〜〜〜い翻訳の苦労があるのですが、そこは技術的な話では無いので割愛します。最終的に翻訳が完了した時点でオリジナルをフォークして差分をプッシュしました。

さて、翻訳が完了して静的なサイトをどこでホストするか、という話になるのですが、今回は Google Cloud 上で Node を動かしてホストしています。これ実は、デフォルトのまま運用しようとすると、うっかり警告無しに課金が発生してしまう可能性があったりするので、無料の範囲内で安全に使うにはちょっとコツがあるのですが、それはまた需要があれば別の記事にでもまとめることにしましょう。Node を利用して静的なサイトをホストしつつ、無料で済ませてかつ独自ドメイン HTTPS で公開する方法、とか需要ありそうですよね。

なお、Jekyll で静的サイトを生成したり、そこで作成されたものを Google Cloud にデプロイする基本的な方法なんかは、リポジトリ上の package.json やテンプレートファイルを見てもらえば雰囲気で分かると思うので、興味のある方はご覧下さい。

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

2019年のフロントエンドエンジニアへの道のり〜パッケージマネージャ編〜

はじめに

こんにちは。
最近、ロードマップのパッケージマネージャを進めているTakuyaHanadaです。
パッケージマネージャを勉強する際、簡単なサンプルを見つけることができず苦労しました。
そこで、誰かの役に立てばと思い、私がつくったものをご紹介します。
間違いなどがあればご指摘していただけると幸いです。

今回、パッケージマネージャを使ってつくるものはこちら
only_jquery.gif

サンプルコード
yarn-sample - GitHub

私が進めているロードマップ
https://github.com/kamranahmedse/developer-roadmap
2018年の最先端フロントエンドエンジニアになろう - Qiita

では、さっそくつくっていきましょう!

事前知識

  • HTML
  • CSS
  • JavaScript
  • jQuery(知らなくてもOK)
  • ターミナルの操作

環境

  • MacOS Mojave 10.14.5
  • Homebrew 2.1.3
  • Yarn 1.16.0

手順

0. 今回使うもの

  • Yarn
  • Webpack
  • jQuery

1. Yarnを使って準備をする

Yarnと同じようなものにnpmというものもありますが、公式によるとYarnの方が「はやい」「安全」「信頼できる」そうなので、Yarnを使います。
Yarn - 公式サイト

以下は、手順です。

  1. yarn initコマンドで新しいプロジェクトを作成します。Enterを連打してすべてデフォルトのままにします。

    (1-1)ターミナル
    $ yarn init
    
    yarn init v1.16.0
    question name (yarn-sample):  # Enter
    question version (1.0.0):  # Enter
    question description:  # Enter
    question entry point (index.js):  # Enter
    question repository url:  # Enter
    question author:  # Enter
    question license (MIT):  # Enter
    question private:  # Enter
    success Saved package.json
    ✨  Done in 9.46s.
    
  2. jQueryをインストールします。

    (1-2)ターミナル
    $ yarn add jquery
    

2. Webpackを使ってまとめる

パッケージをインストールしたし、あとはHTML, CSS, JavaScriptを書けば終わりかと思いきや、このままでは動きません。

そこで、登場するのがモジュールバンドラーというものです。
調べてみると、JavaScriptやCSS、画像などのファイルを1つにまとめてくれるものだそうです。
今回、「なんか見たことある」という理由で、Webpackというモジュールバンドラーを使います。
Webpackってどんなもの? - Qiita
webpack 4 入門 - Qiita

以下は、手順です。

  1. まず、Webpackをインストールします。
    オプション-Dについては公式に説明がありました。
    jQueryのようにコードを実行する際に必要なものはオプションをつけず、Webpackのように開発する際に必要なものは-Dオプションをつけるものだそうです。
    yarn add - Yarn
    依存関係の種類 - Yarn

    (2-1)ターミナル
    $ yarn add webpack webpack-cli -D
    
  2. Webpackでは、まとめたいファイルをsrcディレクトリに入れます。1つにまとめたファイルはdistディレクトリに出力してくれます。
    というわけで、src, distディレクトリを作成します。作成後のディレクトリ構成は以下の通り。

    (2-2)ディレクトリ構成
    .
    ├── node_modules/
    ├── package.json
    ├── dist/  # 新しく作成
    ├── src/  # 新しく作成
    └── yarn.lock
    
  3. distディレクトリにindex.htmlを作成します。

    (2-3)dist/index.html
    <!DOCTYPE html>
    <html lang="ja">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Yarn Sample</title>
        <style>
          #msg {
            display: none;
          }
        </style>
      </head>
    
      <body>
        <button id="btn">
          Click Me!
        </button>
        <h1 id="msg">
          Hello World!
        </h1>
    
        <script src="main.js"></script>
      </body>
    </html>
    
  4. srcディレクトリにindex.jsを作成します。

    (2-4)src/index.js
    const $ = require('jquery');  // jQueryを読み込む
    
    $(function() {
      $('#btn').click(function() {  // ボタンがクリックされたとき、
        $('#msg').slideDown();  // メッセージが出てくる
      });
    });
    
  5. Webpackでバンドルします(まとめます)。
    package.jsonにscriptsを書き加えます。

    (2-5)package.json
    {
      "name": "yarn-sample",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT",
      "dependencies": {
        "jquery": "^3.4.1"
      },
      "devDependencies": {
        "webpack": "^4.32.2",
        "webpack-cli": "^3.3.2"
      },
    ----------追加----------
      "scripts": {
        "build": "webpack --mode development"
      }
    ------------------------
    }
    
  6. あとは、yarn run build を実行すれば、distにmain.jsが生成されます。
    (さっき、scriptsに書いたwebpackが実行される)

    (2-6-1)ターミナル
    $ yarn run build
    
    (2-6-2)ディレクトリ構成
    .
    ├── node_modules/
    ├── package.json
    ├── dist
    │   ├── index.html
    │   └── main.js  # <-- 生成された main.js
    ├── src/
    └── yarn.lock
    
  7. index.htmlをブラウザで開いてみましょう!
    jQueryで書いた動作をしたらOKです。
    only_jquery.gif

おわりに

Webpackに関しては完全初心者でしたが、1日ちょいで学んだことをまとめてみました。

パッケージマネージャ、Webpackを触ってみたい人に役立つかなと思い記事を書いていましたが、この記事の上位互換のようなものがありました(笑)
npmとwebpack4でビルド - jQueryからの次のステップ - Qiita

心が折れそうになりましたが、せっかく途中まで書いたので書ききりました。
参考になってくれれば幸いです。

参考記事等

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

javascript学習① 20190410

javascriptについて学習をする

javascript.html
要素を全て読み込んで動作を加えたいため、
bodyタグの後半にscriptタグを書くことが一般的

<body>
    <div id="target" class="box"></div>

<script>
    'use strict';  

    document.getElementById('target').addEventListener('click', function(){
        document.getElementById('target').style.background = 'pink';
    });

    const target = document.getElementById('target')
</script>

document.getElementById('target')は要素をidで指定してgetする命令
.addEventListener('click', function(){〇〇}:何かをしたら何かの処理を行う(例)clickをした時にfunctionに記した動作を行う

fuctionには何個でも処理を追加することができる
document.getElementById('target').style.background = 'pink';getした要素に対して.で繋いで処理をかく

const hoge = 命令 で変数に置き換えることができる
<script>
for (let i=0;i<10;i++){
    if(i===num){
    }else{
    }
}
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Magic Leap MagicScript Landscape Application. XMLHttpRequest

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

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

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

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

Create Project

magic-script init my-xhr org.magicscript.xhr "XMLHttpRequest"
cd my-xhr

Code

Change app.js

import { LandscapeApp, ui } from 'lumin';

const { 
  UiLinearLayout, 
  UiText,
  UiButton,
  EclipseLabelType, 
  Alignment, 
  HorizontalTextAlignment} = ui;

export class App extends LandscapeApp {
  onAppStart () {
    const prism = this.requestNewPrism([0.5, 0.5, 0.5]);
    const layout = UiLinearLayout.Create(prism);
    layout.setLocalPosition([-0.1, 0.1, 0]);
    const text = UiText.CreateEclipseLabel(
      prism,
      '',
      EclipseLabelType.kB2
    );
    text.setAlignment(Alignment.CENTER_CENTER);
    text.setTextAlignment(HorizontalTextAlignment.kCenter);
    const button = UiButton.Create(prism, 'REQUEST', 0, 0.1);
    button.onActivateSub(function (uiEventData) {
        const xhr = new XMLHttpRequest();
        xhr.addEventListener("load", function(event)
        {
             text.setText(xhr.responseText);   
        });
        xhr.open("GET", "https://magicmodelers.net/");
        xhr.send();
    });

    layout.addItem(text, [0, 0, 0.03, 0]
      , Alignment.BOTTOM_CENTER);
    layout.addItem(button, [0, 0, 0.03, 0]
      , Alignment.BOTTOM_CENTER);
    prism.getRootNode().addChild(layout);   
  }
}

Change .eslintrc.js

// ESLint config for MagicScript Apps
module.exports = {
    env: {
        "es6": true
    },
    globals: {
        // These globals are provided by the vm itself.
        "print": true,
        "globalThis": true,
        // The following globals are provided by `magic-script-polyfills`
        "setTimeout": true,
        "clearTimeout": true,
        "setInterval": true,
        "clearInterval": true,
        "setImmediate": true,
        "clearImmediate": true,
        "fetch": true,
        "Headers": true,
        "Request": true,
        "Response": true,
        "XMLHttpRequest":true ,
    },
    parserOptions: {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    extends: "semistandard",
};

Build

magic-script build -i

Run

magic-script run --port=10000

Reference
xhr.js (magic-script-polyfills at Github)
https://github.com/magic-script/magic-script-polyfills/blob/master/src/xhr.js

magicscript
https://www.magicscript.org/

Thanks!

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

【視聴メモ】Unlocking New Capabilities for the Web (Google I/O ’19)

この記事は Google I/O '19 のセッションの視聴メモです。
想定読者は自分なので正確性や網羅率には問題があるかもしれません。


References

https://www.youtube.com/watch?v=GSiUzuB-PoI

Speaker(s): Thomas Steiner and Pete LePage

Abstract

To close the capability gap between the web and native to provide a solid foundation for modern applications delivered on the web, the web needs open. Learn how some of these new capabilities work, what's on our road map, and how we're designing them to work in a way that respects user.

Webとネイティブの間の機能に関するギャップを埋めるためには、またWebを通じて届けられるモダンなアプリケーションに確固たる下地を提供するには、Webはオープンでなければなりません。このセッションでは、いくつかの新しい機能がどう動くかを説明し、そのロードマップに何があるか、またユーザーにそれらを意味のある形で届けるために私達がどういった取り組みをしているかをご紹介します。

Contents

  • Project Fugu
    • Webが様々な機能を得ていくときにはユーザーのプライバシー・信用・セキュリティといったものを守らなければいけない
    • Project Fugu はWebの新機能に対してそういった面から啓蒙・監視・対処等を行うプロジェクト
    • 75の機能がProject Fuguの対象になっている
  • Webの機能を追加したければどうすれば良いのか
    • MicrosoftのApp Icon ShortcutやIntelのWeb NFCなどが例として挙げられていた
    • 1. ニーズを見つける。ユースケースをはっきりさせる。
    • 2. 問題を定義するためのDesign Docを書く
    • 3. Feedbackを受ける
    • 4. Specとして体裁をまとめる
      • Specを見た人がその機能を使いたいと思うことで実装が優先度付されるので
    • 5. 実装される
  • BLE: Bluetooth Low Energy
    • LEGOがWebからアプリなしにおもちゃを操作できるコントローラーを作ってる
  • Web Share API
    • 自身のページをOSのシェア機能でシェアできる
    • ファイルもシェアできる
  • Web Share Target API
    • モバイルに追加されたPWAなどのアプリケーションでOSのその他のコンテンツを自身でシェアできるようにする(ようは Web Share API の逆)
    • Web app manifestに share_target を追加し、シェア機能が使われたときのエンドポイントなどを指定する
    • ファイルを受け取れるようにする Web Share Target API v2 は現在開発中でChrome 76以降で実装予定
  • Media Session API
    • OSに付属のメディアコントロールに表示する情報を操作したり、メディア操作のボタンの挙動を変えたりできる
    • 下図の通り、いろいろ設定できる
    • PCに付属している再生・停止のボタンなどの挙動を変えられる

image.png

  • Shape Detection API
    • バーコード認識 & 顔認識
    • Perception toolkit
      • カメラセッションの管理
      • バーコード認識(ネイティブのものが使えればそれを使い、使えなければWebAssemblyのものを使う)
      • 物体を実際の商品などに紐付ける機能
      • 商品を表示するカードなどのUIツールキット
  • Badging
    • アイコンに表示するバッジの内容を変更できる
    • 次のようにとても簡単にできる
    • Chrome 78 あたりでリリース予定
window.Badge.set(42); // set number
window.Badge.set(); // simple dot
window.Badge.clear();
  • Wake Lock
    • スクリーンをオフにしないようにするAPI
    • スクリーンのみのオンオフを制御するAPIと、システム自体のオンオフを制御するAPIの2つに別れている
    • オンオフの状態監視を行ったり、Wake Lockのキャンセルリクエストを送れたりする
  • File System
    • フォトライブラリとかメディアライブラリとかでユースケースがある
    • vscodeがブラウザで開けてローカルのファイルを編集できたらすごい
  • Serial API
    • シリアル通信ができる
    • Arduinoと通信したりできる
  • WebHID
    • キーボードやマウス以外のデバイスでWebを操作するためのAPI
    • ゲームコントローラーとか
    • HMDとか
    • Chrome 78 以降でリリース予定
  • 連絡先情報
    • Contacts Picker というUIとして利用可能になる
    • 選択したものだけがページに渡されるのでセキュア
  • Font Access API
    • ローカルフォントを使えるようになる
  • Clipboard Access
  • SMS認証
  • Notification Triggers API
    • ある時間に確実に通知を送るAPI
    • 現状のPush APIはその時間に確実にというのができない

すでに存在しているWeb API

image.png

2019年中に登場するAPI

image.png

リリース予定

image.png

  • Web Capabilities codelabs:
    • codelabs.developers.google.com/codelabs/web-capabilities
    • Badge, Shape Detection API, Web Share Target API, Wake Lock API が試せるサイト
  • Capabilities landing page:
    • developers.google.com/web/updates/capabilities

Memo

  • バーコード認証については下記のebayの記事の通り、このAPIが使えれば解決する苦労も多いのでとても嬉しい
  • Perception Toolkit の物体認識は結局どうやって商品情報などと紐付けるんだろう
  • Webでできることが本当に多くなってきていて、だからこそRailsなどのバックエンドがフロントの責務までカバーしている(していた)フレームワークが厳しい感じになってきているのだと改めて実感できた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【jQuery】クリックした要素を取得して属性値を画面に表示

クリックした要素を取得して属性値を画面に表示

画面1.png
「one」「two」「three」の3つボタンがあって。

画面2.png
クリックしたボタンの属性「data-id」に設定した値をテキストで表示する。

画面3.png
他のボタンを押したらテキストが変わる。

コード

index.html
<!doctype html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <title>test</title>
</head>
<body>
    <div>
        <button class='button' data-id='one'>ONE</button>
        <button class='button' data-id='two'>TWO</button>
        <button class='button' data-id='three'>THREE</button>
    </div>

    <p id='selected'>選択してください</p>

    <script type="text/javascript">
        $('.button').on('click', function(){
            var click =  $(this).data('id');

            $('#selected').text(click);
        });
    </script>
</body>
</html>

手順

1.取得したい要素に属性「data-id」を設定

index.html
<button class='button' data-id='one'>ONE</button>

属性「data-id」を記述することで、jQueryでは.data('id')で取得できる。
その他にも「data-name」「data-value」も使える。
今回は、data-id='one'とすることでテキストに「one」を表示する。

2.ボタンクリックで処理を実行

practice.js
$('.button').on('click', function(){
    // ここに処理を記述
});

3.$(this)でクリックされた要素を取得

practice.js
var click =  $(this).data('id');

$(this).data('id')でクリックされたdata-idの値を$clickに入れる。

4.text()で表示内容を変更

practice.js
$('#selected').text(click);

<p id='selected'>選択してください</p>
上記の要素を書き換えるため、$('#selected')で要素を取得。
text()を使い、$clickの値で書き換える。

参考

jQueryのdata()で属性を取得・設定・変更する方法まとめ!
https://www.sejuku.net/blog/38263

jQueryのtext()によるテキスト操作まとめ!
https://www.sejuku.net/blog/40700

【初心者向け】jQueryとは?jQueryの基本的な使い方まとめ
https://handywebdesign.net/2017/09/jquery-for-beginner/

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

会計freeeの取引データから、資金繰り表作成のために決済済み取引を取得する

この投稿の目的

  • 資金繰り表を自動作成できたらいいなぁと前から思っていたので作ってみた。
  • 表にしたり、資金繰り表項目に寄せたりするのはコーディングとは別の話なので割愛します。

方法

  • 会計freeeの取引データ(※)一覧のうち、決済済みのものを抽出してスプレッドシートで加工。
  • freeeでは銀行口座や現金以外にクレカデータも「口座」として抽出されるので、データ抽出後の集計加工時は留意が必要。
  • 取引の一覧を抽出しただけでは、勘定科目・取引先・部門・品目はすべてIDで返ってきます。別途、取引先等の一覧を抽出してきて、IDと名称の紐付けをしておく必要があります。

(※)これを作るためにも、資金収支に絡む項目は、そもそものfreeeでの仕訳入力時にできるだけ振替伝票を使わないことが重要。
振替伝票で資金口座科目を使っているものを抽出すればできなくもないが、取引でフラグつけて抽出したほうが遥かに楽です。
日常の記帳において、振替伝票をできるだけ使わない(決算整理とかだけ)運用にするのがおすすめです。

コード

パラメータ設定

//取得対象会社の事業所ID
var COMPANY_ID = 99999999; 

//スプレッドシート
var ss = SpreadsheetApp.getActiveSpreadsheet();

//クエリストリングで指定するパラメータ
var STATUS = 'settled'  //決済ステータス(決済済み)
var START_ISSUE_DATE = "2018-04-01"; //取得する取引の開始日
var LIMIT = 100; //ページあたりの取引件数
var ACCRUALS = 'with'; //債権債務行の取得有無(取得)
var offsets = Array.apply(null, Array(300)).map(function (_, i) {return i;}); //offset。何ページ抽出したらいいかわからないので、とりあえず300暗い取得。Arrayの中のカッコを変えると操作可能

offsetのとり方が初心者過ぎてわからない。少なくとも書き方としては、claspとかでES6でかくともっと簡単っぽい。

//ページネーション指定をclaspでES6でやるとき
const offset = [...Array(300).keys()];

取引データの取得

スプレッドシートに転記(メインの処理)

本当はmapとかfilterでもっと簡単にできるっぽいけど、ひとまず動くものを作ってみた

function getAllDeals() {
  //dealsの取得
  var dealset = offsets.map(function (offset) {
    return getDeals(offset * 100);
  });

  //データ格納する箱の作成。1行目にタイトル付与
  var rowDataList = [[
    "取引ID",
    "発生日",
    "収支区分",
    "取引先ID",
    "科目ID",
    "品目ID",
    "部門ID",
    "決済日",
    "口座種別",
    "口座ID",
    "金額"
  ]];

  //取引データをrowDataListに格納。取引明細があるのでforEachネストで取得
  dealset.forEach(function (deals) {
    deals.forEach(function (deal) {
      for (var i = 0; i < deal.details.length; i++) {
        rowDataList.push([
          deal.id,
          deal.issue_date,
          deal.type,
          deal.partner_id,
          deal.details[i].account_item_id,
          deal.details[i].item_id,
          deal.details[i].section_id,
          deal.payments[0].date,
          deal.payments[0].from_walletable_type,
          deal.payments[0].from_walletable_id,
          deal.details[i].amount
        ]);
      }
    });
  });

  var sheet = ss.getSheetByName("取引取得");
  sheet.clear();
  sheet.getRange(1, 1, rowDataList.length, rowDataList[0].length).setValues(rowDataList);
}

APIから取引データを持ってきているところの処理

function getDeals(offset) {
  //トークン取得
  var service = getService().getAccessToken();

  //URL指定
  var url = 'https://api.freee.co.jp/api/1/deals?';
  url += "company_id=" + COMPANY_ID;
  url += "&status=" + status;
  url += "&start_issue_date=" + start_issue_date;
  url += "&offset=" + offset;
  url += "&limit=" + limit;
  url += "&accruals=" + accruals;

  //オプション
  var options = {
    "method": "get",
    "headers": {
      "Authorization": "Bearer " + accessToken
    }
  };

  //データ取得と格納
  var res = UrlFetchApp.fetch(url, options).getContentText();
  res = JSON.parse(res).deals;
  return res;
}

実行結果(getAllDeals);

取引はかなり多量になるので、かなり実行時間かかります(コードがだめなせいもあると思いますが。。。)
とりあえず、決済済み(資金収支のあった)の取引明細を取ってくることには成功。これだけだと取引先名称などがわからないので、別途取引先getするなどが必要。また、各口座の期末残高も別途取得必要です。
image.png

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

会計freeeから、資金繰り表作成のために決済済み取引データを取得する

この投稿の目的

  • 資金繰り表を自動作成できたらいいなぁと前から思っていたので作ってみた。
  • 表にしたり、資金繰り表項目に寄せたりするのはコーディングとは別の話なので割愛します。

方法

  • 会計freeeの取引データ(※)一覧のうち、決済済みのものを抽出してスプレッドシートで加工。
  • freeeでは銀行口座や現金以外にクレカデータも「口座」として抽出されるので、データ抽出後の集計加工時は留意が必要。
  • 取引の一覧を抽出しただけでは、勘定科目・取引先・部門・品目はすべてIDで返ってきます。別途、取引先等の一覧を抽出してきて、IDと名称の紐付けをしておく必要があります。

(※)これを作るためにも、資金収支に絡む項目は、そもそものfreeeでの仕訳入力時にできるだけ振替伝票を使わないことが重要。
振替伝票で資金口座科目を使っているものを抽出すればできなくもないが、取引でフラグつけて抽出したほうが遥かに楽です。
日常の記帳において、振替伝票をできるだけ使わない(決算整理とかだけ)運用にするのがおすすめです。

コード

パラメータ設定

//取得対象会社の事業所ID
var COMPANY_ID = 99999999; 

//スプレッドシート
var ss = SpreadsheetApp.getActiveSpreadsheet();

//クエリストリングで指定するパラメータ
var STATUS = 'settled'  //決済ステータス(決済済み)
var START_ISSUE_DATE = "2018-04-01"; //取得する取引の開始日
var LIMIT = 100; //ページあたりの取引件数
var ACCRUALS = 'with'; //債権債務行の取得有無(取得)
var offsets = Array.apply(null, Array(300)).map(function (_, i) {return i;}); //offset。何ページ抽出したらいいかわからないので、とりあえず300暗い取得。Arrayの中のカッコを変えると操作可能

offsetのとり方が初心者過ぎてわからない。少なくとも書き方としては、claspとかでES6でかくともっと簡単っぽい。

//ページネーション指定をclaspでES6でやるとき
const offset = [...Array(300).keys()];

取引データの取得

スプレッドシートに転記(メインの処理)

本当はmapとかfilterでもっと簡単にできるっぽいけど、ひとまず動くものを作ってみた

function getAllDeals() {
  //dealsの取得
  var dealset = offsets.map(function (offset) {
    return getDeals(offset * 100);
  });

  //データ格納する箱の作成。1行目にタイトル付与
  var rowDataList = [[
    "取引ID",
    "発生日",
    "収支区分",
    "取引先ID",
    "科目ID",
    "品目ID",
    "部門ID",
    "決済日",
    "口座種別",
    "口座ID",
    "金額"
  ]];

  //取引データをrowDataListに格納。取引明細があるのでforEachネストで取得
  dealset.forEach(function (deals) {
    deals.forEach(function (deal) {
      for (var i = 0; i < deal.details.length; i++) {
        rowDataList.push([
          deal.id,
          deal.issue_date,
          deal.type,
          deal.partner_id,
          deal.details[i].account_item_id,
          deal.details[i].item_id,
          deal.details[i].section_id,
          deal.payments[0].date,
          deal.payments[0].from_walletable_type,
          deal.payments[0].from_walletable_id,
          deal.details[i].amount
        ]);
      }
    });
  });

  var sheet = ss.getSheetByName("取引取得");
  sheet.clear();
  sheet.getRange(1, 1, rowDataList.length, rowDataList[0].length).setValues(rowDataList);
}

APIから取引データを持ってきているところの処理

function getDeals(offset) {
  //トークン取得
  var service = getService().getAccessToken();

  //URL指定
  var url = 'https://api.freee.co.jp/api/1/deals?';
  url += "company_id=" + COMPANY_ID;
  url += "&status=" + STATUS;
  url += "&start_issue_date=" + START_ISSUE?DATE;
  url += "&offset=" + offset;
  url += "&limit=" + LIMIT;
  url += "&accruals=" + ACCRUALS;

  //オプション
  var options = {
    "method": "get",
    "headers": {
      "Authorization": "Bearer " + accessToken
    }
  };

  //データ取得と格納
  var res = UrlFetchApp.fetch(url, options).getContentText();
  res = JSON.parse(res).deals;
  return res;
}

実行結果(getAllDeals);

取引はかなり多量になるので、かなり実行時間かかります(コードがだめなせいもあると思いますが。。。)
とりあえず、決済済み(資金収支のあった)の取引明細を取ってくることには成功。これだけだと取引先名称などがわからないので、別途取引先getするなどが必要。また、各口座の期末残高も別途取得必要です。
image.png

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

売掛金の増減分析を自動でやってみる。

この記事の目的

  • 会計監査資料の作成が面倒だったため自動化を行うこと
  • 資料作成コストを削減するための手法としての利用
  • @Kirin8210 さんとQiita記事を書く流れになったため

前提

  • freeeAPIを叩くことで試算表を取得している
  • 情報のアウトプットを2次元配列にしており、スプレッドシートに出力する仕組み
  • OAuth2ライブラリを活用して、AcceesTokenを取得している
code.js
/**
* 売掛金の得意先別二期比較の結果が出力される配列を出力する。
* @author @kabanyasu 
* @return {any} array
*/
function getReceivable() {
    var service = getService();
    var accessToken = service.getAccessToken();
    var baseUrl = "https://api.freee.co.jp/api/1/reports/trial_bs_two_years";
    var companyId = 9999999;//任意の事業所番号を記載
    var fiscalYear = 2019;
    var startMonth = 1;
    var endMonth = 12;
    var postUrl = baseUrl +
        ("?company_id=" + companyId + "&fiscal_year=" + fiscalYear + "&start_month=" + startMonth + "&end_month=" + endMonth+"&breakdown_display_type=partner");
    var options = {
        "method": "get",
        "headers": {
            "Authorization": "Bearer " + accessToken
        }
    };
    var resBs = JSON.parse(UrlFetchApp.fetch(postUrl, options)).trial_bs_two_years.balances;
    //[得意先名, 前期末残高, 当期末残高, 増減額, 増減率]
    var array = resBs[2].partners.map(function (customer){
      var last = customer.last_year_closing_balance;
      var end = customer.closing_balance;
      return [customer.name, last, end, end-last, end/last];
    });
   return array;
}

工夫

  • 出力形式の操作を行うことにより、品目別・部門別等の増減分析を自動化できる
  • アウトプットでのスプレッドシート加工方法によっては、月次推移だったり諸々使える
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.js・Vue.js の基本的な使い方を理解する

概要

Nuxt.js (Vue.js) を使って簡単な画面を実装することを通して、Nuxt.js の基本的な使い方を学びます。
今回実装する画面の仕様は以下の通りです。

  • API から json データを取得する(今回は json を取得する関数で代用)
  • データをテーブルに表示する
  • データの件数も表示する

実際にこんな画面ができあがります。

image.png

準備

必要なソフトウェア

事前に以下のソフトウェアをインストールしておく必要があります。

  • Node.js
  • Yarn
  • 好みのエディタ (VSCode がおすすめ)

Nuxt.js のプロジェクトを作成する

Nuxt.js のプロジェクトは create-nuxt-app を使うとほんの数分で作成できます!

$ yarn create nuxt-app nuxt-json-to-table

いくつか質問されますが、以下のようにします。
詳しい説明は今回の本題から外れるので省略します。

? Project name nuxt-json-to-table
? Project description My super-duper Nuxt.js project
? Use a custom server framework none
? Choose features to install Linter / Formatter, Prettier
? Use a custom UI framework none
? Use a custom test framework none
? Choose rendering mode Universal
? Author name Taro Yamada
? Choose a package manager yarn

これで Nuxt.js のプロジェクトが作成されました!

開発環境を起動する

プロジェクトのディレクトリに移動し、yarn dev コマンドで開発環境を起動します。

$ cd nuxt-json-to-table
$ yarn dev

http://localhost:3000 を開き、以下のような画面が表示されたら起動成功です!

image.png

これで開発の準備が整いました!

実装

早速ですが、以下のような流れで実装していきます!

  • ページを作成する
  • json を取得する関数を用意する
  • ボタンを押すと json を取得してそのまま表示する
  • データをテーブルに表示する
  • 件数を表示する
  • ページを開いたときに json を取得する

各ステップごとに、まず実装(コード)を示してから、その中で使った構文などを解説するというスタイルで進めます。

ページを作成する

まずはテーブルを表示するためのページを作成しましょう。
ページを作成するには、pages ディレクトリに Vue ファイルというものを作成する必要があります。

以下の内容で pages/table.vue を作成します。

pages/table.vue
<template>
  <div>
    <h1>table page</h1>
  </div>
</template>

<script>
export default {}
</script>

保存したら、http://localhost:3000/table を開いてみてください。
以下のような画面が表示されるはずです。

image.png

たったこれだけでページを作成することができました!簡単ですね!

せっかくなので、作成したページへのリンクをトップページに表示してみます。
pages/index.vue を以下のように編集します。

pages/index.vue
 <div class="links">
   <a href="https://nuxtjs.org/" target="_blank" class="button--green"
     >Documentation</a
   >
   <a
     href="https://github.com/nuxt/nuxt.js"
     target="_blank"
     class="button--grey"
     >GitHub</a
   >
+  <n-link to="table" class="button--grey">Table</n-link>
 </div>

保存したら、http://localhost:3000 を開いてみてください。
「Table」のリンクが増えているはずです!

image.png

これでページの作成は完了です!

「Vue ファイル」「pages ディレクトリ」「n-link」という見慣れないものが出てきたと思います。
以下で説明します。

Vue ファイル

pages/table.vue のように、Vue ファイルには関連する HTML・JavaScript・CSS を1つのファイルにまとめて書きます。
(外部から読み込むことも可能)

  • HTML は <template> ブロックに書く
  • JavaScript は <script> ブロックに書く
  • CSS は <style> ブロックに書く

HTML・JavaScript・CSS それぞれでファイルを分けることと比較して、以下のような利点があります。

  • UI コンポーネントごとにコードが分離される
  • 影響範囲を Vue ファイルの中に閉じ込めることができる
  • 保守しやすくなる

個人的にはもう HTML・JavaScript・CSS それぞれでファイルを分けることはしなくないです...笑

ちなみに、Vue ファイルのことを 単一ファイルコンポーネント (Single File Component) と言います。SFC と略されることが多いです。(スーパー○ァミコンではないです)

Nuxt.js におけるルーティング

Nuxt.js では、pages ディレクトリに Vue ファイルを作成すると、そのディレクトリ構造に沿ってページが作成されます。

ルーティング - Nuxt.js
https://ja.nuxtjs.org/guide/routing

別のページへのリンクを作成するときは nuxt-link (n-link) タグを使います。

ちなみに、ルーティングには Vue Router を使っており、Nuxt.js を使わない場合は自分で設定する必要があります。

json を取得する関数を用意する

続いて、json を取得する関数を用意します。

<script> の直下に getJson() 関数を定義します。

table.vue
/**
 * 年齢別の推定ユーザー数を json で返す。件数はランダム
 */
function getJson() {
  const ages = Array.from(
    new Set(
      new Array(parseInt(Math.random() * 100))
        .fill(null)
        .map(() => parseInt(Math.random() * 100))
    )
  )
  return ages.map(age => ({
    age,
    users: Math.random() * 10000
  }))
}

この関数は以下のように年齢とその推定ユーザー数を返します。

[
    {
        "age": 32,
        "users": 66.6471422182191
    },
    {
        "age": 14,
        "users": 6426.058219829258
    },
    ...
]

実装の説明は今回の本題から外れるので省略しますmm

ボタンを押すと json を取得してそのまま表示する

定義した getJson() を使って、json を画面に表示してみましょう。

まずは、「ボタン」と「json を表示する場所」を pages/table.vue<template> に記述します。

pages/table.vue
 <template>
   <div>
     <h1>table page</h1>
+    <button @click="fetch">fetch</button>
+    <p>{{ items }}</p>
   </div>
 </template>

ボタンには @click="fetch" を付与していて、クリックされたら fetch() メソッドを実行するようにしています。

次に、ボタンを押した時の処理を pages/table.vueexport default {} に記述していきます。

pages/table.vue
export default {
  data: () => ({
    items: []
  }),

  methods: {
    fetch() {
      this.items = getJson()
    }
  }
}

保存したら、http://localhost:3000/table を開いてみてください。

image.png

fetch ボタンをクリックすると json が表示されました!

data とか methods とか急に色々でてきましたね。順番に紹介していきます。

data

{{ }} (ムスタッシュ構文)

  • data を DOM にバインディングします。
  • data の更新を検出して自動的に画面を更新してくれます。

methods

  • <template> 内で利用できるメソッドをここに記述します。
  • <template> 内で使う必要がなければ Vue インスタンス外に関数定義しても構わないです。

@click (v-on:click)

  • クリックイベントを登録するために使います。
  • 上記の methods で定義したメソッドも指定できます。
  • @input は input イベント、@change は change イベントなど、基本的な DOM のイベントハンドラをこの構文で登録できます。

データをテーブルに表示する

では、取得した json からテーブルを描画してみます。
pages/table.vue で、json をそのまま表示させていた部分を table に置き換えます。

pages/table.vue
-<p>{{ items }}</p>
+<table border="1">
+  <tr>
+    <th>年齢</th>
+    <th>推定ユーザー数</th>
+  </tr>
+  <tr v-for="item in items" :key="item.age">
+    <td>{{ item.age }}</td>
+    <td>{{ item.users }}</td>
+  </tr>
+</table>

保存したら、http://localhost:3000/table を開いてみてください。

image.png

テーブルが表示されました!

このステップでは、v-for という構文が出てきました。

v-for

件数を表示する

続いて、取得したデータの件数を表示してみましょう。

pages/table.vue<template> に件数を表示する部分を追加します。

pages/table.vue
 <div>
   <h1>table page</h1>
   <button @click="fetch">fetch</button>
+  {{ length }}件 fetch しました
   <table border="1">
     <tr>
       <th>年齢</th>

length が必要なので、<script> 部分で以下のように定義します。

pages/table.vue
   items: []
 }),

+computed: {
+  length() {
+    return this.items.length
+  }
+},

 methods: {
   fetch() {
     this.items = apiRequest()
   }

保存したら、http://localhost:3000/table を開いてみてください。

image.png

件数が表示されました!

このステップでは、件数を求めるために computed という構文を使いました。

computed (算出プロパティ)

ページを開いたときに json を取得する

いよいよ最後です。ページを開いた時に自動的に json を取得するようにしてみます。
(開いた時点でデータが表示されていてほしいですよね?)

pages/table.vue<script> に以下を追加します。

pages/table.vue
   }
 },

+created() {
+  this.fetch()
+},

 methods: {

保存したら、http://localhost:3000/table を開いてみてください。
fetch ボタンを押さずともテーブルにデータが表示されたはずです!

ページを開いたときに何か処理を実行したい場合は created を使います。

created

  • Vue インスタンスが生成された直後に呼び出されます。
  • mountedupdated など、他にも様々なタイミングで発火するライフサイクルフックがあります。
  • ページを開く時に API からデータを取得する場合は asyncData の方が適切なことが多い。

まとめ

ここまでで、最初に説明した仕様をすべて満たすことができました!

この記事では、取得したデータをテーブルに表示するページの実装を通して、Nuxt.js (Vue.js) について以下の内容を学びました。

  • SFC
  • ルーティング
  • data
  • methods
  • computed
  • ムスタッシュ構文
  • @click (イベント)
  • v-for
  • created (ライフサイクルフック)

これらは Nuxt.js・Vue.js のほんの一部にすぎませんが、

  • Nuxt.js・Vue.js のことが少し理解できた!
  • Nuxt.js・Vue.js を使ってみたくなった!

と思っていただけたら嬉しいです!

また、本文中でも Nuxt.js・Vue.js の公式ドキュメントへのリンクを入れていたのですが、Nuxt.js・Vue.js は公式ドキュメントが本当に充実しています!しかも日本語です!
そのおかげで勉強するにあたっての敷居はかなり低いので、少しでも興味を持ったらぜひ読んでみてくださいー!

おまけ

さらに改良してみる

今回実装したページにはまだまだ改善の余地がありますよね...!
改善案と、それを実現するために使う Nuxt.js・Vue.js の機能を紹介しておくので、ぜひチャレンジしてみてください!

  • 小数点以下を四捨五入して、3桁区切りのカンマを表示する
  • fetch 中は「Loading...」と表示する
  • 値が大きいほどセルの背景色を濃くしてみる
  • 表示する数値は推定ユーザー数固定ではなく、フォームから入力して指定できるようにする
  • フォームで指定したものをクエリパラメーターとして保持する
  • fetch ボタンとテーブルのコンポーネントを分離する
  • 四捨五入の filter を共通化する

実装サンプル

今回実装したコードは以下のリポジトリに置いてあります!
「おまけ」についてもすべてて実装済みです。
https://github.com/shun91/nuxt-json-to-table

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

「正」の数でオセロするWebComponentを作った

元ネタはこれです。

https://togetter.com/li/1359065

othello.gif

突貫で作ったので見た目はガバガバだし、「正」の数じゃなくて数字が増えてくだけなんですが、
これ以上頑張る気力が沸かなかったので許してくださいなんでもしますから
オセロのロジックまわり作るのが一番大変でした

以下のURLで遊べるのでよければ遊んでみてください
https://codepen.io/kuwabatak/full/YbLrMa

See the Pen counting-othello by KuwabataK (@kuwabatak) on CodePen.

遊び方

見たまんまオセロです。マス目をクリックするとオセロのルール的に置ける場所なら、自分の色でぬり潰されて、周りのマスの色が反転します。
普通のオセロと違うとこはマス目の中に数字が振ってあって、ひっくり返されるごとに1ずつ増えていくとこです。
偶数が青側、奇数が赤側ですね。
なので、青側がクリックしたとこだけ2増えちゃいます。

ひっくり返された回数がカウントされていくので、攻防の激しいとこがどこかわかって面白いんじゃないかと思って勢いで作ったんですが、一人遊びしてた感じでは、ぶっちゃけまんなかあたりの数字が大きくなるだけであまり面白くなかったです。。。他の人と対戦はしていないので、対戦すればまた変わるのかもしれません。

あと、せっかくなのでひっくり返せる上限値を設定できるようにしました、上限値に達すると、そのマスは変更不能の中立地帯になり、挟んでも色を変えることができなくなります。
また、自分のセルで挟んだ部分の中に中立地帯が含まれているとひっくり返らなくなります。空きマスと同じ判定ですね

あんまり上限値を低くしすぎると、中立地帯がどんどん多くなっていって、置ける場所が少なくなってしまうのですが、うまいこと設定してやると後半は中立地帯にすべきかどうかも考えながらプレイすることになってなかなか面白いんじゃないかと思います。

使い方

WebComponentで作ったので、以下のようなhtmlファイルを作ればそれだけで使えます。

<!DOCTYPE html>
<html>
<head>
  <!-- WebComponent読み込み -->
  <script src="https://kuwabatak.github.io/counting-othello/counting-othello.js"></script>
</head>
<body>
  <!-- custom elementを書く -->
  <ks-othello></ks-othello>
</body>
</html>

上でやったように、Code Penとかでも使えますし、VueやReactに組み込んで、Webアプリっぽくもできると思います。(とかいってWebComponentをVueやReactで使ったことないんですが)

一応、以下みたいな感じで縦横盤面の大きさだけ外から指定できるようにしました。ほんとはもっといろいろ引数設定できるようにしたかったんですが、力尽きましたごめんなさい

<ks-othello x="8" y="8" ></ks-othello>

内部の話

StencilというWebComponentを作れるフレームワークを使って作っています。(というかStencilの勉強のために作った)。ついこの前v1.0になったばっかりの新し目のフレームワークです。雰囲気はReactにかなり近いです。
参考までに、今回作ったオセロのソースの一部を以下に貼り付けておきます。

JSXベースでDom部分を書いてく感じですが、@Prop() @Watch などのデコレータが用意されていて、TypeScriptでVueを書いたときみたいな雰囲気になってるのが、なんとなくわかるんじゃないでしょうか。

公式ドキュメントも大した量じゃないので、ReactやVueを触ったことのある人なら、すぐに書けるようになるんじゃないかと思います。

othello.jsx
import { Component, Prop, h, State, Watch } from '@stencil/core';
import { generateField, calcReverseField } from '../../utils/utils';
import _ from 'lodash'

@Component({
  tag: 'ks-othello',
  styleUrl: 'othello.css',
  shadow: true
})
export class Othello {
  /**
   * xLength of Field
   */
  @Prop() x_length: number = 8

  /**
   * yLength of Field
   */
  @Prop() y_length: number = 8

  @State() field: number[][] = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

  @State() player: 0 | 1 = 0

  @State() maxval: number = 1000

  /**
   * コンポーネントロード時発火する関数
   */
  componentWillLoad() {
    this.field = generateField(this.x_length, this.y_length)
  }

  @Watch('x')
  xWatch() {
    this.field = generateField(this.x_length, this.y_length)
  }

  @Watch('y')
  yWatch() {
    this.field = generateField(this.x_length, this.y_length)
  }

  /**
   * オセロをおいたときの処理
   * 
   * @param e 
   * @param x 
   * @param y 
   */
  private clickSlot(e: Event, x: number, y: number) {
    ...
  }

  /**
   * 各色のコマの数を返す
   * @param color 
   */
  private countCellNum(color: 'blue' | 'red') {
    ...
  }

  /**
 * 各色の値の合計値を返す
 * @param color 
 */
  private countCellSum(color: 'blue' | 'red') {
    ...
  }

  /**
 * inputの中身が変わったときに結果を再計算し、
 * changeResultイベントを発火して呼び出し元に伝える
 * 
 * @param event  
 */
  handleMaxValChange(event: Event) {
    this.maxval = event.target["value"]
  }

  private reset() {
    this.field = generateField(this.x_length, this.y_length)
    this.player = 0
  }

  render() {
    return <div>
      <table>
        {this.field.map((xArr, yIndex) =>
          <tr>
            {xArr.map((num, xIndex) =>
              <th class={[
                'cell',
                num === 0 ? 'unuse' : 'use',
                num % 2 === 0 ? 'blue' : 'red'
              ].join(' ')}
                onClick={(e) => this.clickSlot(e, xIndex, yIndex)} >
                <div >
                  {num}
                </div>
              </th>
            )}
          </tr>
        )}
      </table>
      <div class={['teban',
        this.player === 0 ? 'red' : 'blue'
      ].join(' ')} >
        {this.player === 0
          ? 'レッドの番だよ!!'
          : 'ブルーの番だよ!!'}</div>
      <div>{'レッドのセルの数:' + this.countCellNum('red')}</div>
      <div>{'ブルーのセルの数:' + this.countCellNum('blue')}</div>
      <div>{'レッドのセルの中の合計値:' + this.countCellSum('red')}</div>
      <div>{'ブルーのセルの中の合計値:' + this.countCellSum('blue')}</div>
      <div class="cp_iptxt">
        許されるセルの値の最大値:
      <input type="number" value={this.maxval}
          onChange={(event) => { this.handleMaxValChange(event) }} />
      </div>
      <button class="btn-square-little-rich" onClick={() => this.reset()}>リセット</button>
    </div>
  }
}

汚いソースなのであれですが、興味のある人向けにGitHubのソース をおいておきます。

CSS部分とかは全くわからないので完全にネットに落ちてたコードのコピペです。
CSSの勉強もしないと・・・

終わり

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

Deck.GLのTripLayerでGTFS-JPのバス運行状況を可視化してみた

概要

地域交通の未来と公共交通オープンデータ - HackMDの発表資料「地域交通の未来と公共交通オープンデータ」を見て、GTSF-JPに興味を持ちました。
GTFS-JPの仕様を確認したところ面白そうだったので、GTFS-JPデータを公開している両備バスの運行状況をDeck.GLのTriplayerで可視化してみました。

なお、今回作成したコードはGithubにおいています。
Github:gtfsjp_tmat_test

GTFS-JPの概要

GTFS(General Transit Feed Specification)は公共交通機関の時刻表と地理的情報に関するオープンフォーマットであり、GTFSを日本の「標準的なバス情報フォーマット」として拡張されたものがGTFS-JPです。
国土交通省・公共交通政策ホームページでは、「静的バス情報フォーマット(GTFS-JP)」と「動的バス情報フォーマット(GTFSリアルタイム)」についての仕様が示されています。

静的バス情報フォーマット(GTFS-JP)

GTFS-JPは、バス路線の事業者やバス停、バス路線の情報を最大17個のCSVファイルで整理し、Zip形式で配布されます。 配布データは単なるCSVなので、テキスト処理で諸情報を入手することができます。

  • agency.txt(事業者情報)
  • stops.txt(停留所・標柱情報)
  • routes.txt(経路情報)
  • trips.txt(便情報)
  • office_jp.txt(営業所情報)
  • stop_times.txt(通過時刻情報)
  • calendar.txt(運行区分情報)
  • fareattributes.txt(運賃情報)
  • farerules.txt(運賃定義情報)
  • feed_info.txt(提供者情報)
  • agency_jp.txt(事業者追加情報)
  • routes_jp.txt(経路追加情報)
  • calendar_dates.txt(運行日情報)
  • shapes.txt(描写情報)
  • frequencies.txt(運行間隔情報)
  • transfers.txt(乗換情報)
  • translations.txt(翻訳者情報)

動的バス情報フォーマット(GTFSリアルタイム)

GTFSリアルタイムでは、遅延等のルート最新情報や⾞両位置情報、運⾏情報等のリアルタイム除法をProtocol Bufferというデータ構造が規定されたバイナリデータ(*.bin)で配布されます。 なお、GTFSリアルタイムについては、バイナリデータ読み取り用クラスを生成する「gtfs-realtime-bindings」がMavenRepositiryに登録されています。

GTFS-JPからDB(Sqlite3)を生成する

仕様書に目を通したところで、Bus-Visionから、GTFS-JPを配布している両備バスのデータをSqliteに導入しています。
処理の概要は以下のとおりです。なお、ORマッパーはomliteを使用しています。

    private static Pattern csvDiv=Pattern.compile("\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"|([^,]+)|,|");

    /** GTFS-JPファイル(ZIPファイル)からCSVデータを取得してテーブルを作成*/
    public void buildGtfsDB(InputStream is) throws IOException,ParseException{
        ZipInputStream zis=new ZipInputStream(is);
        ZipEntry ze;
        byte[] buf= new byte[1024];
        StringBuffer sb=new StringBuffer();
        while ((ze=zis.getNextEntry()) != null) {
            int size = 0;
            while((size=zis.read(buf))>0){
                String str=new String(buf,0,size);
                sb.append(str);
            }
            zis.closeEntry();
            BufferedReader br=new BufferedReader(new StringReader(sb.toString()));
            String[][] csv=parseCsv(br);
            String name=ze.getName().replace(".txt", "").toLowerCase();
            List<Map<String,Object>> list=json2Csv(csv);
            try{
                create(name,list);
            }catch(SQLException e){
                e.printStackTrace();
            }
            sb.delete(0, sb.length());
        }
    }

    private void create(String name,List<Map<String,Object>> list)throws SQLException{
        if(name.equals("agency")){
            create(this.agencyDao,list);
        }else if(name.equals("agency_jp")){
            create(this.agencyJpDao,list);
        }else if(name.equals("calendar")){
            create(this.calDao,list);
        }else if(name.equals("calendar_dates")){
            create(this.calDateDao,list);
        }else if(name.equals("fare_attributes")){
            create(this.fareAttrDao,list);
        }else if(name.equals("fare_rules")){
            create(this.fareRuleDao,list);
        }else if(name.equals("feed_info")){
            create(this.feedDao,list);
        }else if(name.equals("frequencies")){
            create(this.freqDao,list);
        }else if(name.equals("office_jp")){
            create(this.officeJpDao,list);
        }else if(name.equals("routes_jp")){
            create(this.routesJpDao,list);
        }else if(name.equals("routes")){
            create(this.routesDao,list);
        }else if(name.equals("shapes")){
            create(this.shapeDao,list);
        }else if(name.equals("stop_times")){
            create(this.stopTimeDao,list);
        }else if(name.equals("stops")){
            create(this.stopsDao,list);
        }else if(name.equals("transfers")){
            create(this.transfersDao,list);
        }else if(name.equals("translations")){
            create(this.translationslDao,list);
        }else if(name.equals("trips")){
            create(this.tripsDao,list);
        }
    }

    /** setter/getterを使うのはめんどいので、一度JSONを生成し、
        JSONからオブジェクトを生成してDBにぶっこんでます*/
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void create(Dao dao,List<Map<String,Object>> list)throws SQLException{
        final List array=new ArrayList();
        for(Map<String,Object> map : list){
            String json=gson.toJson(map);
            array.add(gson.fromJson(json,dao.getDataClass()));
        }
        TransactionManager.callInTransaction(dao.getConnectionSource(), new Callable<Void>(){
            public Void call() throws Exception {
                for(Object fp : array){
                    dao.create(fp);
                }
                return null;
            }
        });
    }

    /** CSVファイルをパース*/
    private static String[][] parseCsv(BufferedReader reader)throws IOException{
        ArrayList<String[]> list=new ArrayList<String[]>();
        String line;
        while((line=reader.readLine())!=null){
            String[] sp=split(csvDiv,line);
            list.add(sp);
        }
        if(list.size()==0)return new String[0][0];
        return list.toArray(new String[list.size()][]);
    }

    private static String[] split(Pattern pattern,String line){
        Matcher matcher=pattern.matcher(line);
        List<String> list=new ArrayList<String>();
        int index=0;
        int com=0;
        while(index<line.length()){
            if(matcher.find(index+com)){
                String s=matcher.group().replaceAll("\"", "");
                index=matcher.end();
                list.add(s);
                com=1;
            }
        }
        return list.toArray(new String[list.size()]);
    }

DBからTripLayer用データ(JSON)を生成する

次いで、Tripsからtrip_idを、Stop_timesからそのTripの停留所と発車時刻、Stopsから停留所の位置情報を取得して、TripLayer用データ(Json)を生成しています。
処理の概要は以下のとおりです。
なお、Tripデータは[経度、緯度、タイムスタンプ]の形で整理しており、タイムスタンプは32bit浮動小数点にする必要があるので、一度、Javaのシリアル値を生成した後、午前0時からの経過時間(分)に変換しています。また、今回は停留所のリストをバスの通行経路として扱いました。

    /** DBからTripLayer用データを生成 */
    public String getTripData() throws SQLException,ParseException{
        Map<String,Object> map=new HashMap<String,Object>();
        List<GTrip> trips=getTrips();
        String ymd=getSurviceDate(trips.get(0));
        List<Map<String,List<Number[]>>> waypoints=new ArrayList<Map<String,List<Number[]>>>();
        long st=dateFormat.parse(ymd+" 00:00:00").getTime();
        for(GTrip t : trips){
            List<Number[]> wl=getWaypoints(t,st);
            Map<String,List<Number[]>> mm=new HashMap<String,List<Number[]>>();
            mm.put("segments", wl);
            waypoints.add(mm);
        }
        map.put("starttime", st);
        map.put("trips",waypoints);
        map.put("stops", stop2Map(getStops()));
        return gson.toJson(map);
    }

    private List<GStop> getStops()throws SQLException{
        return this.stopsDao.queryForAll();
    }

    private List<GTrip> getTrips()throws SQLException{
        return this.tripsDao.queryForAll();
    }

    /** Trip単位で通過経路(経度、緯度、時刻)のデータを生成 */
    private List<Number[]> getWaypoints(GTrip t,long sub)throws SQLException,ParseException{
        String ymd=getSurviceDate(t);
        QueryBuilder<GStopTime,Long> query=stopTimeDao.queryBuilder();
        query.where().eq("trip_id", t.trip_id);
        List<GStopTime> list=stopTimeDao.query(query.prepare());
        list.sort(new Comparator<GStopTime>(){
            @Override
            public int compare(GStopTime arg0, GStopTime arg1) {
                int v0=Integer.parseInt(arg0.stop_sequence);
                int v1=Integer.parseInt(arg1.stop_sequence);
                return (v0-v1);
            }
        });
        int n=list.size();
        List<Number[]> data=new ArrayList<Number[]>();
        for(int i=0;i<n;i++){
            GStopTime st=list.get(i);
            data.add(createWaypoint(st,ymd,sub));
        }
        return data;
    }

    private Number[] createWaypoint(GStopTime st,String ymd,long sub)throws ParseException,SQLException{
        long timestamp=getStopTimes(st,ymd);
        GStop sa=getStops(st.stop_id);
        return new Number[]{sa.stop_lon,sa.stop_lat,((double)(timestamp-sub))/(60.0*1000.0)};
    }

    private String getSurviceDate(GTrip t)throws SQLException{
        QueryBuilder<GCalendar,Long> query=calDao.queryBuilder();
        query.where().eq("service_id", t.service_id);
        List<GCalendar> list=calDao.query(query.prepare());
        String date=list.get(0).start_date;
        return date.substring(0, 4)+"-"+date.substring(4,6)+"-"+date.substring(6,8);
    }

    private GStop getStops(String stop_id)throws SQLException{
        QueryBuilder<GStop,Long> query=stopsDao.queryBuilder();
        query.where().eq("stop_id", stop_id);
        List<GStop> list=stopsDao.query(query.prepare());
        return list.get(0);
    }

    private long getStopTimes(GStopTime s,String ymd)throws ParseException{
        return dateFormat.parse(ymd+" "+s.arrival_time).getTime();
    }

Deck.GLでTripLayer可視化

作成したJSONデータはTripLayerに合わせた形にしているので、そのまま読み込ましてTripLayerを作成しています。
今回は停留所のリストをそのまま通行経路としたため、直行便はほぼ一直線に目的地へ移動してしまいます。バス運行経路を正確に再現し、道路に沿って動くようにするにはShapes.txtのデータをトレースして[経度、緯度、タイプスタンプ]のデータのリストを生成する必要があります。

<!doctype html>
<html class="no-js" lang="ja">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>Traffic-Bus</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.css" />
        <script src="https://code.jquery.com/jquery-3.4.0.js" integrity="sha256-DYZMCC8HTC+QDr5QNaIcfR7VSPtcISykd+6eSmBW5qo=" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/deck.gl@7.0.0/dist.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-scale/1.0.7/d3-scale.js"></script>
        <style type="text/css">
            html, body {
                padding: 0;
                margin: 0;
                width: 100%;
                height: 100%;
            }
            #panel {
                position: absolute;
                background: #ffffffaa;
                top: 0;
                left: 0;
                margin: 4px;
                padding: 4px;
                line-height: 1;
                width:260px;
                height:26px;
                z-index: 2;
                text-align: center;
                vertical-align: middle;
            }
            #tooltip {
                font-family: Helvetica, Arial, sans-serif;
                font-size: 12px;
                position: absolute;
                padding: 4px;
                margin: 8px;
                background: rgba(0, 0, 0, 0.8);
                color: #fff;
                max-width: 300px;
                z-index: 9;
                pointer-events: none;
            }
        </style>
    </head>
    <body>
        <div id="panel"></div>
        <div id="app" style="width:100%;height:100%;"></div>
        <div id="tooltip"></div>
    </body>
    <script type="text/javascript">
        const LAT=34.6;
        const LNG=135.5;
        let c_lon=0;
        let c_lat=0;
        let c_time=300;
        let triplayers=[];
        const deckgl = new deck.DeckGL({
            container: 'app',
            mapboxApiAccessToken: "アクセストークン",
            mapStyle: "mapbox://styles/mapbox/dark-v9",
            longitude: LNG,
            latitude: LAT,
            zoom: 11,
            pitch: 40,
            bearing: -10,
            onViewStateChange: ({viewState}) => {
                return viewState;
            }
        });
        const timeVal = () =>{
            return c_time;
        };

        const loadData = () => {
            d3.json("trips.json", (error, response)=>{
                setCenter(response.stops);
                let v_time=response.starttime;
                let trip=renderTripLayer(response.trips);
                let stop=renderStopLayer(response.stops);
                deckgl.setProps({
                    layers: [trip,stop],
                    viewState: {
                        longitude: c_lon,
                        latitude: c_lat,
                        zoom: 11,
                        transitionInterpolator: new FlyToInterpolator(),
                        transitionDuration: 3000
                    }
                });
                const update = () =>{
                    c_time +=0.1;
                    if(c_time>1439){
                        window.cancelAnimationFrame(thread);
                        return; 
                    }else{
                        trip.setState({time: c_time});
                    let da=new Date(v_time+c_time*60*1000);
                    $("#panel").text(da);
                    window.requestAnimationFrame(update);
                    }
                };
                let thread=window.requestAnimationFrame(update);
            });
        };
        const renderStopLayer = (data) => {
            const pointlayer = new deck.ScatterplotLayer({
                id: 'stop',
                data,
                fp64: false,
                opacity: 0.8,
                stroked: true,
                filled: true,
                radiusScale: 6,
                radiusMinPixels: 1,
                radiusMaxPixels: 100,
                lineWidthMinPixels: 1,
                getPosition: d => d.coordinates,
                getRadius: 8,
                getFillColor: [255, 140, 0],
                getLineColor: [0, 0, 0],
                pickable: true,
                onHover: updateTooltipStop
            });
            return pointlayer;
        };

        const renderTripLayer =(trips) => {
            const tripLayer = new TripsLayer({
                id: 'trips-layer',
                data: trips,
                getPath: d => d.segments,
                getColor: [253, 128, 93],
                opacity: 0.6,
                widthMinPixels: 3,
                rounded: true,
                trailLength: 15,
                currentTime: timeVal 
            });
            return tripLayer;
        }

        const setCenter=(feature) =>{
            const n=feature.length;
             for(let i=0;i<n;i++){
                c_lon +=feature[i].coordinates[0];
                c_lat +=feature[i].coordinates[1];
            }
            c_lon=c_lon/n;
            c_lat=c_lat/n;
        };

        const updateTooltipStop=({x, y, object}) => {
            const tooltip = document.getElementById("tooltip");
            if (object) {
                tooltip.style.visibility="visible";
                tooltip.style.top = y+"px";
                tooltip.style.left = x+"px";
                tooltip.innerHTML = "<p>"+object.name+"</p>";
            } else { 
                tooltip.style.visibility="hidden";
                tooltip.innerHTML = "";
            }
        };
        loadData();
    </script>
</html>

最後に

とりあえず、GTFS-JPからDBを作成し、Tripデータを作成して可視化するとこまで処理してみました。
GTFS-JPはCSVデータなので扱いが楽です。GTFSリアルタイムも「gtfs-realtime-bindings」を使用すると簡単にデータを処理できそうな感じです。また、両備バスの場合で、DB容量は約14MB(Sqlite)だったので、Android端末等に導入することもできそうだと思いました。
GTFS-JPの本格的な普及はこれからだと思いますが、少子高齢化や過疎化により地方の公共交通機関は色々な課題を抱えているので、かじっておくと何かしら地域の役に立つかも?と思いました。

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

Deck.GLのTripLayerでGTFS-JPのバス運行状況を可視化してみる

概要

地域交通の未来と公共交通オープンデータ - HackMDの発表資料「地域交通の未来と公共交通オープンデータ」を見て、GTSF-JPに興味を持ちました。
GTFS-JPの仕様を確認したところ面白そうだったので、GTFS-JPデータを公開している両備バスの運行状況をDeck.GLのTriplayerで可視化してみました。

なお、今回作成したコードはGithubにおいています。
Github:gtfsjp_tmat_test

GTFS-JPの概要

GTFS(General Transit Feed Specification)は公共交通機関の時刻表と地理的情報に関するオープンフォーマットであり、GTFSを日本の「標準的なバス情報フォーマット」として拡張されたものがGTFS-JPです。
国土交通省・公共交通政策ホームページでは、「静的バス情報フォーマット(GTFS-JP)」と「動的バス情報フォーマット(GTFSリアルタイム)」についての仕様が示されています。
- GTFS-JP仕様書
- GTFS-JPサイト
- GTFS-Githubプロジェクト

静的バス情報フォーマット(GTFS-JP)

GTFS-JPは、バス路線の事業者やバス停、バス路線の情報を最大17個のCSVファイルで整理し、Zip形式で配布されます。 配布データは単なるCSVなので、テキスト処理で諸情報を入手することができます。
- agency.txt(事業者情報)
- stops.txt(停留所・標柱情報)
- routes.txt(経路情報)
- trips.txt(便情報)
- office_jp.txt(営業所情報)
- stop_times.txt(通過時刻情報)
- calendar.txt(運行区分情報)
- fareattributes.txt(運賃情報)
- farerules.txt(運賃定義情報)
- feed_info.txt(提供者情報)
- agency_jp.txt(事業者追加情報)
- routes_jp.txt(経路追加情報)
- calendar_dates.txt(運行日情報)
- shapes.txt(描写情報)
- frequencies.txt(運行間隔情報)
- transfers.txt(乗換情報)
- translations.txt(翻訳者情報)

動的バス情報フォーマット(GTFSリアルタイム)

GTFSリアルタイムでは、遅延等のルート最新情報や⾞両位置情報、運⾏情報等のリアルタイム除法をProtocol Bufferというデータ構造が規定されたバイナリデータ(*.bin)で配布されます。 なお、GTFSリアルタイムについては、バイナリデータ読み取り用クラスを生成する「gtfs-realtime-bindings」がMavenRepositiryに登録されています。
- gtfs-realtime-bindings

GTFS-JPからDB(Sqlite3)を生成する

仕様書に目を通したところで、Bus-Visionから、GTFS-JPを配布している両備バスのデータをSqliteに導入しています。
処理の概要は以下のとおりです。なお、ORマッパーはomliteを使用しています。

    private static Pattern csvDiv=Pattern.compile("\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"|([^,]+)|,|");

    /** GTFS-JPファイル(ZIPファイル)からCSVデータを取得してテーブルを作成*/
    public void buildGtfsDB(InputStream is) throws IOException,ParseException{
        ZipInputStream zis=new ZipInputStream(is);
        ZipEntry ze;
        byte[] buf= new byte[1024];
        StringBuffer sb=new StringBuffer();
        while ((ze=zis.getNextEntry()) != null) {
            int size = 0;
            while((size=zis.read(buf))>0){
                String str=new String(buf,0,size);
                sb.append(str);
            }
            zis.closeEntry();
            BufferedReader br=new BufferedReader(new StringReader(sb.toString()));
            String[][] csv=parseCsv(br);
            String name=ze.getName().replace(".txt", "").toLowerCase();
            List<Map<String,Object>> list=json2Csv(csv);
            try{
                create(name,list);
            }catch(SQLException e){
                e.printStackTrace();
            }
            sb.delete(0, sb.length());
        }
    }

    private void create(String name,List<Map<String,Object>> list)throws SQLException{
        if(name.equals("agency")){
            create(this.agencyDao,list);
        }else if(name.equals("agency_jp")){
            create(this.agencyJpDao,list);
        }else if(name.equals("calendar")){
            create(this.calDao,list);
        }else if(name.equals("calendar_dates")){
            create(this.calDateDao,list);
        }else if(name.equals("fare_attributes")){
            create(this.fareAttrDao,list);
        }else if(name.equals("fare_rules")){
            create(this.fareRuleDao,list);
        }else if(name.equals("feed_info")){
            create(this.feedDao,list);
        }else if(name.equals("frequencies")){
            create(this.freqDao,list);
        }else if(name.equals("office_jp")){
            create(this.officeJpDao,list);
        }else if(name.equals("routes_jp")){
            create(this.routesJpDao,list);
        }else if(name.equals("routes")){
            create(this.routesDao,list);
        }else if(name.equals("shapes")){
            create(this.shapeDao,list);
        }else if(name.equals("stop_times")){
            create(this.stopTimeDao,list);
        }else if(name.equals("stops")){
            create(this.stopsDao,list);
        }else if(name.equals("transfers")){
            create(this.transfersDao,list);
        }else if(name.equals("translations")){
            create(this.translationslDao,list);
        }else if(name.equals("trips")){
            create(this.tripsDao,list);
        }
    }

    /** setter/getterを使うのはめんどいので、一度JSONを生成し、
        JSONからオブジェクトを生成してDBにぶっこんでます*/
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void create(Dao dao,List<Map<String,Object>> list)throws SQLException{
        final List array=new ArrayList();
        for(Map<String,Object> map : list){
            String json=gson.toJson(map);
            array.add(gson.fromJson(json,dao.getDataClass()));
        }
        TransactionManager.callInTransaction(dao.getConnectionSource(), new Callable<Void>(){
            public Void call() throws Exception {
                for(Object fp : array){
                    dao.create(fp);
                }
                return null;
            }
        });
    }

    /** CSVファイルをパース*/
    private static String[][] parseCsv(BufferedReader reader)throws IOException{
        ArrayList<String[]> list=new ArrayList<String[]>();
        String line;
        while((line=reader.readLine())!=null){
            String[] sp=split(csvDiv,line);
            list.add(sp);
        }
        if(list.size()==0)return new String[0][0];
        return list.toArray(new String[list.size()][]);
    }

    private static String[] split(Pattern pattern,String line){
        Matcher matcher=pattern.matcher(line);
        List<String> list=new ArrayList<String>();
        int index=0;
        int com=0;
        while(index<line.length()){
            if(matcher.find(index+com)){
                String s=matcher.group().replaceAll("\"", "");
                index=matcher.end();
                list.add(s);
                com=1;
            }
        }
        return list.toArray(new String[list.size()]);
    }

DBからTripLayer用データ(JSON)を生成する

次いで、Tripsからtrip_idを、Stop_timesからそのTripの停留所と発車時刻、Stopsから停留所の位置情報を取得して、TripLayer用データ(Json)を生成しています。
処理の概要は以下のとおりです。
なお、Tripデータは[経度、緯度、タイムスタンプ]の形で整理しており、タイムスタンプは32bit浮動小数点にする必要があるので、一度、Javaのシリアル値を生成した後、午前0時からの経過時間(分)に変換しています。また、今回は停留所のリストをバスの通行経路として扱いました。

    /** DBからTripLayer用データを生成 */
    public String getTripData() throws SQLException,ParseException{
        Map<String,Object> map=new HashMap<String,Object>();
        List<GTrip> trips=getTrips();
        String ymd=getSurviceDate(trips.get(0));
        List<Map<String,List<Number[]>>> waypoints=new ArrayList<Map<String,List<Number[]>>>();
        long st=dateFormat.parse(ymd+" 00:00:00").getTime();
        for(GTrip t : trips){
            List<Number[]> wl=getWaypoints(t,st);
            Map<String,List<Number[]>> mm=new HashMap<String,List<Number[]>>();
            mm.put("segments", wl);
            waypoints.add(mm);
        }
        map.put("starttime", st);
        map.put("trips",waypoints);
        map.put("stops", stop2Map(getStops()));
        return gson.toJson(map);
    }

    private List<GStop> getStops()throws SQLException{
        return this.stopsDao.queryForAll();
    }

    private List<GTrip> getTrips()throws SQLException{
        return this.tripsDao.queryForAll();
    }

    /** Trip単位で通過経路(経度、緯度、時刻)のデータを生成 */
    private List<Number[]> getWaypoints(GTrip t,long sub)throws SQLException,ParseException{
        String ymd=getSurviceDate(t);
        QueryBuilder<GStopTime,Long> query=stopTimeDao.queryBuilder();
        query.where().eq("trip_id", t.trip_id);
        List<GStopTime> list=stopTimeDao.query(query.prepare());
        list.sort(new Comparator<GStopTime>(){
            @Override
            public int compare(GStopTime arg0, GStopTime arg1) {
                int v0=Integer.parseInt(arg0.stop_sequence);
                int v1=Integer.parseInt(arg1.stop_sequence);
                return (v0-v1);
            }
        });
        int n=list.size();
        List<Number[]> data=new ArrayList<Number[]>();
        for(int i=0;i<n;i++){
            GStopTime st=list.get(i);
            data.add(createWaypoint(st,ymd,sub));
        }
        return data;
    }

    private Number[] createWaypoint(GStopTime st,String ymd,long sub)throws ParseException,SQLException{
        long timestamp=getStopTimes(st,ymd);
        GStop sa=getStops(st.stop_id);
        return new Number[]{sa.stop_lon,sa.stop_lat,((double)(timestamp-sub))/(60.0*1000.0)};
    }

    private String getSurviceDate(GTrip t)throws SQLException{
        QueryBuilder<GCalendar,Long> query=calDao.queryBuilder();
        query.where().eq("service_id", t.service_id);
        List<GCalendar> list=calDao.query(query.prepare());
        String date=list.get(0).start_date;
        return date.substring(0, 4)+"-"+date.substring(4,6)+"-"+date.substring(6,8);
    }

    private GStop getStops(String stop_id)throws SQLException{
        QueryBuilder<GStop,Long> query=stopsDao.queryBuilder();
        query.where().eq("stop_id", stop_id);
        List<GStop> list=stopsDao.query(query.prepare());
        return list.get(0);
    }

    private long getStopTimes(GStopTime s,String ymd)throws ParseException{
        return dateFormat.parse(ymd+" "+s.arrival_time).getTime();
    }

Deck.GLでTripLayer可視化

作成したJSONデータはTripLayerに合わせた形にしているので、そのまま読み込ましてTripLayerを作成しています。
今回は停留所のリストをそのまま通行経路としたため、直行便はほぼ一直線に目的地へ移動してしまいます。バス運行経路を正確に再現し、道路に沿って動くようにするにはShapes.txtのデータをトレースして[経度、緯度、タイプスタンプ]のデータのリストを生成する必要があります。

<!doctype html>
<html class="no-js" lang="ja">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>Traffic-Bus</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.css" />
        <script src="https://code.jquery.com/jquery-3.4.0.js" integrity="sha256-DYZMCC8HTC+QDr5QNaIcfR7VSPtcISykd+6eSmBW5qo=" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/deck.gl@7.0.0/dist.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-scale/1.0.7/d3-scale.js"></script>
        <style type="text/css">
            html, body {
                padding: 0;
                margin: 0;
                width: 100%;
                height: 100%;
            }
            #panel {
                position: absolute;
                background: #ffffffaa;
                top: 0;
                left: 0;
                margin: 4px;
                padding: 4px;
                line-height: 1;
                width:260px;
                height:26px;
                z-index: 2;
                text-align: center;
                vertical-align: middle;
            }
            #tooltip {
                font-family: Helvetica, Arial, sans-serif;
                font-size: 12px;
                position: absolute;
                padding: 4px;
                margin: 8px;
                background: rgba(0, 0, 0, 0.8);
                color: #fff;
                max-width: 300px;
                z-index: 9;
                pointer-events: none;
            }
        </style>
    </head>
    <body>
        <div id="panel"></div>
        <div id="app" style="width:100%;height:100%;"></div>
        <div id="tooltip"></div>
    </body>
    <script type="text/javascript">
        const LAT=34.6;
        const LNG=135.5;
        let c_lon=0;
        let c_lat=0;
        let c_time=300;
        let triplayers=[];
        const deckgl = new deck.DeckGL({
            container: 'app',
            mapboxApiAccessToken: "アクセストークン",
            mapStyle: "mapbox://styles/mapbox/dark-v9",
            longitude: LNG,
            latitude: LAT,
            zoom: 11,
            pitch: 40,
            bearing: -10,
            onViewStateChange: ({viewState}) => {
                return viewState;
            }
        });
        const timeVal = () =>{
            return c_time;
        };

        const loadData = () => {
            d3.json("trips.json", (error, response)=>{
                setCenter(response.stops);
                let v_time=response.starttime;
                let trip=renderTripLayer(response.trips);
                let stop=renderStopLayer(response.stops);
                deckgl.setProps({
                    layers: [trip,stop],
                    viewState: {
                        longitude: c_lon,
                        latitude: c_lat,
                        zoom: 11,
                        transitionInterpolator: new FlyToInterpolator(),
                        transitionDuration: 3000
                    }
                });
                const update = () =>{
                    c_time +=0.1;
                    if(c_time>1439){
                        window.cancelAnimationFrame(thread);
                        return; 
                    }else{
                        trip.setState({time: c_time});
                    let da=new Date(v_time+c_time*60*1000);
                    $("#panel").text(da);
                    window.requestAnimationFrame(update);
                    }
                };
                let thread=window.requestAnimationFrame(update);
            });
        };
        const renderStopLayer = (data) => {
            const pointlayer = new deck.ScatterplotLayer({
                id: 'stop',
                data,
                fp64: false,
                opacity: 0.8,
                stroked: true,
                filled: true,
                radiusScale: 6,
                radiusMinPixels: 1,
                radiusMaxPixels: 100,
                lineWidthMinPixels: 1,
                getPosition: d => d.coordinates,
                getRadius: 8,
                getFillColor: [255, 140, 0],
                getLineColor: [0, 0, 0],
                pickable: true,
                onHover: updateTooltipStop
            });
            return pointlayer;
        };

        const renderTripLayer =(trips) => {
            const tripLayer = new TripsLayer({
                id: 'trips-layer',
                data: trips,
                getPath: d => d.segments,
                getColor: [253, 128, 93],
                opacity: 0.6,
                widthMinPixels: 3,
                rounded: true,
                trailLength: 15,
                currentTime: timeVal 
            });
            return tripLayer;
        }

        const setCenter=(feature) =>{
            const n=feature.length;
             for(let i=0;i<n;i++){
                c_lon +=feature[i].coordinates[0];
                c_lat +=feature[i].coordinates[1];
            }
            c_lon=c_lon/n;
            c_lat=c_lat/n;
        };

        const updateTooltipStop=({x, y, object}) => {
            const tooltip = document.getElementById("tooltip");
            if (object) {
                tooltip.style.visibility="visible";
                tooltip.style.top = y+"px";
                tooltip.style.left = x+"px";
                tooltip.innerHTML = "<p>"+object.name+"</p>";
            } else { 
                tooltip.style.visibility="hidden";
                tooltip.innerHTML = "";
            }
        };
        loadData();
    </script>
</html>

最後に

とりあえず、GTFS-JPからDBを作成し、Tripデータを作成して可視化するとこまで処理してみました。
GTFS-JPはCSVデータなので扱いが楽です。GTFSリアルタイムも「gtfs-realtime-bindings」を使用すると簡単にデータを処理できそうな感じです。また、両備バスの場合で、DB容量は約14MB(Sqlite)だったので、Android端末等に導入することもできそうだと思いました。
GTFS-JPの本格的な普及はこれからだと思いますが、少子高齢化や過疎化により地方の公共交通機関は色々な課題を抱えているので、かじっておくと何かしら地域の役に立つかも?と思いました。

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

会計freee×GASで取引先別の月次貸借対照表データを取得してみる

この投稿の目的

  • 前回、以下の記事で月次推移表の投稿をしてみましたが、PLのみかつ補助科目がないレベルでの投稿でした。  https://qiita.com/Kirin8210/items/ba5bd7aed19411b0c4b8
  • 現実に試算表から業務管理とかするときは、特にBSでは取引先別の補助科目(タグ)情報を取得してやるのが一般的っぽいので、取引先のBSを取得するコードをGASで書いてみます。
  • 月次は予実とかで使ったり、監査法人提出するくらいの使途しかない気もしますが一応。二期比較であれば、@kabanyasuのコチラがおすすめ:https://qiita.com/kabanyasu/items/eef45e56d88be7e3ad37
  • 相変わらずホリデープログラマなので、汚いところとかありましたらご容赦下さい。

前提

  • 言語はGAS、データの取得&保存先はGoogle Spreadsheetとします。
  • 認証のところは、前回に引き続き省略します。最近、とてもわかり易いブログを見つけたので、コチラご参照いただき、認証したあとで実行して下さい。  https://moripro.net/freee-gas-api/

コード

スプレッドシートに転記する処理

//params
var fiscalMonth = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

function setToSheet() {
    //各月のBSを取得
    var monthlyFs = fiscalMonth.map(function (month) {return getItemArray(month);});

    //BSの配列を3次元→2次元に削減
    var list = monthlyFs.reduce(function (prev, post) {
        prev.push.apply(prev, post);
        return prev;
    });

    //シートに転記
    var bsSheet = ss.getSheetByName('BS');
    bsSheet.getRange(2, 1, list.length, list[0].length).setValues(list);
}

取引先データ、合計勘定データを分けて配列に格納する処理

//取引先情報の有無(仮払・仮受消費税は取引先持っていない)と、合計勘定か否かにより抽出分岐をかける
function getItemArray(month) {  
  //合計勘定=Falseなら個別科目のデータを、Trueならカテゴリデータを返す
  var data = getBs(month).map(function(account){
    if (account.partners) {
      var arr = account.partners.map(function(partner){
        return [
          currentYear,
          month,
          account.account_item_id,
          account.account_item_name,
          partner.name,
          partner.closing_balance
        ];
      });
      return arr;

    //仮払・仮受消費税はaccount_item_idはあるが取引先はない
    }else if (!account.total_line){
      return [[
        currentYear,
        month,
        account.account_item_id,
        account.account_item_name,
        null,
        account.closing_balance
      ]];
      //合計勘定の場合

    }else{
      return [[
        currentYear,
        month,
        account.account_category_id,
        account.account_category_name,
        "合計勘定",
        account.closing_balance
      ]];
    }
  });

  //次元削減して返す
  var reducedArray = data.reduce(function (prev, post) {
    prev.push.apply(prev, post);
    return prev;
  });

  return reducedArray;
}

BSを取得してくる処理(開始月、終了月を引数に取る)

//BSを取得してくる
function getBs(month) {
    //トークン取得
    var freeApp = getService();
    var accessToken = freeApp.getAccessToken();

    //取得情報のパラメータ指定
    var url = 'https://api.freee.co.jp/api/1/';
    var targetItem = 'reports/trial_bs?';
    var companyId = 'company_id=' + 事業所IDを数値で入力;
    var fiscalYear = '&fiscal_year=' + 会計年度を数値で入力;
    var startMonth = '&start_month=' + month;
    var endMonth = '&end_month=' + month;
    var breakdown = '&breakdown_display_type=' + 'partner';

    //url結合
    url += targetItem + companyId + fiscalYear + startMonth + endMonth + breakdown;

    //option, headers
    var options = {
        'method': 'get',
        'headers': {
            'Authorization': 'Bearer ' + accessToken
        }
    };
    //取得したデータをJsonParseしてバランスの部分だけ取得
    var res = UrlFetchApp.fetch(url, options).getContentText();
    var resBs = JSON.parse(res).trial_bs.balances;
    return resBs;
}

freeeのデモ用実行環境だと取引先ちゃんとついてなかったのであれだけど、データ整形して後日更新してみる。

終わりに

  • ひとまずデータはちゃんと取れた。
  • 2ブロック目のmapのところはこれじゃあんまり高階関数使った意味なさげだから、もうちょい研究
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

会計freee×GASで取引先別の貸借対照表データを取得してみる

この投稿の目的

  • 前回、以下の記事で月次推移表の投稿をしてみましたが、PLのみかつ補助科目がないレベルでの投稿でした。  https://qiita.com/Kirin8210/items/ba5bd7aed19411b0c4b8
  • 現実に試算表から業務管理とかするときは、特にBSでは取引先別の補助科目(タグ)情報を取得してやるのが一般的っぽいので、取引先のBSを取得するコードをGASで書いてみます。
  • 相変わらずホリデープログラマなので、汚いところとかありましたらご容赦下さい。

前提

  • 言語はGAS、データの取得&保存先はGoogle Spreadsheetとします。
  • 認証のところは、前回に引き続き省略します。最近、とてもわかり易いブログを見つけたので、コチラご参照いただき、認証したあとで実行して下さい。  https://moripro.net/freee-gas-api/

コード

スプレッドシートに転記する処理

//params
var fiscalMonth = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

function setToSheet() {
    //各月のBSを取得
    var monthlyFs = fiscalMonth.map(function (month) {return getItemArray(month);});

    //BSの配列を3次元→2次元に削減
    var list = monthlyFs.reduce(function (prev, post) {
        prev.push.apply(prev, post);
        return prev;
    });

    //シートに転記
    var bsSheet = ss.getSheetByName('BS');
    bsSheet.getRange(2, 1, list.length, list[0].length).setValues(list);
}

取引先データ、合計勘定データを分けて配列に格納する処理

//取引先情報の有無(仮払・仮受消費税は取引先持っていない)と、合計勘定か否かにより抽出分岐をかける
function getItemArray(month) {  
  //合計勘定=Falseなら個別科目のデータを、Trueならカテゴリデータを返す
  var data = getBs(month).map(function(account){
    if (account.partners) {
      var arr = account.partners.map(function(partner){
        return [
          currentYear,
          month,
          account.account_item_id,
          account.account_item_name,
          partner.name,
          partner.closing_balance
        ];
      });
      return arr;

    //仮払・仮受消費税はaccount_item_idはあるが取引先はない
    }else if (!account.total_line){
      return [[
        currentYear,
        month,
        account.account_item_id,
        account.account_item_name,
        null,
        account.closing_balance
      ]];
      //合計勘定の場合

    }else{
      return [[
        currentYear,
        month,
        account.account_category_id,
        account.account_category_name,
        "合計勘定",
        account.closing_balance
      ]];
    }
  });

  //次元削減して返す
  var reducedArray = data.reduce(function (prev, post) {
    prev.push.apply(prev, post);
    return prev;
  });

  return reducedArray;
}

BSを取得してくる処理(開始月、終了月を引数に取る)

//BSを取得してくる
function getBs(month) {
    //トークン取得
    var freeApp = getService();
    var accessToken = freeApp.getAccessToken();

    //取得情報のパラメータ指定
    var url = 'https://api.freee.co.jp/api/1/';
    var targetItem = 'reports/trial_bs?';
    var companyId = 'company_id=' + 事業所IDを数値で入力;
    var fiscalYear = '&fiscal_year' + 会計年度を数値で入力;
    var startMonth = '&start_month' + month;
    var endMonth = '&end_month=' + month;
    var breakdown = '&breakdown_display_type=' + 'partner';

    //url結合
    url += targetItem + companyId + fiscalYear + startMonth + endMonth + breakdown;

    //option, headers
    var options = {
        'method': 'get',
        'headers': {
            'Authorization': 'Bearer ' + accessToken
        }
    };
    //取得したデータをJsonParseしてバランスの部分だけ取得
    var res = UrlFetchApp.fetch(url, options).getContentText();
    var resBs = JSON.parse(res).trial_bs.balances;
    return resBs;
}

freeeのデモ用実行環境だと取引先ちゃんとついてなかったのであれだけど、データ整形して後日更新してみる。

終わりに

  • ひとまずデータはちゃんと取れた。
  • 2ブロック目のmapのところはこれじゃあんまり高階関数使った意味なさげだから、もうちょい研究
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あなたは何問解ける?JavaScriptクイズ

JavaScriptの基本構文に関するクイズ。全20問です。
あなたは何問正解できるでしょうか?
問題のコードはGoogle Chromeのディベロッパーツールで動作確認しています。

問1 以下のコードの実行結果として正しいものを選べ。

var str1 = new String("1");
var str2 = "1";
var num1 = new Number(1);
var num2 = 1;

console.log(
    str1 == num1, 
    str1 == num2, 
    str2 == num1, 
    str2 == num2
);
  1. true true true true
  2. false true true true
  3. false false true true
  4. false false false true


答え

2
JavaScriptでは数値も文字列もプリミティブ型として扱われますが、newでインスタンスを作ると参照型になります。
参照型同士を==で比較すると参照を比較しますが、どちらかがプリミティブ型であれば値をプリミティブ型に変換してから比較します。

問2 以下のコードの実行結果として正しいものを選べ。

var num1 = new Number(1);
var num2 = new Number(1);
var num3 = 1;
var num4 = 1;

console.log(
    num1 == num2, 
    num1 === num2,
    num1 == num3,
    num1 === num3,
    num3 == num4,
    num3 === num4
);
  1. ture true true true true true
  2. true true false false true true
  3. false true false true true true
  4. false false true false true true


答え

4
==で比較したときの挙動は問1で解説した通りです。
===で参照型を比較するときは==と同じく参照を比較します。
プリミティブ型を比較するときは型まで比較するので==のように参照型をプリミティブ型に変換するような挙動はありません。

問3 以下のコードの実行結果として正しいものを選べ。

console.log(
    1 / 0,
    0 / 0,
    NaN / NaN,
    Infinity / Infinity
);
  1. Infinity NaN NaN NaN
  2. NaN NaN NaN NaN
  3. Infinity NaN 1 1
  4. Infinity Infinity Infinity Infinity


答え

1
0を0で割るとNaNになります。
正の数を0で割るとInfinityになり、負の数を0で割ると-Infinityになります。
InfinityをInfinityで割ったり、引いたりするとNaNになります。

問4 以下のコードの実行結果として正しいものを選べ。

var infinity1 = Infinity;
var infinity2 = Infinity;
var nan1 = NaN;
var nan2 = NaN;

console.log(
    infinity1 == infinity2,
    infinity1 - infinity2,
    infinity1 + infinity2,
    infinity1 * -1,
    nan1 == nan2,
    nan1 === nan2
);
  1. true NaN Infinity -Infinity false false
  2. false Infinity Infinity Infinity false false
  3. true NaN Infinity -Infinity true true
  4. true NaN NaN NaN false false


答え

1
NaNを比較するとfalseが返ります。

問5 以下のコードの実行結果として正しいものを選べ。

var message = "hello";

function hello() {
    var message = "world";
    console.log(message);
}

console.log(hello());
console.log(message);
  1. world null hello
  2. world null world
  3. world undefined hello
  4. world undefined world


答え

3
戻り値がない関数の結果はundefinedになります。
また、関数内でvarを使って変数を定義するとローカル変数となります。

問6 以下のコードの実行結果として正しいものを選べ。

var str1 = new String("03 ");
var str2 = " 2 ";

console.log(
    str1 + str2,
    str1 - str2,
    str1 * str2,
    str1 / str2
);
  1. 03 2 1 6 1.5
  2. 5 1 6 1.5
  3. エラー
  4. 03 2 NaN Nan NaN


答え

1
文字列で変数を定義して足し算をすると文字列結合になりますが、その他の算術演算子を使うとNaNが返ります。
ただし、数字とスペースなどの組み合わせであれば足し算以外は数字に変換してから実行されます。

問7 以下のコードの実行結果として正しいものを選べ。

var people = ["Alice", "Bob", "Chris"];

delete people[1];

people[4] = "Eliza";

for(var i = 0; i < people.length; i++) {
    console.log(people[i]);
}
  1. Alice Chris Eliza
  2. エラー
  3. Alice undefined Chris undefined Eliza
  4. Alice null Chris null Eliza


答え

3
delete演算子を使うとオブジェクトのプロパティや配列の要素を削除できます。
ただし配列の場合は要素自体はundefinedになり、要素数は変わりません。
また、JavaScriptの配列は可変なのでインデックスを指定して要素を追加できます。
指定したインデックスよりも前の要素が存在しない場合はそれらの要素にはundefinedが入ります。

問8 以下のコードの実行結果として正しいものを選べ。

var name = "John";

function greet() {
    console.log("I'm " + name);
    var name = "Bob";
}
greet();
  1. I'm Bob
  2. I'm John
  3. I'm undefined
  4. エラー


答え

3
関数内で変数を定義した場合、定義した位置に関わらず、最初にundefinedとして定義されます。
これを変数の巻き上げといいます。

問9 以下のコードの実行結果として正しいものを選べ。

var person = {name: "Alice"};

console.log(person.name);
console.log(person.age);
console.log(age);
  1. Alice エラー エラー
  2. Alice undefined エラー
  3. Alice undefined undefined
  4. Alice null undefied


答え

2
オブジェクトのプロパティを参照したときに、そのプロパティが存在しなければundefinedが返ります。
存在しない変数を参照しようとした場合、ReferenceErrorが発生します。

問10 ①〜④の中でのコメントを外して実行したときにエラーが発生しないものの組み合わせとして正しいものを選べ。

var num = 100;

function greet() {
    var message1 = "Good morning";
}

if(num > 0) {
    greet();
    var message2 = "Hello";
    let message3 = "Good after noon";
    const message4 = "Good eveining";
}

// ① console.log(message1);
// ② console.log(message2);
// ③ console.log(message3);
// ④ console.log(message4);

  1. ①、②
  2. ②のみ
  3. ①のみ
  4. ①、②、③、④


答え

2
JavaScriptではブロック内であってもvarで変数を定義するとグローバルスコープで定義されます。
ただし、関数内で定義した場合はローカルスコープになります。
関数以外でスコープを制限したい場合はletを使うと良いです。

問11 ①〜④の中でのコメントを外して実行したときにエラーが発生しないものの組み合わせとして正しいものを選べ。

var num = 0;

function greet() {
    var message1 = "Good morning";
}

if(num > 0) {
    greet();
    var message2 = "Hello";
    let message3 = "Good after noon";
    const message4 = "Good eveining";
}

// ① console.log(message1);
// ② console.log(message2);
// ③ console.log(message3);
// ④ console.log(message4);

  1. ①、②
  2. ②のみ
  3. ①のみ
  4. ①、②、③、④


答え

2
問10と同じ。
varで宣言するとその行が実行されるかどうかに関わらず変数が定義されます。

問12 以下のコードの実行結果として正しいものを選べ。

var global = "Global";

function f1() {
    console.log(global);
}

function f2() {
    var global = "F2";
    f1();
    console.log(global);
}

f2();
  1. Global F2
  2. F2 F2
  3. Global Global
  4. undefined F2


答え

1
こちらもスコープの問題です。
f2関数内でglobalを宣言した後にf1関数を呼んでいますが、f1のglobalはグローバルスコープのglobalを参照します。

問13 ①〜③の中からpeople内の要素が出力されるものの組み合わせとして正しいものを選べ。

var people = ["Alice", "Bob", "Chris"];

// ①
for(var p in people) {
    console.log(p);
}

// ②
for(var p of people) {
    console.log(p);
}

// ③
for(var i = 0; i < people.length; i++) {
    console.log(people[i]);
}
  1. ①、②、③
  2. ①、③
  3. ②、③
  4. ③のみ


答え

3
inを使ってループすると配列のインデックスを取得できます。
ofを使ってループすると配列の要素を取得できます。

問14 以下のコードの実行結果として正しいものを選べ。

function Person(name) {
    this.name = name;
}
Person.age = 10;

var person1 = new Person("John");
console.log(person1.name, person1.age);

person1.age = 11;
console.log(person1.age);
  1. エラー
  2. John 10 11
  3. John undefined 11
  4. John undefined undefined


答え

3
「インスタンス.プロパティ名 = 値」とすることで新しくプロパティを追加できます。
また、「オブジェクト名.prototype.プロパティ名 = 値」とすることでもプロパティを追加できます。
上記のコードであれば4行目を「Person.protptype.age = 10」に修正すれば追加でき、選択肢2の結果になります。

問15 以下のコードの実行結果として正しいものを選べ。

Date.prototype.getFormattedDate = function(formatString) {

    formatString = formatString.replace(/yyyy/g, this.getFullYear());
    formatString = formatString.replace(/MM/g, ("0" + (this.getMonth())).slice(-2));
    formatString = formatString.replace(/dd/g, ("0" + this.getDate()).slice(-2));
    formatString = formatString.replace(/HH/g, ("0" + this.getHours()).slice(-2));
    formatString = formatString.replace(/mm/g, ("0" + this.getMinutes()).slice(-2));
    formatString = formatString.replace(/ss/g, ("0" + this.getSeconds()).slice(-2));

    return formatString;
}
// 2019年5月25日 9時0分0秒に実行
var date = new Date();
console.log(date.getFormattedDate("yyyy-MM-dd HH:mm:ss"));
  1. 2019-05-25 09:00:00
  2. 2019-04-25 09:00:00
  3. 2019-5-25 9:0:0
  4. 2019-4-25 9:0:0


答え

2
問14と同じくprototypeを使った問題。
ただし、Date.getMonthで返ってくる値は実際の月 - 1が返ってくるので、そのまま使うと4月になってしまいます。

問16 ①〜④の中でfalseが出力されるものの組み合わせとして正しいものを選べ。

function f() {
    console.log(window == this);
}

var person = {
    f: function() {
        console.log(window == this);
    }
};

console.log(window == this); // ①
f();                         // ②
person.f();                  // ③
setTimeout(person.f, 1000);  // ④
  1. ②、③
  2. ①、②
  3. ②、③、④


答え

3
thisの問題。
オブジェクトのメソッド内ではthisはオブジェクト自身を指す。
それ以外は基本的にグローバルオブジェクトを指す。
この記事の例がわかりやすいと思います。
ただし、オブジェクトのメソッドでもコールバック関数で呼ばれるときはグローバルオブジェクトを指す。

問17 以下のコードの実行結果として正しいものを選べ。

var num1 = 1;
var num2 = new Number(2);
var array = [3, 4, 5];

function Person(name) {
    this.name = name;
}

var person = new Person("Mike");

console.log(
    typeof(num1),
    typeof(num2),
    typeof(array),
    typeof(person),
    typeof(person.name),
);
  1. number Number Array Person string
  2. number Number Array object string
  3. number number object object string
  4. number object object object string


答え

4
typeofの引数にプリミティブ型を指定する引数の型が、参照型を入れると全てobjectが返ります。

問18 以下のコードの実行結果として正しいものを選べ。

var num1 = 1;
var num2 = new Number(2);
var array = [3, 4, 5];

function Person(name) {
    this.name = name;
}

var person = new Person("Mike");

console.log(
    num1 instanceof Number,
    num2 instanceof Number,
    array instanceof Array,
    person instanceof Person,
    person.name instanceof String,
);
  1. エラー
  2. true true true true true
  3. true true false false true
  4. false true true true false


答え

4
プリミティブ型以外であれば型を指定して判定できます。

問19 以下のコードの実行結果として正しいものを選べ。

var num1 = 1;
var num2 = new Number(2);
var array = [3, 4, 5];

function Person(name) {
    this.name = name;
}

var person = new Person("Mike");

console.log(
    num1 instanceof Object,
    num2 instanceof Object,
    array instanceof Object,
    person instanceof Object,
    person.name instanceof Object,
);
  1. false true true true false
  2. true true true true true
  3. false true true true true
  4. false false true true false


答え

1
こちらも18と同じです。
全ての参照型はObjectを継承します。

問20 ①〜④の中でHelloと出力されるものの組み合わせとして正しいものを選べ。

Object.prototype.hello = "Hello";

var str = "String";
var num = new Number(1);
var array = [1, 2, 3];
var person = {name : "Kate"};

console.log(str.hello);    // ①
console.log(num.hello);    // ②
console.log(array.hello);  // ③
console.log(person.hello); // ④
  1. ①、②、④
  2. ③、④
  3. ②、③、④
  4. ①、②、③、④


答え

4
プリミティブ型でもラッパークラスのプロパティにアクセスできます。

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

あなたは何問解ける?JavaScript構文クイズ

JavaScriptの基本構文に関するクイズ。全20問です。
あなたは何問正解できるでしょうか?
問題のコードはGoogle Chromeのディベロッパーツールで動作確認しています。

問1 以下のコードの実行結果として正しいものを選べ。

var str1 = new String("1");
var str2 = "1";
var num1 = new Number(1);
var num2 = 1;

console.log(
    str1 == num1, 
    str1 == num2, 
    str2 == num1, 
    str2 == num2
);
  1. true true true true
  2. false true true true
  3. false false true true
  4. false false false true


答え

2
JavaScriptでは数値も文字列もプリミティブ型として扱われますが、newでインスタンスを作ると参照型になります。
参照型同士を==で比較すると参照を比較しますが、どちらかがプリミティブ型であれば値をプリミティブ型に変換してから比較します。

問2 以下のコードの実行結果として正しいものを選べ。

var num1 = new Number(1);
var num2 = new Number(1);
var num3 = 1;
var num4 = 1;

console.log(
    num1 == num2, 
    num1 === num2,
    num1 == num3,
    num1 === num3,
    num3 == num4,
    num3 === num4
);
  1. ture true true true true true
  2. true true false false true true
  3. false true false true true true
  4. false false true false true true


答え

4
==で比較したときの挙動は問1で解説した通りです。
===で参照型を比較するときは==と同じく参照を比較します。
プリミティブ型を比較するときは型まで比較するので==のように参照型をプリミティブ型に変換するような挙動はありません。

問3 以下のコードの実行結果として正しいものを選べ。

console.log(
    1 / 0,
    0 / 0,
    NaN / NaN,
    Infinity / Infinity
);
  1. Infinity NaN NaN NaN
  2. NaN NaN NaN NaN
  3. Infinity NaN 1 1
  4. Infinity Infinity Infinity Infinity


答え

1
0を0で割るとNaNになります。
正の数を0で割るとInfinityになり、負の数を0で割ると-Infinityになります。
InfinityをInfinityで割ったり、引いたりするとNaNになります。

問4 以下のコードの実行結果として正しいものを選べ。

var infinity1 = Infinity;
var infinity2 = Infinity;
var nan1 = NaN;
var nan2 = NaN;

console.log(
    infinity1 == infinity2,
    infinity1 - infinity2,
    infinity1 + infinity2,
    infinity1 * -1,
    nan1 == nan2,
    nan1 === nan2
);
  1. true NaN Infinity -Infinity false false
  2. false Infinity Infinity Infinity false false
  3. true NaN Infinity -Infinity true true
  4. true NaN NaN NaN false false


答え

1
NaNを比較するとfalseが返ります。

問5 以下のコードの実行結果として正しいものを選べ。

var message = "hello";

function hello() {
    var message = "world";
    console.log(message);
}

console.log(hello());
console.log(message);
  1. world null hello
  2. world null world
  3. world undefined hello
  4. world undefined world


答え

3
戻り値がない関数の結果はundefinedになります。
また、関数内でvarを使って変数を定義するとローカル変数となります。

問6 以下のコードの実行結果として正しいものを選べ。

var str1 = new String("03 ");
var str2 = " 2 ";

console.log(
    str1 + str2,
    str1 - str2,
    str1 * str2,
    str1 / str2
);
  1. 03 2 1 6 1.5
  2. 5 1 6 1.5
  3. エラー
  4. 03 2 NaN Nan NaN


答え

1
文字列で変数を定義して足し算をすると文字列結合になりますが、その他の算術演算子を使うとNaNが返ります。
ただし、数字とスペースなど、数値に変換できる組み合わせであれば足し算以外は数字に変換してから実行されます。

問7 以下のコードの実行結果として正しいものを選べ。

var people = ["Alice", "Bob", "Chris"];

delete people[1];

people[4] = "Eliza";

for(var i = 0; i < people.length; i++) {
    console.log(people[i]);
}
  1. Alice Chris Eliza
  2. エラー
  3. Alice undefined Chris undefined Eliza
  4. Alice null Chris null Eliza


答え

3
delete演算子を使うとオブジェクトのプロパティや配列の要素を削除できます。
ただし配列の場合は要素自体はundefinedになり、要素数は変わりません。
また、JavaScriptの配列は可変なのでインデックスを指定して要素を追加できます。
指定したインデックスよりも前の要素が存在しない場合はそれらの要素にはundefinedが入ります。

問8 以下のコードの実行結果として正しいものを選べ。

var name = "John";

function greet() {
    console.log("I'm " + name);
    var name = "Bob";
}
greet();
  1. I'm Bob
  2. I'm John
  3. I'm undefined
  4. エラー


答え

3
関数内で変数を定義した場合、定義した位置に関わらず、最初にundefinedとして定義されます。
これを変数の巻き上げといいます。

問9 以下のコードの実行結果として正しいものを選べ。

var person = {name: "Alice"};

console.log(person.name);
console.log(person.age);
console.log(age);
  1. Alice エラー エラー
  2. Alice undefined エラー
  3. Alice undefined undefined
  4. Alice null undefied


答え

2
オブジェクトのプロパティを参照したときに、そのプロパティが存在しなければundefinedが返ります。
存在しない変数を参照しようとした場合、ReferenceErrorが発生します。

問10 ①〜④の中でのコメントを外して実行したときにエラーが発生しないものの組み合わせとして正しいものを選べ。

var num = 100;

function greet() {
    var message1 = "Good morning";
}

if(num > 0) {
    greet();
    var message2 = "Hello";
    let message3 = "Good after noon";
    const message4 = "Good eveining";
}

// ① console.log(message1);
// ② console.log(message2);
// ③ console.log(message3);
// ④ console.log(message4);

  1. ①、②
  2. ②のみ
  3. ①のみ
  4. ①、②、③、④


答え

2
JavaScriptではブロック内であってもvarで変数を定義するとグローバルスコープで定義されます。
ただし、関数内で定義した場合はローカルスコープになります。
関数以外でスコープを制限したい場合はletを使うと良いです。

問11 ①〜④の中でのコメントを外して実行したときにエラーが発生しないものの組み合わせとして正しいものを選べ。

var num = 0;

function greet() {
    var message1 = "Good morning";
}

if(num > 0) {
    greet();
    var message2 = "Hello";
    let message3 = "Good after noon";
    const message4 = "Good eveining";
}

// ① console.log(message1);
// ② console.log(message2);
// ③ console.log(message3);
// ④ console.log(message4);

  1. ①、②
  2. ②のみ
  3. ①のみ
  4. ①、②、③、④


答え

2
問10と同じ。
varで宣言するとその行が実行されるかどうかに関わらず変数が定義されます。

問12 以下のコードの実行結果として正しいものを選べ。

var global = "Global";

function f1() {
    console.log(global);
}

function f2() {
    var global = "F2";
    f1();
    console.log(global);
}

f2();
  1. Global F2
  2. F2 F2
  3. Global Global
  4. undefined F2


答え

1
こちらもスコープの問題です。
f2関数内でglobalを宣言した後にf1関数を呼んでいますが、f1のglobalはグローバルスコープのglobalを参照します。

問13 ①〜③の中からpeople内の要素が出力されるものの組み合わせとして正しいものを選べ。

var people = ["Alice", "Bob", "Chris"];

// ①
for(var p in people) {
    console.log(p);
}

// ②
for(var p of people) {
    console.log(p);
}

// ③
for(var i = 0; i < people.length; i++) {
    console.log(people[i]);
}
  1. ①、②、③
  2. ①、③
  3. ②、③
  4. ③のみ


答え

3
inを使ってループすると配列のインデックスを取得できます。
ofを使ってループすると配列の要素を取得できます。

問14 以下のコードの実行結果として正しいものを選べ。

function Person(name) {
    this.name = name;
}
Person.age = 10;

var person1 = new Person("John");
console.log(person1.name, person1.age);

person1.age = 11;
console.log(person1.age);
  1. エラー
  2. John 10 11
  3. John undefined 11
  4. John undefined undefined


答え

3
「インスタンス.プロパティ名 = 値」とすることで新しくプロパティを追加できます。
また、「オブジェクト名.prototype.プロパティ名 = 値」とすることでもプロパティを追加できます。
上記のコードであれば4行目を「Person.prototype.age = 10」に修正すれば追加でき、選択肢2の結果になります。

問15 以下のコードの実行結果として正しいものを選べ。

Date.prototype.getFormattedDate = function(formatString) {

    formatString = formatString.replace(/yyyy/g, this.getFullYear());
    formatString = formatString.replace(/MM/g, ("0" + (this.getMonth())).slice(-2));
    formatString = formatString.replace(/dd/g, ("0" + this.getDate()).slice(-2));
    formatString = formatString.replace(/HH/g, ("0" + this.getHours()).slice(-2));
    formatString = formatString.replace(/mm/g, ("0" + this.getMinutes()).slice(-2));
    formatString = formatString.replace(/ss/g, ("0" + this.getSeconds()).slice(-2));

    return formatString;
}
// 2019年5月25日 9時0分0秒に実行
var date = new Date();
console.log(date.getFormattedDate("yyyy-MM-dd HH:mm:ss"));
  1. 2019-05-25 09:00:00
  2. 2019-04-25 09:00:00
  3. 2019-5-25 9:0:0
  4. 2019-4-25 9:0:0


答え

2
問14と同じくprototypeを使った問題。
ただし、Date.getMonthで返ってくる値は実際の月 - 1が返ってくるので、そのまま使うと4月になってしまいます。

問16 ①〜④の中でfalseが出力されるものの組み合わせとして正しいものを選べ。

function f() {
    console.log(window == this);
}

var person = {
    f: function() {
        console.log(window == this);
    }
};

console.log(window == this); // ①
f();                         // ②
person.f();                  // ③
setTimeout(person.f, 1000);  // ④
  1. ②、③
  2. ①、②
  3. ②、③、④


答え

3
thisの問題。
オブジェクトのメソッド内ではthisはオブジェクト自身を指す。
それ以外は基本的にグローバルオブジェクトを指す。
この記事の例がわかりやすいと思います。
ただし、オブジェクトのメソッドでもコールバック関数で呼ばれるときはグローバルオブジェクトを指す。

問17 以下のコードの実行結果として正しいものを選べ。

var num1 = 1;
var num2 = new Number(2);
var array = [3, 4, 5];

function Person(name) {
    this.name = name;
}

var person = new Person("Mike");

console.log(
    typeof(num1),
    typeof(num2),
    typeof(array),
    typeof(person),
    typeof(person.name),
);
  1. number Number Array Person string
  2. number Number Array object string
  3. number number object object string
  4. number object object object string


答え

4
typeofの引数にプリミティブ型を指定する引数の型が、参照型を入れると全てobjectが返ります。

問18 以下のコードの実行結果として正しいものを選べ。

var num1 = 1;
var num2 = new Number(2);
var array = [3, 4, 5];

function Person(name) {
    this.name = name;
}

var person = new Person("Mike");

console.log(
    num1 instanceof Number,
    num2 instanceof Number,
    array instanceof Array,
    person instanceof Person,
    person.name instanceof String,
);
  1. エラー
  2. true true true true true
  3. true true false false true
  4. false true true true false


答え

4
プリミティブ型以外であれば型を指定して判定できます。

問19 以下のコードの実行結果として正しいものを選べ。

var num1 = 1;
var num2 = new Number(2);
var array = [3, 4, 5];

function Person(name) {
    this.name = name;
}

var person = new Person("Mike");

console.log(
    num1 instanceof Object,
    num2 instanceof Object,
    array instanceof Object,
    person instanceof Object,
    person.name instanceof Object,
);
  1. false true true true false
  2. true true true true true
  3. false true true true true
  4. false false true true false


答え

1
こちらも18と同じです。
全ての参照型はObjectを継承します。

問20 ①〜④の中でHelloと出力されるものの組み合わせとして正しいものを選べ。

Object.prototype.hello = "Hello";

var str = "String";
var num = new Number(1);
var array = [1, 2, 3];
var person = {name : "Kate"};

console.log(str.hello);    // ①
console.log(num.hello);    // ②
console.log(array.hello);  // ③
console.log(person.hello); // ④
  1. ①、②、④
  2. ③、④
  3. ②、③、④
  4. ①、②、③、④


答え

4
プリミティブ型でもラッパークラスのプロパティにアクセスできます。

参考文献

JavaScriptリファレンス
HTML5プロフェッショナル認定試験レベル2 サンプル問題
JavaScriptのthisがよくわからないので徹底的に調べてできるだけわかりやすく解説してみる

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

Reactのversionを上げるためにyarnを実行したらfseventsでERRORが発生した

久しぶりに個人で作っているPortfolioのversionを上げようとしたらfseventsが原因でエラーが発生したのでメモ。

yarnを実行したら下記のようなエラーが発生する

warning Error running install script for optional dependency: "/Users/minusfive/dev/oss/ember-cli-sass-variables-export/node_modules/fsevents: Command failed.
Exit code: 1
Command: node install
Arguments:
Directory: /Users/minusfive/dev/oss/ember-cli-sass-variables-export/node_modules/fsevents
Output:
node-pre-gyp info it worked if it ends with ok
node-pre-gyp info using node-pre-gyp@0.6.39
node-pre-gyp info using node@10.4.0 | darwin | x64
node-pre-gyp info check checked for \"/Users/minusfive/dev/oss/ember-cli-sass-variables-export/node_modules/fsevents/lib/binding/Release/node-v64-darwin-x64/fse.node\" (not found)
node-pre-gyp http GET https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.1.3/fse-v1.1.3-node-v64-darwin-x64.tar.gz
node-pre-gyp http 404 https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.1.3/fse-v1.1.3-node-v64-darwin-x64.tar.gz
node-pre-gyp ERR! Tried to download(404): https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.1.3/fse-v1.1.3-node-v64-darwin-x64.tar.gz
node-pre-gyp ERR! Pre-built binaries not found for fsevents@1.1.3 and node@10.4.0 (node-v64 ABI, unknown) (falling back to source compile with node-gyp)
node-pre-gyp http 404 status code downloading tarball https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.1.3/fse-v1.1.3-node-v64-darwin-x64.tar.gz
node-pre-gyp ERR! Tried to download(undefined): https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.1.3/fse-v1.1.3-node-v64-darwin-x64.tar.gz
node-pre-gyp ERR! Pre-built binaries not found for fsevents@1.1.3 and node@10.4.0 (node-v64 ABI, unknown) (falling back to source compile with node-gyp)
node-pre-gyp http Connection closed while downloading tarball file
gyp info it worked if it ends with ok
gyp info using node-gyp@3.6.2
gyp info using node@10.4.0 | darwin | x64
gyp info ok
gyp info it worked if it ends with ok
gyp info using node-gyp@3.6.2
gyp info using node@10.4.0 | darwin | x64

以下略

yarn startもERRORが発生してしまい、1~6の解決策がyarn側から提示されたがうまくいかなかった。

これはfseventsNode.jsのversionが対応していないために起こるエラーで、fseventsv1.2.9で解決されている。

versionを上げるにはpackage.jsonにて以下のように記述して、yarnを実行する。

package.json
"resolutions": {
  "**/**/fsevents": "^1.2.9"
}

これ以外の解決策で見つけたのは以下の通り

  • rm -r /node_modules => rm yarn.lock => yarn
  • yarn cache clean => yarn upgrade
  • yarn upgradeを実行すると解決するといったissueが多かった

fseventsに関する解決策の参考URI: https://github.com/yarnpkg/yarn/issues/5962

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

クリックイベントを createEvent で発火させると getSelection は取れない

ドキュメントで確認が取れたわけではないけれど、動作確認をするとそのように感じるのでメモ。

例えば以下のように createEvent で任意の座標をクリックさせるイベントを発火させる。そしてクリックした位置にあるテキストの Range オブジェクトを取得するために window.getSelection() を使う。が、これだと Range オブジェクトは取得できない。

const elm = document.getElementById('hoge')

elm.addEventListener('click', () => {
  const sel = window.getSelection()
  sel.getRangeAt(0) // error
})

const evt = document.createEvent('MouseEvents')
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 100, 200, false, false, false, false, 0, null)
elm.dispatchEvent(evt)

createEvent で発火されたイベントの isTrustedfalse になっている。つまりユーザ自身が能動的に起こしたイベントではない。推測だがセキュリティ的な問題で Range オブジェクトが取得できないのかもしれない。

回避策というか普通はこちら

わざわざ window.getSelection() を使わなくても caretPositionFromPoint / caretRangeFromPoint を使えば指定座標の Range オブジェクトが取得できる。

if (document.caretRangeFromPoint) {
  const range = document.caretRangeFromPoint(x, y)
  const elm = document.elementFromPoint(x, y)
} else {
  // Firefox は caretRangeFromPoint がないのでこちら
  const pos = document.caretPositionFromPoint(x, y)
  const elm = document.elementFromPoint(x, y)
  const range = document.createRange()
  range.setStart(pos.offsetNode, pos.offset)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む