20200228のJavaScriptに関する記事は26件です。

JavaScriptでフォントが存在するか確認する方法

指定したフォントが存在するか真偽値を返す関数。

function font_exists(fontName){
    const canvas = document.createElement('canvas').getContext('2d')
    const text   = 'abcdefghijklmnopqrstuvwxyz0123456789'

    canvas.font  = `72px monospace`
    const size1  = canvas.measureText(text).width

    canvas.font  = `72px ${fontName}, monospace`
    const size2  = canvas.measureText(text).width

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

nuxtでDOMException: Failed to execute 'appendChild' on 'Node'エラー

Failed to execute 'appendChild' on 'Node'

nuxt でこんな感じのエラーが出る
因みにdev では出ない。

DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.
    at Object.appendChild

原因

<template> タグ内に <body> が入ってると起こる
色々なエラーにつながるので原因究明がしづらい。注意。

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

Vue.js研修資料

この資料について

・2020年の新卒に向けた研修資料である
・目的はVue.jsへの導入
・プロゲートで【HTML・CSS・JavaScript】を一通り終えた後を想定して作った
・この講義の後は下のサイトのイントロコースをやってもらう
https://www.vuemastery.com/ 
イントロコース完了後にVueUIの Vuetifyについて講義を行う(キータで投稿予定)
https://vuetifyjs.com/ja/

Vue.jsとは

JavaScriptの3大フレームワークの1つ
左から順番にAngular・React・Vue.js
スクリーンショット 2020-02-28 21.58.54.png

利用している企業

Angular
 ・YouTube Google Udemy Nike PayPal
React
 ・Facebook Instagram Netflix Dropbox
Vue.js
 ・Xiaomi Alibaba DeNA LINE

情報の比較

Angular React Vue
開発元 Google+コミュニティ Facebook+コミュニティ コミュニティ主体
初期リリース日 2010/10(Angular.js) 2013/07 2013/12
機能 フルスタック ユーザーインターフェイス ユーザーインターフェイス
ルーティング 同梱 React Router Vue Router
適切な開発規模 中〜やや大 小〜中
エコシステム 多い 豊富 小〜中
学習コスト 高い やや低〜中
構文上の特徴 TypeScript JSX 単一コンポーネントファイルシステム

Googleトレンドのトピックの推移

スクリーンショット 2020-02-28 22.19.01.png

Vue.jsのメリット

学習コストが低い

公式ドキュメントがしっかりしている
日本語ドキュメントが多い

軽量なフレームワークである

基本機能に絞ることで簡単に作れる

DOM操作を自動的に行ってくれる

小中規模のWebサイトを作るのに向いている

SPA(Single Page Application)開発につよい

簡単かつシンプルで日本語ドキュメントもある!!→社内システムを作るのにちょうどいい

Vueの勉強の仕方

公式サイト:https://jp.vuejs.org/index.html

スクリーンショット 2020-02-28 22.28.55.png
勉強を始める前になぜ Vue.jsの動画を見ると感覚的に理解できる

有名な本

スクリーンショット 2020-02-28 22.28.14.png
amazon:https://www.amazon.co.jp/s?k=vue&__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&ref=nb_sb_noss_1

オススメのサイト:https://www.vuemastery.com/

スクリーンショット 2020-02-28 22.33.03.png
英語ではあるがイントロコースを終了する頃にはある程度理解できているはずである
https://www.vuemastery.com/courses/intro-to-vue-js/vue-instance

Vueを支えるシステム

コンポーネントシステム:部分ごとに切り分けてコードを作成することで使い回しが可能(ヘッダーとかサイドバーなど)
           リアクティブシステム:状態の変化を自動的に画面に反映する仕組み
レンタリングシステム:仮想DOMによる高速レンタリング
スクリーンショット 2020-02-28 22.34.54.png
この辺はおいおい理解すれば良い

基本のコード(オススメ作業場所:https://jsfiddle.net/)

hello.html
<!DOCTYPE html>
<title>始めてのVue.js</title>
<script src="https://unpkg.com/vue@2.5.17"></script>

<div id="app" ></div>

<script>
    new Vue({
        template: '<p>{{msg}}</p>',
        data: {msg: 'hello world'}
    }).$mount('#app')
</script>

<style>
p{background-color: red;}
</style>

vue.ファイル

app.vue
<template>
  <p class="message">メッセージ:{{ msg }}</p>
</template>

<script>
    export default{
        data(){
            return{msg: 'こんにちわ'}
        }
    }
</script>

<style>
    .message{background-color: red;}
</style>

vue端子を出力したい時は@vue/cli @vue/cli-service-globalが必要になる
$を除いたコマンドを順番にうてばおそらく使えるはず
$ npm install -g @vue/cli @vue/cli-service-global
$ vue–version
$ vueserve –o

用語について

フレームワークという単語自体は英語で「枠組み・構造」を意味しています。

つまり、JavaScriptでWebサービスやアプリを開発するうえで、
サーバーとの連携、ルーティング、データの送受信(CRUD)など、全体的な処理の流れを構造化するわけです。
そのため、フレームワークを使えば足りない部分だけを集中して開発すれば良いので、
とても効率が良いうえ複数人が一緒に作業しやすくなるメリットもあります。

SPAとは単一のページでコンテンツの切り替えを行うWebアプリケーションのことです。

Vue.jsはSPA(Single Page Application)開発に使われていることが非常に多いです。
SPAで開発すると、ページ遷移をする時はJavaScriptを使用してHTMLの一部を差し替えて切り替えます。
従来のWebページではページ遷移の時は全体を切り替える必要があったため、
SPAによって新しいUIの実現やスピードの向上が可能となりました。
パフォーマンスの良さから今は多くの企業でWebサイト・アプリケーションにSPAを採用しており、
SPA開発に使われるVue.jsの人気も高まっています。
DeNAやGMOペパボなど、大手IT企業の開発現場でもVue.jsは採用されています。

DOMとは「Document Object Model」の略

直訳すると、「ドキュメントを物として扱うモデル」になる。
プログラムからHTMLやXMLを自由に操作するための仕組みだ。
例えばブラウザに表示される文字の色を変更したり、大きくしたりと、Webページの見た目をプログラムで処理をしたい場合があるだろう、
しかし何もしていない状態のHTMLファイルではJavaScriptから手を出す事が出来ない。
そこでファイルの特定の部分に目印を付けて「この部分」に「こういう事をしたい」という処理を可能にするための
取り決めがDOMである

公開した目的

エンジニア経験6ヶ月で研修を任せてもらえたので、資料を作成した。
どうせ作った資料だからいろんな人に見てもらいたい
後、スーパーエンジニアにアドバイスもらえたらなんて、、、

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

vue cliのsass(scss)でglobを使ってインポートするためのメモ

プロローグ

都内某所にてvue cliを使ってWEBアプリケーションを作っていると、唐突に神1からの信託が下った。

「ん~…、CSSはBEMを使って書いてほしいんだなぁ~」2

私はその時何が起こったのか理解できなかった。

vueファイルってscoped使えるし、BEMで作るとテンプレートにclassたくさん書いて見づらくなるんでやらない方が良いと思うし、なんで必要なんですかね?と問いかけても明確な答えはなかった…。

globが欲しい

かつて、WEB制作会社(ブラック)で日夜働いていた時の私は、BEMに習いblockごとに作られたcssファイルを@importする作業に疲弊していた。
そんなときにsassでglobするgulpの設定を知った際には、初めてアッパー昇竜拳3が打てた時以来の感動がありました。

vue cliのsass(scss)でglobを使ってインポートする

そんなわけで、かなり需要が無いと思われるvue-cliでglobの方法です。

vue cliのsassはwebpackを使っていますので、node-sass-glob-importerをインストールします。

npm i node-sass-glob-importer

ポイントは、dart-sassもnode-sassのnode-sass-glob-importerで大丈夫なところです。4
dart-sass用の物をひたすら探し回ったのですが、これでOKだった…。

次に、vue.config.jsnode-sass-glob-importerの設定を書き込みます。

const globImporter = require('node-sass-glob-importer')

module.exports = {
  css: {
    loaderOptions: {
      scss: {
        sassOptions: {
          importer: globImporter()
        }
      }
    }
  }
}

なんとこれでvue-cliでもglobがつかえるようになります。

これが

@import './block/_aaa.scss';
@import './block/_bbb.scss';
@import './block/_ccc.scss';
@import './block/_ddd.scss';

こうなる

@import './block/**/*.scss';

便利!


  • この記事の物語はフィクションです
  • この記事を書いた人間はBEMが好きではないみたいなので、BEM LOVE!!!な人にはごめんなさいと言っておきます。

  1. 三波春夫でございます 

  2. 空の色は紫がいいんだなぁ~ 

  3. 個人差があります。人によっては崩撃雲身双虎掌…(ry 

  4. このnodeとは何のnodeなのだろうか? 

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

Javascriptのチートシート10選

  1. htmlcheatsheet.com/

https://htmlcheatsheet.com/js/

基本機能が一望できるので、端から写経していくだけでも、初心者レベルであれば勉強になります。
仕事でのコーディングの際に、記述方法をど忘れした時などにたまに助かるサイトです。

  1. websitesetup.org

https://websitesetup.org/javascript-cheat-sheet/

PDFをmarkdownでウェブで再現したサイトです。

初心者が勉強する時の内容としては、非常にまとめてくれていて便利です。

ただ、それぞれの関数の細かな意味がこれだけだと分かりにくいのと、網羅性が低く、内容に偏りがあるので、ビギナー向けのチートシートですね。

  1. javascript.pythoncheatsheet.org

https://javascript.pythoncheatsheet.org/

こちらも、初心者学習用のチートシートです。

熟練者でも基本をど忘れした時にチラッと見ると思い出せて便利かもしれませんね。
個人的には、リンクされているGithubでのreadmeが同じ事を書いているんですが、見やすいと思います。

  1. overapi.com

http://overapi.com/javascript

ザ・チートシートというインターフェイスですが、中身はMDNのリンク集です。

カテゴリ分けの分類や、MDNのリンク先にスニペットが掲載されているので、閲覧していると知らないこともたまに書いてあって勉強になりますね。

  1. ilovecoding.org

https://ilovecoding.org/blog/js-cheatsheet

PDFがダウンロードできるこのサイトですが、内容は初心者向けリファレンス本の感じです。

図解がされていて、非常に見やすい内容なので、学習には非常に有効ですね。

  1. codementor.io

https://www.codementor.io/johnnyb/javascript-cheatsheet-fb54lz08k

そもそもが、学習サイトの中の要素なので、初心者勉強用の教材的な内容になっています。

コーディングになれるための写経サイトと考えてもいいかもですね。

  1. courses.cs.washington.edu

https://courses.cs.washington.edu/courses/cse154/17au/cheat-sheets/javascript-cheat-sheet-v1.pdf

.eduというドメインから推測できるのですが、こちらも学習サイトの教材みたいですね。

pdfなのですが、内容が少なすぎて次ページもなく、サイトのリンク集に辿り付くことができません・・・

  1. debuggex.com

https://www.debuggex.com/cheatsheet/regex/javascript

正規表現に特化したチートシートです。
たまに必要になりますね、この手の情報。

  1. cheatography.com

https://www.cheatography.com/anas-95/cheat-sheets/javascript-object-methods/

Array,Math,Stiring,Number関数のチートシートです。
あると便利系ですね。

  1. one-pic

https://github.com/coodict/javascript-in-one-pic

マインドマップ風の1枚画像のチートシートです。

見ずらいのが難点ですが、よくまとまっていて印刷してデスクにおいておきたいヤツですね。

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

JavaScript のスコープを理解する

JavaScript のスコープを理解する

JavaScriptについておさらい
スコープとは、変数の有効範囲のことで、プログラムのどの場所から参照できるかを決める概念です。

スコープの種類
JavaScript のスコープには、グローバル変数とローカル変数の 2 種類あります。

グローバル変数 ローカル変数
関数の外(トップレベル)で宣言した変数 関数の中で宣言した変数, 関数の仮引数
プログラム全体から参照できる その関数の中でのみ参照できる
ブロックスコープは存在しない
Java などの言語では、if や for などの {} で囲まれたブロックごとにもブロックスコープがありますが、JavaScript には存在しません。 JavaScript でどうしてもブロックスコープを使いたい場合は、with 命令を使う方法や、無名関数を定義と同時に呼び出すなどの方法で、擬似的にブロックスコープを作ることは可能です。

補足 : let を使うとブロックスコープがつくれると教えてもらったのですが、ちゃんと調べてないので、調べたらここに追記します!

スコープの役割
同じ名前の変数が、意図せず競合することを避ける。
スコープが違う場合、同じ名前の変数であっても別物として扱われる。
ローカル変数の記憶領域は、関数の実行が終わり次第、破棄される。
グローバル変数はプログラムが終了するまで記憶領域を確保するため、関数内でしか必要ない変数までグローバル変数にした場合、無駄にメモリを消費することになる。
宣言時の var の有無
var を省略して変数宣言をした場合、関数内での宣言であっても、その変数はグローバル変数になってしまいます。 混乱を避けるため、変数宣言の var は必ずつけること。

ローカル変数の有効範囲
ローカル変数は「関数全体で有効」なので、同じ関数内であれば、変数宣言より前のコードからアクセスできます。 これをホイスティング(巻き上げ)と呼びます。

var scope = "Global";

function getValue() {
console.log(scope); // > undefined
var scope = "Local";
return scope;
}

console.log(getValue()); // > "Local" (ローカル変数を参照)
console.log(scope); // > "Global" (グローバル変数を参照)
getValue 関数の console.log(scope); は、変数宣言 var scope = "Local"; より前にありますが、ホイスティングによってグローバル変数の scope ではなく、ローカル変数の scope を参照しています。 ただし、変数宣言はホイスティングされますが、代入はされないために undefined が返っています。

ホイスティングとは、JavaScript の内部で、上記コードの getValue 関数を、下記のように書き換えているようなイメージです。

function getValue() {
var scope; // ホイスティングで関数宣言だけを先頭に移動
console.log(scope); // > undefined
scope = "Local"; // 代入は元の位置にそのまま残る
return scope;
}
Java などのブロックスコープがある言語では、できるだけスコープを小さくするために、必要になった場所で変数宣言をしますが、JavaScript にはブロックスコープがないので、関数内の先頭に変数宣言を持っていった方が、直感的なスコープと実際のスコープが一緒になるので分かりやすいです。

関数の入れ子
関数を入れ子にして定義すると、それぞれの関数ごとに独自のローカルスコープを持ちます。 内側と外側のローカル変数が同じ名前の場合は、内側のローカル変数が優先されます。

仮引数のスコープ
仮引数もローカル変数です。

var n = 0;

function incrementValue(n) {
n++;
return n;
}

console.log(incrementValue(n)); // > 1 (ローカル変数を参照)
console.log(n); // > 0 (グローバル変数を参照)
incrementValue 関数の仮引数は、グローバル変数と同じ名前ですが、incrementValue 関数内のみで有効で、グローバル変数に影響は与えません。 また、incrementValue 関数の外から変数 n を参照した場合は、グローバル変数を参照します。 スコープの外から、スコープの中の変数を参照することはできません。

同じ名前でも別の変数として扱われるというのはつまり、仮引数の名前を変えた下記のコードと同じことです。

var n = 0;

function incrementValue(m) { // n を m にするのと同じ意味
m++;
return m;
}

console.log(incrementValue(n)); // > 1 (ローカル変数を参照)
console.log(n); // > 0 (グローバル変数を参照)
引数で値を渡すということは、渡した先の関数のローカル変数に、渡した値をコピーしているようなイメージです。 よって、コピー先(ローカル変数)をいくら変更しても、コピー元(グローバル変数)に影響は与えません。

仮引数で参照型を渡したときのスコープ
参照型とは、値そのものではなく、その値があるメモリ上の番地(アドレス)が格納される変数のことです。 関数 function、配列 array などは参照型なので、これらの変数には実際のオブジェクトが格納されているわけではなく、別の場所にあるオブジェクトのアドレス情報が格納されているだけです。

引数で参照型を渡すということは、オブジェクトそのものをコピーしているわけではなく、アドレス情報をコピーしているだけなので、元々のオブジェクトはひとつのままです。

var ary = [1, 2];

function addValue(ary) {
ary.push(3);
return ary;
}

console.log(addValue(ary)); // > 1, 2, 3
console.log(ary); // > 1, 2, 3
仮引数で渡されたアドレスにあるオブジェクトの値を関数内で変更すると、グローバル変数からも同じオブジェクトを参照するので、どちらも値が変わっています。仮引数はローカル変数ですが、参照渡しの場合はスコープの話からは外れます。

まとめ
グローバル変数はプログラム全体から参照でき、ローカル変数はその関数の中でのみ参照できる。
ローカル変数は var の有無でスコープが変わるので、変数宣言の var は必ずつける。
ホイスティングによる混乱を避けるため、関数内の先頭に変数宣言をまとめて書く。
仮引数もローカル変数のスコープだが、参照渡しの場合は関数外から参照するオブジェクトにも影響を与えるので気をつける。

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

TypeScriptの練習でストック一覧にコメントできるChrome拡張を作った

TypeScriptの講座が一通り終了し、JavaScriptでのフロント操作の復習+TypeScriptの練習のため、
Qiitaのストック記事にコメントを付けられるGoogleChrome拡張を作ってみました。

ツール説明

qsc.png

Stockページのタイトル下に最長60文字のメモ書きが可能です。
カーソルが離れたタイミングで保存されるので上書き等に注意して下さい。

https://github.com/sena-v/type_test/releases/tag/QSC

使用技術,ツール
TypeScript / Webpack / localStorage

前提

・JavaScriptは業務システムのValidationとか、要素取得・文字挿入などのDOM操作をある程度やったくらい
・文法は一通り両方とも学んだけど、なにがわかって何が分からないか分からない
・JSフレームワークの学習をTypeScriptでやろうとしてるけどボリューム感的に簡単な物をなにか挟みたい

・記事ストックが溜まってきたけど、ストック理由の切り分けができていない(また見たい/取っておきたい)
なんかスキ!って思うとすぐストックしてしまうので数が多くてよくわからなくなってしまった
・理由があってストックしたような気がするけど思い出せない(なんかに使いたいと思ったけど・・・?)
・最近簡単なchrome拡張なら1hでできるみたいな記事読んだし、1日で作れるラインなら丁度いいかな?

じゃあ練習にもなるしストック記事にコメント付けられれば良くない?みたいな感じで作りました。

実装

let Target = document.querySelectorAll('article');

上記のquerySelectorで取ってきたNodeListOf<HTMLElement>を繰り返し処理します。

for (TargetDiv of Target) {

  TargetChildDiv = TargetDiv.children[1].children[2]
  ArticleNode = TargetDiv.outerHTML;

  UrlStartPlace = ArticleNode.indexOf('data-item-url') + 16;
  UrlEndPlace = ArticleNode.indexOf('data-secret') - 2;

  ArticleUrl = ArticleNode.slice(UrlStartPlace, UrlEndPlace)

  // 挿入するdiv要素を生成
  let newEle = document.createElement('input');
  newEle.setAttribute('Id', ArticleUrl) // divタグのIDが記事番号になる
  newEle.setAttribute('type', 'text')
  newEle.setAttribute('maxlength', '70')
  newEle.setAttribute('style', 'height:2em;width:610px;margin-bottom:3px;')
  newEle.addEventListener("blur", function () {
    // 記事Id(url)をKey、inputされた文字をvalueとしてLocalStorageに投げる
    let Id: string = this.id
    let InputData = this.value;
    localStorage.setItem(Id, InputData);
  });

  // R処理にlocalStorageを使用する
  MemoData = localStorage.getItem(ArticleUrl)!;

  // 取得できたメモデータをセットしinputを挿入
  if (MemoData) newEle.setAttribute('value', MemoData);
  TargetChildDiv.parentNode!.insertBefore(newEle, TargetChildDiv);
}

要素単体の処理は簡単ですが以下の流れになっています。
①取得してきた記事(HTMLElement)から記事のUrlを抜き出し、user名-記事Urlをidとしてinput要素を作成する
②input要素のblurイベントにlocalStorageへの保存処理を埋め込む( key: id, value: inputのvalue )
③現在の記事keyでlocalStorageからgetし、存在した場合メモデータをセットしinput要素を指定位置へ挿入

結局gitHubのリリースの書き方を調べたり、chrome.storage(後述)で詰まったり、記事を合間に書いたりして
実質10時間くらいの作業になりました。

困ったこと・つまった所

Object is possibly 'null'がめっちゃ出た

querySelector等でDOM要素を取得する処理について、実行するまで要素が存在するかわからないことから
トランスパイルを実行して初めてエラーが見つかりました(Lintツール変更したら先に出るのかな?)

今回は確実に存在するidをquerySelectorで取得する新規Elementを挿入する処理をする関係で
対応する要素が必ず存在するため、適宜Non-null assertion operatorを使うことで解決しました。

参考:TypescriptでgetElementByIdを使ったら「Object is possibly 'null'.」のエラーが出た時の対処方法

chrome.storageがちゃんと動かない(未解決)

chromeの機能でKey-value型を内部に保存する方法があり(chrome.storage)、設定次第でchromeアカウント内の
データ共有が可能なため導入したかったのですが実行時エラーを吐かず結果が返ってきませんでした。
参考記事を読んだところsync設定の利用制限が厳しい※1であったり、LevelDBで実装されているので別途学習が
必要な気がしたこと、そもそもプログラミングするPCでメモ保存して見れれば最低限いいよね※2 という
理由から、localStorageを選択しました。

※1:制限の範囲は今回の用途では問題ないレベルでしたが、今後PC間でデータを共有するツールを作る際に
  汎用性が高いFirebase等の処理を学習した方がタイムパフォーマンスが良いと思い採用をやめました。

※2:データ量が少ないと思うので最悪共有はInport/Outportできるようにすればいいかなと思ってます。

参考:ブラウザにデータを保存するlocalStorage(ローカルストレージ)の使い方

欲しい要素を探すためにObjectの型を考えながら実装する必要がある

検索してきたJS処理をTSに変換する場合、型とプロパティをより意識して実装する必要があります。
型の恩恵でJSと違いブラウザで実行する前にエラーに気付くことができたのでそこは良かったのですが、
prototypeにchrome.storageデバッグ時にデータが入っているのかいないのかを見る事に時間がかかりました。

参考:【初心者】ECMAScrip、TypeScript には class 定義 があるが、プロトタイプチェーンはおさえておこう

今回TypeScriptを使用し複雑すぎないDOM操作をすることで、型を(チェックしてくれるので)意識しすぎずに
要素のprototypeを意識するきっかけができたので課題ができて良かったかなと思っています。

また型を明示的に書いていないのでイマイチTypeScriptの恩恵が受けられているか分からないことと、
よりTypeScriptの強みを生かした書き方ができるようになることも今後必要かなと思いました。

作ってみて・今後やりたいこと

JavaScriptの処理をTypeScriptにそのまま持ってこれないときに困る、けどやっぱり型が便利

多くのDOM操作についてはJSでの情報がほとんどなので、querySelectorで取った要素が単数だと思っていたら
複数で、使おうと思っていた処理がindex指定しないと使えなかったとか、thisが想定していた結果と違うとか
取ってきた情報をそのまま使えない部分がありました。ただその際もやはりTypeScriptは便利で、
どこがなんの型なのでダメと具体的に教えてくれる分、スムーズに開発することができました。
以前Ecpilseでフロントの開発をした際は実行してみないと間違っている箇所がわからず苦労した経験があるので
実行前に色々判断してくれる、オブジェクトの型を意識しながら開発できるTypeScriptは有利に感じました。

TypeScriptの前にJavaScriptをもう少し滞りなく書けるようにしたい

良くも悪くも型システム+VScodeのlint、補完機能が優秀なのであまりJS自体を理解していなくても
それなりに進んでしまう所が多く、JSの記法を調べたりする時間が比較的長かったです。

おわりに

今後TypeScriptをやろうとする場合、流れを考えるとJavaScriptのチームに参画した上で
同意のもとで導入していくケースになると思うので、サーバーサイド知識も使えるNode.JS周りを勉強しながら
実用的はJavaScriptのコーディングをしていきたいと思います。

参考記事
外部に公開しないミニマムなchrome拡張機能を作るのは1時間も使わずにできる
TypeScriptで作るイマドキChrome拡張機能開発入門
図で理解するJavaScriptのプロトタイプチェーン

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

JavaScript 文字列中に変数展開できるテンプレート構文

var val = "JavaScript"
// 今までのやり方
var str1 = "Hello " + val + "!" // => Hello JavaScript!
// テンプレート構文
var str2 = `Hello ${val}!` // => Hello JavaScript!

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

amazon-qldb-driver-nodejsからQLDBを使う①(接続編)

QLDBとは

https://aws.amazon.com/jp/qldb/
Amazonが提供するフルマネージド型の台帳データベースです。
ブロックチェーンとは異なり中央集権で管理されます。

データに対する変更は全て記録され、後から確認可能なようです。
また、変更履歴が正確であることを暗号的に検証する機能を提供しています。
https://docs.aws.amazon.com/qldb/latest/developerguide/verification.html

中央集権からトランザクションの実行時にネットワーク参加者の合意を経る必要がないため、一般的なブロックチェーンベースのフレームワークより高いスループットが出るようです。

以上から、「データの信頼性やトレーサビリティを担保したいけど、分散型である必要はない」などの場合にとても魅力的な選択肢になりそうです。

amazon-qldb-driver-nodejsについて

プログラムからアクセスする場合は現状はJavaのdriverを使うのが主流なようです。
nodejs用のdriverも用意されており、今回はこちらを使ってQLDBにプログラムから接続してみます!
現在はまだpreviewで本番環境用途に使用するのは推奨していないようですので、ご注意下さい。

前提

今回のサンプルはAWSのコンソールで台帳およびいくつかテーブルを作成してある状態を前提とします。

筆者の環境では以下のチュートリアルで登録したデータ使って動作確認しました。
https://docs.aws.amazon.com/ja_jp/qldb/latest/developerguide/getting-started.html

チュートリアルは以下の記事が参考になりました。
https://qiita.com/yanyansk/items/586b7f1c86eca4352b44

IAMユーザのアクセスキーの作成

実装する前にQLDBにアクセスするための認証情報を作成する必要があります。
以下の手順でアクセスキーを発行して下さい。

サービス > IAM > ユーザ からユーザの追加を選択して下さい。

iam_1.png

任意のユーザ名を入力して下さい。アクセスの種類は「プログラムによるアクセス」にチェックを入れて下さい。

iam2.png

一旦はテストで使用するだけなので、ユーザグループなどは作成せず、ポリシーを直接アタッチします。

iam3.png

タグなどは特に必要がないので、ユーザーの作成まで完了して下さい。
作成完了の画面で表示される「アクセスキーID」と「シークレットアクセスキー」を控えて下さい。
driverからQLDBにアクセスする際にこの情報を使って認証します。

iam4.png

実装

driverや必要なモジュールのinstall

npm i amazon-qldb-driver-nodejs aws-sdk ion-js typescript

credential情報の編集

「credentials.json」などの名前で以下のファイルを作成してください。

{
  "accessKeyId": "${作成したアクセスキーID}",
  "secretAccessKey": "${作成したシークレットアクセスキー}"
}

認証部分の実装

認証情報の設定

作成したcredentialのjsonを使って認証します。

const AWS = require("aws-sdk");
AWS.config.loadFromPath("./credentials.json");

Credentialの確認

デバッグしやすいように、Credentialが正しく設定されているか確認するfunctionを追加します。

function checkCredential() {
  return new Promise((resolve, reject) => {
    AWS.config.getCredentials(function (err: Error) {
      if (err) {
        return reject(err);
      }
      console.log("Access key:", AWS.config.credentials.accessKeyId);
      console.log("Secret access key:", AWS.config.credentials.secretAccessKey);
      resolve();
    });
  })
}

メインフローの実装

メインの部分を実装していきます。

セッションの作成

regionはQLDBを作成したリージョンを設定してください。
PooledQldbDriverの第一引数には作成した台帳の名前を指定して下さい。

  const testServiceConfigOptions = {
    region: "{QLDBを作成したリージョン}"
  };
  const qldbDriver: PooledQldbDriver = new PooledQldbDriver("{作成した台帳の名前}", testServiceConfigOptions);
  const qldbSession: QldbSession = await qldbDriver.getSession();

台帳上のテーブルの確認

  for (const table of await qldbSession.getTableNames()) {
    console.log(table);
  }

動作確認

実装したコードの全文は以下になります。
こちらを動作確認してみます。

import { PooledQldbDriver, QldbSession } from "amazon-qldb-driver-nodejs";

const AWS = require("aws-sdk");
AWS.config.loadFromPath("./credentials.json");

(async () => {

  await checkCredential();

  const testServiceConfigOptions = {
    region: "{QLDBを作成したリージョン}"
  };
  const qldbDriver: PooledQldbDriver = new PooledQldbDriver("{作成した台帳の名前}", testServiceConfigOptions);
  const qldbSession: QldbSession = await qldbDriver.getSession();
  for (const table of await qldbSession.getTableNames()) {
    console.log(table);
  }
})().catch(err => {
  console.error(err);
});

function checkCredential() {
  return new Promise((resolve, reject) => {
    AWS.config.getCredentials(function (err: Error) {
      if (err) {
        return reject(err);
      }
      console.log("Access key:", AWS.config.credentials.accessKeyId);
      console.log("Secret access key:", AWS.config.credentials.secretAccessKey);
      resolve();
    });
  })
}
$ npx tsc main.ts
$ node main.js

Access key: xxxxxxxxxxxxxxxxxx
Secret access key: xxxxxxxxxxxxxxxxxx
VehicleRegistration
DriversLicense
Vehicle
Person

無事台帳上のテーブル名が表示されました!
次回は検索やデータの登録について書きたいと思います!

さいごに

ZEROBILLBANKでは一緒に働く仲間を募集中です。
ZEROBILLBANK JAPAN Inc.

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

Create.jsで親子関係のツリー図を描く

概要

管理画面などで、既存のリストから親子関係のツリーを設定するUIを作ってみたくてやってみる。

有料のライブラリなどは見かけますが、使用料が高い・・・。
また、そこまで高機能なものは必要なかったりしたのでシンプルにする。

前提条件

HTML5のCanvasを操作するCreate.jsのライブラリを使っています。

ファイル構成

ローカルで動作確認したかったのでdocker使ってます。
※ Github: https://github.com/reflet/flowchart-create.js

ファイル構成
├ docker
│  └ nginx
│     └ default.conf
├ src
│  └ html
│    ├ flowchart.js    <- ツリー図を描くスクリプト
│    └ index.html      <- サンプルページ
│
└ docker-compose.yml

サンプルコード

サンプルページのコードはこんな感じです。
create.js に依存しているので、それはCDNで読み込んでます

src/html/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Create.js - フローチャート</title>
    <style type="text/css">
        body { margin: 0; padding: 0; }
        canvas { background: WhiteSmoke; }
    </style>
</head>
<body>
    <canvas id="myCanvas" width="1024" height="800"></canvas>

    <script src="https://code.createjs.com/1.0.0/createjs.min.js"></script>
    <script src="flowchart.js"></script>
    <script>
        window.addEventListener("load", function(){
            var flowchart = new FlowChart('myCanvas');

            // 初期化 (初期データ)
            flowchart.init('佐藤家',[
              {label:'太郎', children:[]},
              ...
              {label: '花子', children: [
                {label: '奈々子', children: []},
                {label: '八重子', children: []},
              ]},
            ]);
        });
    </script>
</body>
</html>

flowchart.js のコードについては、下記を参照ください。
https://github.com/reflet/flowchart-create.js/blob/master/src/html/flowchart.js

ローカル環境起動

実際に、ローカルブラウザで、動かしてみます。

$ git clone https://github.com/reflet/flowchart-create.js.git .
$ docker-compose up -d
$ open http://localhost:8080

こんな感じになりました。

sample.png

追加してみる

「+」ボタンを押して子要素を追加してみる。

add1.png

ラベルを入力し、OKボタンを押すと無事に追加されました。

add2.png

削除してみる

「✗」ボタンを押して削除してみる。

remove1.png

無事に削除されました。

remove2.png

まとめ

カンタンではありますが、とりあえずツリー図を作ることができました。

ドラッグしたり、好きな位置に配置したりもできましたが、
下記のような問題があるため、自動配置するようにしてそのような機能ははずしました。

  • 作ったツリーを再度表示する際に個別に座標情報を保存する必要がある
  • 重なり合わないようにする計算が面倒だった
  • 実際に操作すると複雑で意外と使い難くかった

以上です。

参考サイト

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

スクリプト言語 KINX/基本編(2) - 制御構造

はじめに

前回の続き。過去の解説は以下を参照。概要は以下の初回記事「スクリプト言語 KINX(ご紹介)」を参照してください。

見た目は JavaScript頭脳(中身)は Ruby、(安定感は AC/DC)」なスクリプト言語 KINX。オブジェクト指向と C 系シンタックスで C 系プログラマになじむ触感 をお届け。

Kinx 基本編(2)- 制御構造

文・制御構文

文(ステートメント)として、宣言、代入、continuebreakreturnthrowyield、および制御構文として if-elsewhiledo-whileforswitch-casetry-catch-finally が、使用可能。if-else の接続はぶら下がり構文で使用する。

宣言文

var で宣言する。初期化子で初期化も可能、またカンマで区切って複数同時に宣言することも可能。

var a;
var a = 0, b = 100;

型を指定する場合は変数名の後に記述する。が、ここで指定された型は現時点で native でしか使用されていない。逆に native では指定しなければ全て int と見なされる。

var a:dbl;
var a:int = 0, b:dbl = 100.0;
native<dbl> test(a:dbl, b:dbl) {
    /* function body */
}

指定できる型は現状以下の範囲のみ。

  • int ... 整数。通常スクリプトの範囲では自動的に Big Integer へのプロモーションが行われるが、native 関数内では単にオーバーフローするので注意。
  • dbl ... 実数。intdbldblint へのキャストはサポートできていない。
  • native<type> ... type は復帰値のタイプ(int or dbl)。native< native<type> > とかは未サポート。

代入文

代入文は普通の式文。代入は右辺から評価される。

a = b = 10;

上記では b = 10 が先に評価され、その結果が a に代入される。

continue

ループ先頭に戻る。正確にはループ条件式の直前に戻る。ただし、for 文の場合は第三フィールド(カウンタ更新の部分、何て言うんだ?)の直前に戻る。

continue はラベル指定が可能。continue LABELLABEL の示すブロックの先頭(ブロックがループの場合は上記の場所)に制御が戻る。また、continue は if 修飾が可能。continue if (expression) の形で条件を指定することができる。

continue;
continue LABEL;

break

ループを抜ける。正確にはループ・ブロックの直後に進む。

break はラベル指定が可能。break LABELLABEL の示すブロックの末尾に制御が進む。また、break は if 修飾が可能。break if (expression) の形で条件を指定することができる。

break;
break LABEL;

return

関数を抜ける。正確にはスタックをクリアし、復帰値を設定して呼び出し元の次の命令に進む。

return のみを指定した場合は暗黙的に null が返る。また、return は if 修飾が可能。return expression if (expression) の形で条件を指定することができる。

また、関数が Fiber として定義されていたた場合、一旦リターンすると次の呼び出しで FiberException 例外が発生する。実は catch してもう一回呼ぶと再度最初から実行できるが、この仕様で良いのかはわからない。

return; // same as `return null;`
return expression;

throw

例外を送出する。例外システムは貧弱だが実用できないわけではない。

例外オブジェクトは type()what() というメソッドを持ち、型とメッセージを取得できる。がしかし型によって捕捉する例外を区別したりできない。キャッチしてから type() で確認する感じ。今のところ、SystemExceptionFiberExceptionRuntimeException というのがあるが、ユーザーが一般に投げられる例外は RuntimeException

型によって区別できた方が良いのかな。個人的には例外はあくまで「例外」であって、エラー処理が適切にできれば良いのだが、ご意見ご要望をお待ちしております。

また、throw も if 修飾が可能。throw expression if (expression) の形で条件を指定することができる。

ちなみに catch 節の中では throw 単独での利用が可能。この場合、catch した例外オブジェクトをそのまま再送出する。

throw;
throw expression;

yield

Fiber で一旦ホスト側に処理を戻すために使用。値を返すことも可能。ホスト側から再度 resume(args) 返ってきた値 args を受け取ることも可能。その際、引数は配列の形でまとまってくるので、個別に受信したい場合はスプレッド(レスト)演算子を使って以下のように受け取る。

[a, ...b] = yield;

上記の例では最初の引数を a で受け取り、残りの引数を配列として b が受け取る。また、yield も if 修飾が可能。yield expression if (expression) の形で条件を指定することができる。

通常は以下の形式。

yield;
yield expression;

Fiber#resume(args) の復帰値を受け取る場合は以下の形式。

var fiber = new Fiber(&{
    a = yield;                  // a = [10, 20, 30]
    [a1] = yield expression;    // a1 = 10
})
fiber.resume(); // first call.
fiber.resume(10, 20, 30);
fiber.resume(10, 20, 30);

尚、今後触れるつもりだが、&{...} はブロックを渡しているように見えて実際は &() => {...} と同じ意味。具体的には引数無しの無名関数オブジェクトを簡潔に表現できるようにしたもの。ブロックを渡しているように見えていいなと勝手に思ってこうしてみた。

if-else

if (expression) block else block の形で使用。複数条件を連続させる場合は以下のようにぶら下がり構文を使用する。

if (expression) {
    /* block */
} else if (expression) {
    /* block */
} else {
    /* block */
}

while

while は条件判断をループの最初で行うループ構造を示す。以下が例だが詳細は難しくないため省略。

while (expression) {
    /* block */
}

do-while

do-while は条件判断をループの最後で行うループ構造を示す。従って、必ず 1 度はループ・ブロックが処理される。詳細は省略。

do {
    /* block */
} while (expression);

for

for は「初期化」「条件式」「更新部」(それぞれ何て言うんだ?)の 3 つのフィールドを持つ制御構造。初期化部では var を指定して for ブロックのスコープ内だけで有効な変数の宣言が可能。詳細は省略。

for (initialize; condition; update) {
    /* block */
};

JavaScript には for-in というのがあり、サポートするか検討中。しなくていいかな(すぐには)。通常は Array.each() があるのでそれを使うのが良い。キー一式を取得する keySet() もある。

switch-case

switch-case は悪名高いフォールスルーだ。だが、C プログラマとしてはフォールスルーじゃないと逆に変な感じでムズムズする。想像してみよう。break が無いと逆に 「次に行く感」 を感じてしまうところに根本原因があると思う。ここは馴染んだ道具に合わせてフォールスルーだ。ちゃんと break 書こうぜ。

ちなみに C 言語同様、default は最後に置かなくてもいいんだ。さらに数値以外も case に書ける。こんな感じ。

var array = [1,2,3,4,5,6,7,8,9,10];
function switchTest(n) {
    switch (n) {
    case 1:     System.println(n); break;
    case 2:     System.println(n); break;
    case 3:     System.println(n); break;
    case 4:     System.println(n); break;
    case 5:     System.println(n); break;
    case 6:     System.println(n); break;
    case 7:     System.print(n, ", "); /* fall through */
    case 8:     System.println(n); break;
    case 100:   System.println(n); break;

    default:
        System.println("default");
        break;
    case array.length():
        System.println("array-length:%{n}");
        break;
    case "aaa":
        System.println(n);
        break;
    case "bbb":
        System.println(n);
        break;
    }
}
0.upto(100, function(i) {
    System.print("%{i} => ");
    switchTest(i);
});

尚、native では switch-case をサポートしていない。これはやればできるので、やってないだけ...(優先順位の関係で)。

ただ、今利用している汎用アセンブラみたいなライブラリだとジャンプテーブル化はできなさそう。x64 だけとかならできるんだが。

try-catch-finally

try-catch-finally は例外を扱うための構文。以下のように使用する。だいたい分かってもらえそうなので、詳細は省略。尚、catch (e)(e) は省略できない。最近の JavaScript では省略できるっぽいので、できるようにするか検討中。

try {
    /* block */
} catch (e) {
    /* block */
} finally {
    /* block */
}

native でもサポートしたが、実際の例外オブジェクトを投げることができない制約がある(Type Mismatch とか Divide By Zero とかがスローされる可能性があるのでソレ用)のと、スタックトレースが保持されないという制約がある。これらは何とかなりそうな気もするので、今後の検討課題。

おわりに

誰かが期待してくれるのかはさっぱり不明だが、ご意見・ご要望は随時募集中。色々考えよう。

今回も地道に★が増えるといいな、と宣伝。

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

【初心者】JavaScriptでカレンダーのプログラミングを解説

どうも、UT(@ut_1029)です。ブログ(UTの日常)の紹介です。

JavaScriptでカレンダーをプログラミングしたサンプルコードを紹介します。

JavaScriptでカレンダーのプログラミング

<html>
    <head>
        <meta charset="UTF-8">
        <title>JavaScript Programming</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    </head>
    <body>
        <h1>JavaScriptでカレンダー作成</h1>
        <div class="date-head"></div>
        <table class="table table-border table-hover table-sm">
            <thead class="thead-dark">
                <tr>
                    <th>Sun</th>
                    <th>Mon</th>
                    <th>Tue</th>
                    <th>Wed</th>
                    <th>Thu</th>
                    <th>Fri</th>
                    <th>Sat</th>
                </tr>
            </thead>
            <tbody>
            </tbody>
        </table>
        <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
        <script>
            var today = new Date();

            var year  = today.getFullYear();
            var month = today.getMonth() + 3;

            $('.date-head').html(year + "-" + month);

            var last = new Date(today.getFullYear(), today.getMonth() + 3, 0);
            var last_year  = last.getFullYear();
            var last_month = last.getMonth();
            var last_day   = last.getDate();

            for (var i=1; i<=last_day; i++) {
                var week = new Date(last_year, last_month, i).getDay();
                if (!week || i == 1) {
                    $('table').find('tbody').append('<tr>'+
                                                        '<td></td>' +
                                                        '<td></td>' +
                                                        '<td></td>' +
                                                        '<td></td>' +
                                                        '<td></td>' +
                                                        '<td></td>' +
                                                        '<td></td>' +
                                                    '</tr>');
                }
                $('table').find('tbody').find('tr').last().find('td').eq(week).html(i);
            }
        </script>
    </body>
</html>

解説は、ブログ(UTの日常)で!

【初心者】JavaScriptでカレンダーのプログラミングを解説 | UTの日常

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

Lambda+API Gateway+CloudFrontとVueでOGP画像の自動生成をする

Lambda+API Gateway+CloudFrontとVueを使ってフロントエンドのみでOGP画像の自動生成をしてみたので備忘録。

構成

まずVueでSVGを返すページを用意しておく。
Lambda側はchrome-aws-lambdaでスクリーンショットを撮って、base64で返すようにする。

よくあるLambda@Edgeを使ったダイナミックレンダリングを行いつつ、Edgeで返すMetaタグのog:imagetwitter:imageのURLへのアクセスがあったら、用意しておいたSVGページをLambdaでスクリーンショット撮ってAPI Gateway経由でpngにして返す、というちょっと面倒くさい構成。

バックエンド側でLambdaを起動させてスクリーンショット撮ってS3に保存とかでもよかったのだけど、今回はあくまでもアクセスがあったらOGP画像を返すようにしたかったので、こんな感じの構成にした。

VueでSVG生成

VueでSVGを生成するのはこちらの記事を参考にさせていただいた。
Vue.jsとFirebaseでOGP画像生成系のサービスを爆速で作ろう

<template>
  <div class="hello">
    <svg ref="svgCard">
      <text transform="translate(103.29 347.281)" fill="#e51f4e" font-size="29" font-family="HiraginoSans-W5, Hiragino Sans" letter-spacing="-0.002em">
        <tspan x="0" y="26">{{ data.content }}</tspan>
      </text>
    </svg>
  </div>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  name: 'Svg',
  data () {
    return {
      data: {}
    }
  },
  beforeMount () {
      this.fetchData(response => {
        this.data = response
      })
  },
  methods: {
    ...mapActions([
        'fetchData',
    ])
  },
}
</script>

svgタグの中にvueのデータを埋め込めるので、APIから持ってきたデータを表示できるようにする。
注意が必要なのが、svgタグではテキストを自動で折り返してくれないので、途中で切って配列にしてv-forで回すとかしないといけない。

あとはこのページをrouterに登録する。

import Svg from '@/views/Svg.vue'

Vue.use(VueRouter)

const routes = [
  ...
  {
    path: '/path/to/svg',
    name: 'Svg',
    component: Svg
  }
]

Lambda+API Gateway

次にスクリーンショットを撮るLambdaを作る。ほんとはserverless frameworkで作りたかったのだけど、serverlessで作ると何故かchromeが動いてくれなかったのと、API Gateway側の設定がイマイチ把握しきれなかったので、今回はコンソールでポチポチした。

Lambda
const chromeLambda = require("chrome-aws-lambda");

const defaultViewport = {
  width: 1200,
  height: 630
};

exports.handler = async event => {

  const browser = await chromeLambda.puppeteer.launch({
    args: chromeLambda.args,
    executablePath: await chromeLambda.executablePath,
    defaultViewport 
  });

  const sleep = (time) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve();
      }, time);
    });
  }

  const page = await browser.newPage();

  const url = "https://" + process.env.DOMAIN + "/path/to/svg";
  await page.goto(url, { waitUntil: "networkidle0" });
  sleep(1000)

  const buffer = await page.screenshot({ encoding: "base64", type: "png" });

  return {
    "statusCode": 200,
    "headers": {"Content-Type": "image/png"},
    "isBase64Encoded": true,
    "body": buffer
  };

};

Lambda側のソースコードはこんな感じ。chrome-aws-lambdaはLambda Layerを使わせてもらった。
https://github.com/shelfio/chrome-aws-lambda-layer#available-regions

API Gateway

API Gateway側は適当なリソースを作って、GETメソッドを用意する。

  • /path/to/svg - GET - 統合リクエストで上で作ったLambdaに繋いで、Lambda プロキシ統合の使用にチェックを入れる
  • HTTP リクエストヘッダーにAcceptを追加する
  • メソッドレスポンスのコンテンツタイプにimage/pngを追加する
  • APIの設定で、バイナリメディアタイプにimage/pngを追加する

APIキーや使用量プランは必要に応じて設定して、ステージにデプロイする。ここでは仮にprodステージにデプロイしたと仮定して進める。

これでcurlコマンドでAPI Gatewayを叩くと画像が返ってくるようになる。

curl -H "Accept: image/png" --output test.png https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/path/to/svg
CloudFront

この状態でブラウザでアクセスすると、Acceptヘッダーがリクエストに含まれないのでjsonが返ってきてしまう。CloudFrontを経由させることで、Acceptヘッダーを付けつつ、一度アクセスのあった画像はキャッシュしてもらえる。

まずDistributionsの作成してOriginを追加する。

Origin Domain Name: xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Origin Path: /prod
Origin Custom Headers: Accept: image/png

Behavior側はOrigin or Origin Groupで先ほど追加したOriginを選択する。

CloudFrontのデプロイが完了したら、CloudFrontのURL経由でブラウザで画像が表示されるようになる。

Lambda@EdgeでOGPタグ

Lambda@Edgeを使ったOGPの生成はこのあたりを参考に。
Lambda@EdgeでSPAのOGPを動的に設定する
SSRをやめる。OGP対応はLambda@Edgeでダイナミックレンダリングする。

今回は1個目の記事のような感じで、botからのアクセスだった場合はバックエンドのAPIにアクセスしてタイトルとかを整えつつ、上で用意した画像のURLを含んだOGPタグを生成して返すようにした。

botの種類はこのあたり。

const crawlers = [
  "Googlebot",
  "facebookexternalhit",
  "Twitterbot",
  "bingbot",
  "msnbot",
  "Slackbot",
  "Discordbot"
];

まとめ

とまあ、こんな具合でOGP用の画像を自動生成できた。あとは必要に応じてコールドスタート対策もしておきたいところ。
Serverless Frameworkで行うLambdaのコールドスタート対策

いつもながら先人の知恵や知識に感謝。

参考URL

https://scottbartell.com/2019/03/25/automating-og-images-with-aws-lambda/
https://codissimo.sinumo.tech/2019/12/27/serverless-puppeteer-with-aws-lambda-layers-and-node-js/
https://qiita.com/junara/items/5563ad7ee133ce736ed0
https://qiita.com/kodai-saito/items/9051d2b30a29c7d64f7d

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

GAS Drive関連メモ

Driveクラスをちょっとつかったのでメモ
やりたかったのは指定のファイルと同じフォルダに名前を変えてコピーをつくる、だけだったんだけど。
色々時間かかったので忘れないようにメモ
リファレンス大事だねぇ。

1.前準備

権限設定しておく(ここではDrive関連のエントリだけ記載
(スプレットシートなど他の操作をする場合はそちらのエントリも必要

appscript.json
{
  "oauthScopes": [
    "https://www.googleapis.com/auth/drive.readonly",
    "https://www.googleapis.com/auth/drive",
  ]  
}

2.ファイルの取得
上記の権限があれば以下でファイルオブジェクトが取得できる
Fileクラス
FILE_IDは開きたいDriveファイルのID

    let file = DriveApp.getFileById(FILE_ID);

3.親フォルダの取得
GoogleDocはファイルが一つ以上のフォルダに所属しちゃうので、親フォルダをとろうとすると、フォルダリストのイテレータから探さないといけない
Folderクラス
Folderイテレータ

    let folderIterator = templateFile.getParents();
    while(folderIterator.hasNext()){
        let folder = folderIterator.next();
        console.log(floder.getName());
    }

4.コピーの作成
コピー自身は簡単

    let file = DriveApp.getFileById(FILE_ID);
    let parentFolder = DriveApp.getFolderById(FOLDER_ID);
    file.makeCopy(); // 純粋にコピー
    file.makeCopy(COPY_NAME); //COPY_NAMEで指定したファイル名でつくる
    file.makeCopy(COPY_NAME,parentFolder); //指定したフォルダに指定したファイル名で

5.おまけ
そういえば、ファイルコピーしたらスクリプトもコピーできてた
よく考えたら当たり前だけど。

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

ブラウザバックした際に、画面をリロードでうまくいかないとき。

備忘録的に残します。

ブラウザバックの際に、画面をリロードしたいって時がありまして。

window.onpageshow = function(event) {
    if (event.persisted) {
         window.location.reload();
    }
};

これでは、firefoxはできたけど、chromeとEdgeでは効かないって話でした。

なので、

window.onpageshow = function(event) {
    if (event.persisted || window.performance && 
            window.performance.navigation.type == 2) {
         window.location.reload();
    }
};

これで、できました。
めでたし、めでたし。

※ただし、ブラウザバックだけでなく、フォワードした際にも入る可能性がある(未検証)ので、他の制御はいるかも。

参考:元ネタはここです。

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

Bootstrapでモーダルを重ねる

【javascript】Bootstrapでモーダルを重ねたい!

普通にモーダル2つ表示しようとすると下になったりして対応していないのですが
z-index を設定してやれば動くようです。

環境: bootstrap3

$(function() {
    // Modal on Modal
    $(document).on('shown.bs.modal', '.modal', function (event) {
        var zIndex = Math.max.apply(null, Array.prototype.map.call(document.querySelectorAll('*'), function(el) {
              return +el.style.zIndex;
            })) + 10;
        $(this).css('z-index', zIndex);
        setTimeout(function() {
            $('.modal-backdrop').not('.modal-stack').css('z-index', zIndex - 1).addClass('modal-stack');
        }, 0);
    });
});

削除確認用のモーダル・例

image.png

呼び出し方・例

呼び出し部分

<a data-toggle="modal" data-id="" href="#deleteConfirmModal" class="openDeleteModal"><button type="button" /></a>

重ねるモーダルの部分

  <div id="deleteConfirmModal" class="modal fade">
      <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-body">
              <p>確認</p>
              <div class="alert alert-success hide" role="alert" id="messageSuccessConfirmModal" style="word-break: break-all;"></div>
              <div class="alert alert-danger hide" role="alert" id="messageErrorConfirmModal" style="word-break: break-all;"></div>
            </div>
            <div class="modal-footer"id="updateConfirmModalButtons">
              <button type="button" data-dismiss="modal" class="btn">キャンセル</button>
              <button type="button" class="btn btn-primary"
                id="deleteConfirmModalButton" data-id="" data-loading-text="Setting...">削除</button>
            </div>
            <div class="modal-footer hide" id="completeConfirmModalButtons">
              <button type="button" class="btn btn-default"
                data-dismiss="modal" onclick="location.reload(true);">リロード</button>
            </div>
        </div>
    </div>

以上、お疲れさまでした。

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

Vue.jsを使って簡単な計算ツールを作った

はじめに

グランブルファンタジーというゲーム内のイベントである古戦場にて、目的の貢献度を貯めるためにはどれくらいのトリガーが必要か?を計算するツールを作りました。
作り自体は簡単なため、初めてVue.jsを勉強&アウトプットするにはいい題材でした。

実際に動かしたもの
http://ayama.main.jp/battlefieldRun.html

コード
https://github.com/guranytou/battlefieldRuns

一番困った部分

battlefieldRun.html
        <fieldset class="inputFieldset">
          <div id="app">
            <div>
              <span>目標累計貢献度</span>
              <input type="text" v-model="input" id="contributionInput"></input>
            </div>
            <div id="trigerDigest">
              <div><span>肉の消化方法</span></div>
              <div>
                <input type="radio" class="hellRadio" name="hellRadio" id="hell95" value="Hell95" v-model="trigerDigest"/>
                <label class="hellRadioLabel" for="hell95">95HELL</label>
                <input type="radio" class="hellRadio" name="hellRadio" id="hell100" value="Hell100" v-model="trigerDigest"/>
                <label class="hellRadioLabel" for="hell100">100HELL</label>
                <input type="radio" class="hellRadio" name="hellRadio" id="hell150" value="Hell150" v-model="trigerDigest"/>
                <label class="hellRadioLabel" for="hell150">150HELL</label>
                <input type="radio" class="hellRadio" name="hellRadio" id="both" value="both" v-model="trigerDigest"/>
                <label class="hellRadioLabel" for="both">95&100HELL</label>
              </div>
            <div>
              <span>必要なトリガー数:</span><span>{{ output }}</span>
            </div>
          </div>
        </fieldset>
battlefieldRun.js
(function () {
   var triger = new Vue({
     el: '#app',
     data: {
       input: 0,
       trigerDigest: 'false'
     },
     computed: {
       output: function(){
           const Hell95NeedTriger = 10;
           const Hell100NeedTriger = 20;
           const Hell150NeedTriger = 20;
           const ExTriger = 4;
           const Hell95Contribution = 51500 * (Hell95NeedTriger / ExTriger) + 910000;
           const Hell100Contribution = 51500 * (Hell100NeedTriger / ExTriger) + 2680000;
           const Hell150Contribution = 51500 * (Hell150NeedTriger / ExTriger) + 3600000;
           let ans = 0;

           const ThisInput = String(this.input);
           const InputContribution = removeComma(ThisInput);

           function removeComma(value) {
             var num = value.replace(/,/g, "");

             return parseInt(num);
           }

           if(this.trigerDigest === 'Hell95'){
            // 計算式
            ans = Math.floor(InputContribution / Hell95Contribution * Hell95NeedTriger);
            return ans.toLocaleString()
           }else if(this.trigerDigest === 'Hell100'){
            // 計算式
             ans = Math.floor(InputContribution / Hell100Contribution * Hell100NeedTriger);
             return ans.toLocaleString()
           }else if(this.trigerDigest === 'Hell150'){
             ans = Math.floor(InputContribution / Hell150Contribution * Hell150NeedTriger);
             return ans.toLocaleString()
           }else if(this.trigerDigest === 'both'){
            // 計算式
             ans = Math.floor((InputContribution / 2 / Hell95Contribution * Hell95NeedTriger) +  (InputContribution / 2 / Hell100Contribution * Hell100NeedTriger));
             return ans.toLocaleString()
           }else{
             return 0;
            }
         }
     }
   })
}());

振り返り

・カンマをinput/outputにいれるのに地味に困った
→ここは本題ではなかったので、今回はinputに外部のJQueryを利用、outputにはtoLocaleString()を利用した
・computedとmethodsのどちらを利用すればいいのか分からなくて困った
→入れたらすぐに発火するものが作りたかったのでcomputedを採用。またcomputedとmethodsの違いの勉強が出来てよかった
・必要な部分だけさらっと勉強して書き始めたので、まだまだVue.jsへの理解が足りないように感じた
→座学もちゃんと増やす。簡単な題材のアウトプット(FizzBuzz、ToDoリスト作成etc)をする。

・疑問点:Computed内でif文の分岐をしている例文が見当たらなかったが、他の書き方があったりするのだろうか

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

【JavaScript】ElementUIのFormComponentを使用したフォームでリクエスト送信時、二重送信されないように画面をロックさせる

フォーム送信時はユーザーに操作させたくない

フォームの送信ボタンを押したときは二重にリクエストが飛ばないようにボタンを押せなくしたり、そもそも画面の項目に触れてほしくないので画面をロック状態にするようにしたい

以下は過去行ってた二重送信対応策(jQuery)
【JavaScript】フォーム等の送信ボタンに対する二重送信防止対応策 - Qiita

ElementUIのコンポーネントたちを使用して実現させる

今回はElementUI(Vue.jsのコンポーネントライブラリ)のFormコンポーネントloadingコンポーネントを組み合わせて、フォームのリクエスト送信時に画面をロックさせる

サンプル

See the Pen loading form deactivate By ElementUI by sola-msr (@sola-msr) on CodePen.

対応部分

サンプルではv-loading.fullscreen.lockをボタン部分に追加し、送信時にローディング描写をON(true)、送信完了(正常系レスポンス受け取り時)にOFF(false)をセットしています。
上記の対応だけではまだ送信ボタンを押下することが出来る状態ですので、追加の対応としてフォーム全体を非活性化(disabledさせるように、el-form部分にdisabledを追加し、これも送信時と送信完了時にON/OFFさせるようにしています。

<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="120px" class="demo-ruleForm" :disabled="this.disabledForm">
  <el-form-item label="Name" prop="name">
    <el-input type="text" v-model="ruleForm.name" autocomplete="off"></el-input>
  </el-form-item>
  <el-form-item label="Password" prop="pass">
    <el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>
  </el-form-item>
  <el-form-item label="Confirm" prop="checkPass">
    <el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input>
  </el-form-item>
    <el-form-item label="Activity zone" prop="region">
    <el-select v-model="ruleForm.region" placeholder="Activity zone">
      <el-option label="Zone one" value="shanghai"></el-option>
      <el-option label="Zone two" value="beijing"></el-option>
    </el-select>
  </el-form-item>

  <el-form-item>
    <el-button type="primary" @click="submitForm('ruleForm')" v-loading.fullscreen.lock="fullscreenLoading">Submit</el-button>
    <el-button @click="resetForm('ruleForm')">Reset</el-button>
  </el-form-item>
</el-form>

methodsで変数fullscreenLoadingdisabledFormtrue/falseをセットする関数(openFullScreen()closeFullScreen())を定義し、submitForm関数の中で各々のタイミングで呼び出すようにしてます。

// 略
    methods: {
      openFullScreen() {
         this.fullscreenLoading = true;
         this.disabledForm = true;
      },
      closeFullScreen() {
         this.fullscreenLoading = false;
         this.disabledForm = false;
      },
      submitForm(formName) {        
        this.$refs[formName].validate((valid) => {
          if (valid) {

            // ローディング描写とフォーム非活性化
            this.openFullScreen();

            // イメージしやすいようにあえて3秒間ローディング描写させるようにしてます
            setTimeout(() => {
              alert('submit!');
              // ローディング描写とフォーム非活性化をそれぞれ解除
              this.closeFullScreen();
            }, 3000);

          } else {
            // ここでもcloseFullScreen()を実行したほうがいいかもしれません
            console.log('error submit!!');
            return false;
          }
        });
      },
// 略

おわり

  • ローディング描写中にさらにプログレスバーを表示させる方法を知りたい(切実)

参考URL

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

json-editorを使ってJSON Schemaから入力フォームを生成する処理サンプル

<!DOCTYPE html>
<!-- saved from url=(0034)http://jeremydorn.com/json-editor/ -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <title>JSON Editor Example</title>

    <!-- placeholders for the theme switcher -->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <!-- <link rel="stylesheet" id="theme_stylesheet" href="./aaajsoneditor_files/bootstrap-combined.min.css"> -->
    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">

    <!-- <style>[class*="foundicon-"] {font-family: GeneralFoundicons;font-style: normal;}</style> -->
    <!-- <script src="./aaajsoneditor_files/jsoneditor.min.js"></script> -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/json-editor/0.7.28/jsoneditor.min.js" integrity="sha256-51+oMmpgSgS4jV5/DcGKnDHIOL6Jeie2i7ka6sPQVro=" crossorigin="anonymous"></script>

</head>
<body>
<div id="editor"></div>
<script>
(function() {
    var schema;
    if(!schema) {
        schema = {
            title: "Person",
            type: "object",
            properties: {
                name: {
                    type: "string",
                    description: "First and Last name",
                    minLength: 4,
                    default: "Jeremy Dorn"
                },
                age: {
                    type: "integer",
                    default: 25,
                    minimum: 18,
                    maximum: 99
                },
                favorite_color: {
                    type: "string",
                    format: "color",
                    title: "favorite color",
                    default: "#ffa500"
                },
                gender: {
                    type: "string",
                    enum: ["male", "female"]
                },
                location: {
                    type: "object",
                    title: "Location",
                    properties: {
                        city: {
                            type: "string",
                            default: "San Francisco"
                        },
                        state: {
                            type: "string",
                            default: "CA"
                        },
                        citystate: {
                            type: "string",
                            description: "This is generated automatically from the previous two fields",
                            template: "{{city}}, {{state}}",
                            watch: {
                                city: 'location.city',
                                state: 'location.state'
                            }
                        }
                    }
                },
                pets: {
                    type: "array",
                    format: "table",
                    title: "Pets",
                    uniqueItems: true,
                    items: {
                        type: "object",
                        title: "Pet",
                        properties: {
                            type: {
                                type: "string",
                                enum: ["cat","dog","bird","reptile","other"],
                                default: "dog"
                            },
                            name: {
                                type: "string"
                            }
                        }
                    },
                    default: [
                        {
                            type: "dog",
                            name: "Walter"
                        }
                    ]
                }
            }
        }
    }

    // Divs/textareas on the page
    var $editor = document.getElementById('editor');

    var jsoneditor;

    var reload = function(keep_value) {
        var startval = undefined; // TODO ここに初期値を入れる。入れないとjsonschemaの初期値となる。

        if(jsoneditor) jsoneditor.destroy();
        jsoneditor = new JSONEditor($editor,{
            schema: schema,
            startval: startval,
            theme: 'bootstrap3'
        });
    };

    //JSONEditor.defaults.options.theme = "//maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css";
    // JSONEditor.defaults.options.theme = 'bootstrap3';
    // JSONEditor.defaults.options.object_layout = 'normal';
    // JSONEditor.defaults.options.show_errors = 'interaction';

    reload();
})();
</script>

</body></html>

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

WordPressが嫌で自作CMSに乗り換えたらいい感じになった、という話

今まで技術ブログをWordPressで運用していましたが、WordPressが嫌で自作CMSに乗り換えたら良い感じになったので、今回やったことを紹介します。

どんな自作CMSを作ったのか

自作CMSで一番やりたかったのは、Markdownでシンプルに記事投稿ができるようになること。
無駄なボタンやオプションを剥ぎ落として、とにかく記事執筆に専念できるようにしています。

スクリーンショット 2020-02-28 11.57.27.png

それとブログ部分については、WordPressではやれなかった色々な施策を行い、「PageSpeed Insights」で高スコアが出るようになりました。

(WordPress時のスコアをスクショしていなかった。。。)

スクリーンショット 2020-02-28 11.50.57.png

スクリーンショット 2020-02-28 11.51.37.png

実際に自作CMSで運用している「MooLog」を見ると分かりますが、気持ちよくページ遷移ができます。

WordPressで不満だったこと

もちろん、WordPressはユーザー数が多いこともあり、テーマやプラグインが豊富、レンタルサーバーでサポートされている等、WordPressを使う環境が揃っているのが魅力です。

ただ、WordPressで70点のブログを作るのは簡単にできるのですが、自分にとって90点、100点のブログ環境を作ろうとなると急にきつくなり、「別にWordPressである必要なくない?」となる欠点があります。

当初は自分でWordPressのプラグインやテーマを作ろうとしましたが、そのためにもWordPress独自のアーキテクチャだったりDB周りだったりを理解する必要があり、すごく面倒臭くなってしまいました。

どのように自作CMSを作ったのか

自作CMSで運用している環境は下記の通りです。

  • PHP 7.2.28
  • CodeIgniter 3.1.6
  • MySQL 14.14
  • Apache 2.4.6
  • CentOS 7 (vultrというVPSで運用)
  • git 1.8.3.1 (本番にリポジトリ作って自動デプロイ)

自作CMSは0から作るのではなく、CodeIgniter3というFWをベースに作りました。

CodeIgniter Web Framework

CodeIgniterは最近流行のLaravelほどがっつりと機能を持ったFWではないですが、高速に動くのに定評があるFWです。

ただ、デフォルトだとunittestとmigration周りの機能が弱いので、以下のオープンソースを利用して、テストをしやすくしたり、cliでmigrationできるようにしました。

kenjis/ci-phpunit-test: An easier way to use PHPUnit with CodeIgniter 3.x.

kenjis/codeigniter-cli: Cli for CodeIgniter 3.0

フロントエンドは、turbolinksを使って高速化を測っており、他にもwebpack4でjsファイルを圧縮したり、css,jsファイルを1つにまとめてリクエスト数を減らしています。

また、CMS部分ではjQueryをがっつり使っていますが、ブログ部分では素のJavaScriptで書いています。

自作CMSを運用してわかった欠点

とは言っても自作CMSにも欠点があり、当初は「Markdownが気持ちよく書ける、シンプルなCMS」を目指していたのに、いつの間にか「この機能も付けたそう」みたいなのが増えて、問題点とやるべきことがどんどん増えています。

例えば、「今月書く予定の記事TODOリスト」を作ろうとしたり、「テンプレート機能」を付け足そうとしたり、細かいバグの修正をしたり、結構時間が取られてしまいます。

今現在も修正を続けているのですが、徐々にWordPressっぽくなっており、「最初からWordPressで良かったのでは?」と思わなくもないです。

自作CMSを運用してわかったWordPressの良い点

自作CMSを運用して、改めてWordPressの偉大さを思い知らされました。(人間は何かを失った時に初めてその物の大切さに気づく、みたいな)

自作CMS+VPSで運用するとなると、基本的にCMSを自作する以外にもやることが多く、

  • PHPとかMySQLとかインストールする
  • サーバー周りのセキュリティの設定をする(fail2banとか)
  • ドメイン周りの設定(sslとか)
  • デプロイ周りの設定
  • Apacheのパーミッションとか設定とか

などのように色々やらないといけません。一方、WordPress+レンタルサーバーだと、PHPでHello Worldできなくてもボタン1つ押すだけで全部やってくれると言う凄まじい環境が揃っています。

最近はWordPressをdisってる意見が挙がっており、逆に静的サイトageが多いですが、今回で色々勉強になりました。

まとめ

自作CMSはいいぞ

WordPressもいいぞ

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

TTCアルゴリズムで看護師のアサインメント表を自動作成2

本テーマの今後の予定
・TTCアルゴリズムを採用した理由(今回)
・例題1. 4人の患者を4人の看護師で受け持つ場合
・例題2. 4人の患者を2人の看護師で受け持つ場合
・例題3. 35人の患者を35人の看護師で受け持つ場合
・本題. 35人の患者を7人の看護師で受け持つ場合、そして一般化へ

TTCアルゴリズムを採用した理由

色んなサイトで分かりやすく書かれているので、アルゴリズムそのものについての説明は省略。

本テーマでTTCアルゴリズムを採用する利点はいくつかあります。

・看護師は必ず患者を受け持つから。受け持たれなかったら大変です。よってDAアルゴリズムは敬遠。

・計算が、患者がn人いたらn回以内に完了するから。遺伝アルゴリズムはかっこいいですが、そこまでたくさん計算したくないのです。

・完璧な正解は必要ないから。急な検査や緊急入院、患者の状態変化、スタッフが他病棟にリリーフに行ったり休んだりと、勤務開始後も急遽スケジューリングを組み直さなければならないことは日常茶飯事です。自動作成されたアサインメント表は、あくまで組み合わせの提案に過ぎないものです。

ではまた。

全部書き終わったら、一個の投稿にまとめます。

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

nuxt generateで静的サイトをつくる時の開発環境について(ファイル監視など)

はじめに

皆さんこんにちは。
Nuxt.js、とてもいいですよね。煩雑な設定が少なくすぐに実装に取りかかれるので、アウトプット速度がすごく向上します。

今回は、nuxt generateコマンドで静的サイトをビルドする際の開発環境構築で感じた課題と、その解決方法について軽めの記事を書きたいと思います。

ツッコミどころや改善案などあればぜひコメントお願いします。

TL;DR

  • 単一ファイルコンポーネント等を使ったVueでの開発をしつつ静的なHTMLを書き出せるnuxt generateはとても便利で使い所も多いが、開発中のファイル監視と自動更新は自前で実装せねばならず、したとしても毎回generateをすることになるので時間がかかる。
  • 上記を解決するために、開発時は静的ファイルを生成せずnuxtコマンドで開発を進め、静的ファイルを生成したい時のみnuxt generateを叩くという開発方法を検討した。
  • その際、画像ファイル等の参照でハマった

nuxt generateについて

nuxt.jsそのものに比べ、こちらのコマンドは比較的新しい機能ということもあり知名度がやや落ちるかと思ったので、概要について触れておきます。

前述の通りnuxt generateは、nuxt.jsで実装した内容を、静的なHTMLとjsファイルとして書き出して、そのまま静的Webサイトとして使用できるようにしたAPIプロパティです。

これを使うメリットとして、クライアントサイドでの描画がなくなるためSEO上安心ということ、パフォーマンスがやや向上することがあげられます。

また個人的には、多言語サイトを構築する際に、SSRを実装しなくてもjsonから文言を取得して、手軽に各言語のHTMLを書き出せる点に大きなメリットを感じています。(nuxt-i18n使用時)

ディレクトリ構造自体は通常のnuxt開発と同じように、以下のような形で組めば、pagesの中身のvueファイルが全てHTMLで吐き出されるイメージです。

> components
> assets
> static
> pages
package.json
nuxt.config.js

nuxt generateの弱点

nuxt generateは、一括でファイルを生成するという機能である以上、変更箇所に応じて部分的にビルドをするような監視機能は備わっていません。

そこで、自前でsrcファイルの監視をして更新をしなければならないのですが、毎回nuxt generateを叩かなければいけないため、ビルドに多くの時間がかかってしまいます。

npm scriptsやその他ビルドツールであればscssが変更されればそこのみ、jsが変更されればそこのみビルドするといった形で細かく監視ができるので、その点においてはnuxt generateの弱点と言えそうです。

実装

解決案

そこで、開発時には通常のnuxtコマンドを使ってホットリロードサクサクナウヤングな開発をしつつ、実際に静的HTMLとしてテストしたい時のみnuxt generateを使えばいいんじゃないか、と思い当たりました。

そこで最初に書いたpackage.jsonは以下のような形です。

"scripts", {
  "dev": "nuxt",
  "build": "run-s mock:**",
  "build:clean": "rimraf ./dist/**",
  "build:gen": "nuxt generate",
  "build:browse": "browser-sync start --config bs-config.js"
}

npm run devコマンドでは単にnuxtコマンドを実行し、
npm run buildコマンドでは一旦distディレクトリを綺麗にしてからnuxt generateし、それが完了したらbrowser-syncでローカルサーバを立ち上げています。

つまづいたところ

当初assetsフォルダとstaticフォルダの違いがよくわかっておらず、画像等は適当にassetsに突っ込んでいました。

しかし、nuxt開発においてassetsファイルの参照は、

<img src="~assets/images/hoge.png">

のように記述しなければならず、そのままnuxt generateをしても参照ができません。
面倒だけどパス置換のスクリプトを挟むか・・・?とも思いましたが、よくよく調べるとstaticフォルダ内の参照は、/staticを省いて以下のようにかけるようです。(直接は関係ありませんがassetsの中身はwebpackでコンパイルするものを突っ込むようです)

<img src="/images/hoge.png">

しかも、nuxt generateでファイルが書き出されるdistフォルダにはしっかりとimagesフォルダができており、どうやらnuxt generateではstaticに全ての参照ファイルを突っ込むのが正解のようです。

余談

これで開発環境は構築できたのですが、よくみると、distフォルダの中のHTMLが想定通りのものになっていません。

//想定したもの
index.html
hoge.html

//書き出されたもの
index.html
hoge
|__index.html

これは、nuxt.config.jsでgenerateプロパティのsubFoldersをfalseにすると解決できました。

nuxt.config.js
export default {
  //省略
  generate: {
    subFolders: false
  },
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

axiosのcatchでerror objectの中身を見れない

問題

axiosのレスポンスを console.log() で表示した時
.then のレスポンスはうまくオブジェクトとして取れるのに
.catch のレスポンスは下記のように表示されてしまう

Error: Request failed with status code 422
    at createError (createError.js:16)
    at settle (settle.js:17)
    at XMLHttpRequest.handleLoad (xhr.js:59)

結論

catchのerrorオブジェクト?が文字列として表示されてしまっている。
なのでerror.responseと指定すればOK

axios.post('/test/create', this.formData)
  .then(response => { 
    console.log(response)
  })
  .catch(error => {
    console.log(error.response)
  });

or

axios.post('/test/create', this.formData)
  .then(response => { 
    console.log(response)
  })
  .catch(({response}) => {
    console.log(response)
  });

参考

https://katuo-ai.com/axios-error/
https://github.com/axios/axios/issues/960

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

花粉症対策デジタル医療相談Botの開発 GASでユーザー毎に飛散予測を定時プッシュ

概要

現在スギ花粉症患者さん向けに医療機関を受診しなくてもLINEで市販の医療用医薬品を使い分けることが出来るサービスを開発中です。

3月4日までCAMPFIREでテスト版ユーザー募集中です。花粉症の方は是非ご参加ください!
CAMPFIREのプロジェクトページはこちら
LINEで花粉症の重症度や最適な市販薬がわかるデジタル医療相談【アレルナビ】

以前のQiita記事はこちら
花粉症対策デジタル医療相談Botの開発 ユーザーIDと位置情報をFirestoreで管理

ユーザーが特定した地点のピンポイント花粉飛散予測を定時にプッシュする機能を開発中なのでその辺りをまとめました。

作成方法

1. 気象予測API
今回のアプリではこちらの気象APIを利用しています。
Lifesocket

2. プログラムを作成
気象データを取得する関数 getweather()

async function getweather(userlat,userlong) {

const BASE_URL = "***************************************";
let PATH = `/location/${userlat}/${userlong}`; //緯度経度
let url = BASE_URL + PATH + "?days=7"; //7日分取得の場合
let res = await axios.get(url, {
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
    "x-access-key": "***********************************"
  }
});
return res.data;
}

飛散予測をプッシュする関数pushpollen()

async function pushpollen() {
  //Firebaseから位置情報データを受ける    
  let locationsRef = db.collection('locations');  
  let alllocations = locationsRef.get()
    .then(snapshot => {
      snapshot.forEach(async doc => {
        console.log(doc.id, '=>', doc.data());
        let lineId = doc.data().line_user_id;
        let userlat = doc.data().latitude;
        let userlong = doc.data().longitude;        
        if (lineId == null) {
          console.log("IDなし")
        } else {
          let item2 = await getweather(userlat,userlong);
          console.log(item2);
          let url2;
          let [date, time] = item2.Daily[1].DateTime.split("T");
          if (item2.Daily[1].Index === 0) {
            url2 ="**********";
          } else if (item2.Daily[1].Index === 1) {
            url2 ="**********";
          } else if (item2.Daily[1].Index === 2) {
            url2 ="**********";
          } else if (item2.Daily[1].Index === 3) {
            url2 ="**********";
          } else if (item2.Daily[1].Index === 4) {
            url2 ="**********";
          }

          client.pushMessage(lineId, {            
            type: "template",
            altText: "This is a buttons template",
            template: {
              type: "buttons",
              thumbnailImageUrl: url2,
              imageAspectRatio: "rectangle",
              imageSize: "cover",
              imageBackgroundColor: "#FFFFFF",
              title: `${date}${item2.PinpointName}の花粉飛散情報`,      
              text: item2.Daily[1].IndexCommentary,
              defaultAction: {
                type: "uri",
                label: "View detail",
                uri: "**********"
              },
              actions: [               
                {
                  type: "message",
                  label: "花粉症の重症度を判定",
                  text: "判定"
                }
              ]
            }
          })          
        }
      });
    })
    .catch(err => {
      console.log('Error getting documents', err);
    });  
}

3. GASで定時実行
1.GASファイルを作成
Googleにアクセスしてログイン
Google App Script
新しいプロジェクトを選択
image.png

2.コード.jsに以下のように書く

function myFunction() {
UrlFetchApp.fetch("実行させたいURL/cron");
}

3.トリガーを追加
コード編集画面のメニューにある時計マークをクリック
image.png

pushしたい時間帯に設定
(何分おきや何時間おき、毎週何曜日のこの時間なども設定できます)
image.png

4. プログラムの書き換え
以下を追記して定時に飛散予測をプッシュする関数pushpollen()が実行されるようにする。

app.get("/cron", (req, res) => {
  pushpollen();
});

完成図

定時に花粉予測がプッシュされました。
IMG-1039.jpg

考察

非同期処理の理解が浅かったためユーザーID毎に異なる飛散情報をプッシュさせるまでにかなり苦労しました。定時実行に関してはGASを使うと無料で簡単にCronのように時間指定で任意のURLをリクエストできちゃうのでとても便利ですね。

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

Google Mapを「投稿画面」と「詳細画面」の2か所に実装してみた。

はじめに

こんにちは!現在、アウトプットの一環として個人開発を行っているんですが、エラーでボコボコにされてる今日この頃です!
という話は置いといて、本記事ではレビューの投稿機能にGoogleMapを表示させてみた話をしたいと思います!

実装したいこと

  • 投稿ページ(new)にて住所(または地名)を入力してもらう。
  • 投稿ページのGoogleMapにマーカーを落とす。
  • 詳細ページ(show)のGoogleMapを表示。投稿時に指定した場所にマーカーがある。

開発条件

Ruby 2.5.1
Rails 5.2.3
Haml・Sass記法

データベース

reviewテーブル

Column Type Options
title string null: false
description text null: false

Association

  • has_one :spot

spotテーブル

Column Type Options
address string null: false
latitude float null: false
longitude float null: false
review_id references foreign_key: true, null: false

Association

  • belongs_to :review

実装手順

それでは、実装です。実装手順は参考記事に則ってます。

APIの利用

まず、GoogleMapのAPIを取得する必要があります。そのため、下記のリンクにアクセスしてAPIのkeyを取得してください。発行されたkeyは後々使用します。

Google Maps Platform

今回、利用するAPIは次の2つです。

  • Maps JavaScript API
  • Geocoding API

この2つを有効に設定しておいてください。

gemのインストール

必要なgemをインストールします。

Gemfile
gem "gmaps4rails"
gem "geocoder"
gem "gon"
gem "dotenv-rails"

記入できたらbundle installをしてください。
それぞれの役割をしては

  • GoogleMapを簡単に作成できるgem "gmaps4rails"
  • 地名から緯度経度に変換できるgem "geocoder"
  • JSでcontrollerの変数を使えるようにするgem "gon"
  • GoogleMapAPIのkeyを隠すためのgem "dotenv-rails"

になります。gem無しでも実装はできるみたいですが、結構複雑みたいなのでgemを使用しました。

JSを導入

application.html.hamlを編集

application.html.haml
%head
  《中略》
  = include_gon
  = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
%body
  = yield
  %script{src: "https://maps.googleapis.com/maps/api/js?key=#{ENV["GOOGLE_MAP_KEY"]}&callback=initMap"}
  %script{src: "//cdn.rawgit.com/mahnunchik/markerclustererplus/master/dist/markerclusterer.min.js"}
  %script{src: "//cdn.rawgit.com/printercu/google-maps-utility-library-v3-read-only/master/infobox/src/infobox_packed.js", type:"text/javascript"}

%headにはgem "gon"を使えるようにするための記述をします。
また、%bodyにはJSを使うための記述をしています。はじめは%script%headに記述していましたが、GoogleMapが表示されないというエラーが起きたので%bodyの最後に記述しました。おそらくは読み込みの順番の問題かと思います。
ENV["GOOGLE_MAP_KEY"]には.envファイルに隠したAPIkeyを入れています。

.env
GOOGLE_MAP_KEY = "あなたが取得したkeyを記述してください"

underscore.jsを作成

その後、app/assets/javascripts下にunderscore.jsを作成してください。作成したファイルに次のリンク先のコードをコピペして貼り付けるらしい。

underscore.js

application.jsで読み込み設定

application.jsに次の記述を追記します。

application.js
//= require underscore
//= require gmaps/google

modelを編集

modelを次のように編集していきます。

review.rb
class Review < ApplicationRecord
  has_one :spot, dependent: :destroy
  accepts_nested_attributes_for :spot
end
spot.rb
class Spot < ApplicationRecord
  belongs_to :review

  geocoded_by :address
  after_validation :geocode
end

viewを編集

投稿ページを作成します。GoogleMapを表示させる記述以外は省略してます。

new.html.haml
= form_with(model: @review, local: true, multipart: true) do |f|
  .spot
    = f.fields_for :spot do |s|
      = s.label :address, "レビュー場所(Google Mapで検索)", class: 'spot__title'
      = s.text_field :address, placeholder: "スポットを入力", id: "address", class: 'spot__text'
    %input{onclick: "codeAddress()", type: "button", value: "検索する"}
    .map{id: "map", style: "height: 320px; width: 640px;"}

次に、投稿したレビューを表示させるページを作成します。

show.html.haml
.show
  .show__address
    = @review.spot.address
  .show__maps{id: "show_map", style: "height: 320px; width: 400px;"}

controllerを編集

controllerも編集しておきます。

reviews_controller.rb
def new
  @review = Review.new
  @review.spot.build
end

def create
  @review = Review.new(review_params)
  if @review.save
    redirect_to root_path
  else
    redirect_to new_review_path
  end
end

def show
  @review = Review.find(params[:id])
  @lat = @review.spot.latitude
  @lng = @review.spot.longitude
  gon.lat = @lat
  gon.lng = @lng
end

private

def review_params
  params.require(:review).permit(
    :title,
    :description,
    spot_attributes: [:address]
  )
end

showアクションで記述している

@lat = @review.spot.latitude
@lng = @review.spot.longitude
gon.lat = @lat
gon.lng = @lng

では、controllerで定義した@lat@lngの変数をJavaScriptでも扱えるように、それぞれgon.latgon.lngに代入しています。

JavaScriptの編集

いよいよJavaScriptでGoogleMapを表示させていきます。

googlemap.js
let map //変数の定義
let geocoder //変数の定義

function initMap(){ //コールバック関数
  geocoder = new google.maps.Geocoder() //GoogleMapsAPIジオコーディングサービスにアクセス
  if(document.getElementById('map')){ //'map'というidを取得できたら実行
    map = new google.maps.Map(document.getElementById('map'), { //'map'というidを取得してマップを表示
      center: {lat: 35.6594666, lng: 139.7005536}, //最初に表示する場所(今回は「渋谷スクランブル交差点」が初期値)
      zoom: 15, //拡大率(1〜21まで設定可能)
    });
  }else{ //'map'というidが無かった場合
    map = new google.maps.Map(document.getElementById('show_map'), { //'show_map'というidを取得してマップを表示
      center: {lat: gon.lat, lng: gon.lng}, //controllerで定義した変数を緯度・経度の値とする(値はDBに入っている)
      zoom: 15, //拡大率(1〜21まで設定可能)
    });

    marker = new google.maps.Marker({ //GoogleMapにマーカーを落とす
      position:  {lat: gon.lat, lng: gon.lng}, //マーカーを落とす位置を決める(値はDBに入っている)
      map: map //マーカーを落とすマップを指定
    });
  }
}

function codeAddress(){ //コールバック関数
  let inputAddress = document.getElementById('address').value; //'address'というidの値(value)を取得

  geocoder.geocode( { 'address': inputAddress}, function(results, status) { //ジオコードしたい住所を引数として渡す
    if (status == 'OK') {
      let lat = results[0].geometry.location.lat(); //ジオコードした結果の緯度
      let lng = results[0].geometry.location.lng(); //ジオコードした結果の経度
      let mark = {
          lat: lat, //緯度
          lng: lng  //経度
      };
      map.setCenter(results[0].geometry.location); //最も近い、判読可能な住所を取得したい場所の緯度・経度
      let marker = new google.maps.Marker({
          map: map, //マーカーを落とすマップを指定
          position: results[0].geometry.location //マーカーを落とす位置を決める
      });
    } else {
      alert('該当する結果がありませんでした');
    }
  });   
}

今回は、地図を表示させる場所が2か所あり、それぞれ別の場所をマップの中心にしたかったので、initMap関数内で「指定のidの有無」による条件分岐を設けることで対応しています。

JavaScriptについては知識が浅く、コメントアウトで解説した部分で解釈の違いがあるかもしれません。その際にはご指摘頂けると有り難いです。

おわりに

GoogleMapは多くのサイトで利用されているので、どのように実装すればいいかを多少なり知ることができて良かったです!GoogleMapAPIにはまだまだ使ってない機能が沢山あるので、時間があれば色々チャレンジしていきたいと思います!!

参考記事

gonを使ったRailsとJavascriptの連携について
ジオコーディング備忘録
RailsでGoogleMapを表示させる(gem 'gmaps4rails'の使い方)
Rails Google Mapを表示させる方法

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

Vue.jsを触る際に知っておきたい仮想DOMの話

記事の概要

昨今、様々なプロダクトにおいて使われているVue.js。
人気の理由の1つにリアクティブなブラウザの描画があげられます。

ブラウザの再描画を支えている技術が「仮想DOM」なのですが、Vue.jsからフロントエンドを触り始めた自分にとっては「そもそもDOMって何?」状態だったので、「仮想DOMとその背景」についての解説記事を書きました。

技術的な記事ですが、歴の浅いフロントエンドエンジニアがDOM周りについて最低限の知識を身に付けられる事を目的に大枠の理解を優先しましたので、ご了承ください。

そもそもDOMとは

仮想DOMについて知る前に、そもそもDOM(仮想DOMとの区別の為、今後はリアルDOMと呼びます)とはなんなのかを知りましょう。
リアルDOMとは「Document Object Model」の略で、JavaScript側からHTMLを操作する事の出来る仕組みであり、実際にブラウザで描画されているものです。

リアルDOMを操作する事によって、JavaScriptからHTMLを弄れるため、web上にて見た目の変化が可能になります。

リアルDOMの特徴

リアルDOMには以下の特徴があります。

・HTMLを階層構造として扱う(DOMツリー)
・各要素はノードと呼ばれる

イメージとして下記のHTMLの場合

<html>
 <header>
  <h1>タイトル<h1>
 </header>
 <body>
  <div>
   <p>内容</p>
   <button>送信</button>
 </div>
 </body>

IMG_2429.HEIC.JPG
リアルDOM上ではこの様な階層構造(DOMツリー)として扱います。またこの1つ1つの要素がノードです。
詳しくはこちらを参考にしてください。
https://kuroeveryday.blogspot.com/2018/11/difference-between-dom-and-node-and-element.html

リアルDOMのみの流れ

IMG_6102.JPG

1 HTMLドキュメント
2 リアルDOM(DOMツリー)
3 webページ
の順番で情報は流れていきます。

webページに変化を加えようとした際は、その都度htmlを解析して、DOMツリーを再構築します。
その為、webページへの変化が多いと、再描画まで時間がかかってしまいます。

仮想DOMとは

その名の通り、仮のDOMです。メモリ空間などで擬似的に作ったものです。
今までブラウザで持っていたDOMツリーをJavaScriptのオブジェクトとして扱い、差分のみをリアルDOMに与えてくれる役割を持ちます。

仮想DOMを用いた流れ

IMG_6103.JPG

仮想DOMを用いる事により、情報の順番は
1 HTMLドキュメント
2 仮想DOM
3 リアルDOM
4 webページ

といった形になります。

仮想DOM内部の仕組み

1 DOMツリーを新旧で2つ用意している
2 HTMLドキュメントが書き変わる
3 仮想DOMを再構築し、新旧DOMでの差分を検出する
4 差分のみをリアルDOMに反映する

仮想DOMによる恩恵

描画のスピードです!笑
仮想DOMの存在によって、リアルDOMでは差分飲みの読み込みで描画ができる為、ブラウザの再描画のスピードが格段に早くな利、リアクティブな変化にも耐えうるようになりました。

まとめ

Vue.jsでのリアクティブな変化は仮想DOMの働きによって実現されていました。
普段の業務でフレームワークを使う際に仮想DOMなどの様に、裏側の原理について必ずしも理解しておく必要はないと思いますが、現段階でなぜここまでVue.jsが広まっているのか?を知る一旦にはなりそうです。

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