- 投稿日:2020-09-10T23:29:43+09:00
フワッとわかった気になるElm入門
はじめに
Elm は、JavaScript に似た構文を持つ Web アプリケーションを作るための言語です。
本記事では HTML や JavaScript の例を交えながら、Elm についてフワッとわかった気になってもらうことを趣旨としてます。前提知識
- HTML と JavaScript を触ったことがある
- 変数や関数、配列、オブジェクトについて知っている
注意
Elm と JavaScript では構文が異なります。1 2
特に、関数定義と関数呼び出しでは、括弧とカンマでなく、スペースを使います。
また、return がないことも特徴です。JavaScriptfunction sum (x, y) { // 関数定義 return x + y; } sum(1, 2); // 関数呼び出し function puls1 (x) { return x + 1; } plus1(sum(1, 2))Elmsum x y = x + y; -- 関数定義 sum 1 2 -- 関数呼び出し plus1 x = x + 1; plus1 (sum 1 2)また、 Elm には JavaScript にない、型注釈というものがありますが、いつかまた説明しましょう。
sum : Int -> Int -> Int -- 型注釈 sum x y = x + y; -- 関数定義view 関数
まずは、以下のリンクを開いて、 -- VIEW 以下を見てみましょう
https://ellie-app.com/9WqRmw9Hkf9a145行目-- VIEW view : Model -> Html Msg view model = div [] [ button [] [ text "+" ] , div [] [ text "15" ] , button [] [ text "-" ] ]このコードは、カウンターのボタンとカウンターの値を表示するだけです。
試しにボタンを触っても何も起きません。view 関数で Web ページの見かけを作る
通常、Web ページに表示されるものは、HTML で記述されます。
例えば、ページのタイトル、次のページへのリンク、送信ボタン、など。しかし、Elm では HTML の代わりに view 関数で Web ページを作ります。
view 関数に見出しを追加する
view 関数の中の div 関数や button 関数と、HTML の div タグや button タグを見比べると、よく似ていることがわかります。
HTML<div> <button>-</button> <div>15</div> <button>+</button> </div>Elmdiv [] [ button [] [ text "+" ] , div [] [ text "15" ] , button [] [ text "-" ] ]試しに h1 関数で見出しを追加してみましょう。
-- VIEW view : Model -> Html Msg view model = div [] [ h1 [] [ text "Hello World" ] , button [] [ text "+" ] , div [] [ text "15" ] , button [] [ text "-" ] ]HTML の属性
HTML の要素の関数以外にも、HTML の属性の関数もあります。3
例えば class 関数や、id 関数、style 関数などです。HTML<h1 class="title" id="firstTitle" style="color:red">Hello, World</h1>Elmh1 [ class "title", id "firstTitle", style "color" "red" ] [ text "Hello World" ]Model
今度は、 -- MODEL 以下を見てみましょう
https://ellie-app.com/9WqRmw9Hkf9a118行目-- MODEL type alias Model = { count : Int } init : Model init = { count = 0 }Model で、Web ページの状態を作る
view 関数で、Web ページの見かけを作りましたが、このままではカウンターに表示されるのは常に同じ "15" という定数です。
この定数を変数にして、値を更新できるようにするために、Elm では Model を利用します。type alias Model = { count : Int }Model は、レコードという JavaScript のオブジェクトのようなものです。4
また、レコードの中の変数はフィールドと呼びます。この count というフィールドが、カウンターの値となります。
view 関数で Model から Web ページを作る
view 関数では、model という値を受け取っていました。
そして model.count でカウンターの値を取り出すことができます。-- VIEW view : Model -> Html Msg view model = div [] [ button [] [ text "+" ] , div [] [ text "15" ] , button [] [ text "-" ] ]view 関数の、
text "15"
の部分を、model.count に変えてみましょう。-- VIEW view : Model -> Html Msg view model = div [] [ button [] [ text "+" ] , div [] [ text (String.fromInt model.count) ] , button [] [ text "-" ] ]ちなみに String.fromInt 関数 は、数値 (Int) を 文字列 (String) に変換する関数です。5
また後で説明しますが Elm ではこのように、値の変換をキッチリとしなければなりません。init 関数で、Web ページの最初の状態を作る
Web ページにアクセスしたときの、Model の最初の値は、init 関数で決めることができます。
-- MODEL type alias Model = { count : Int } init : Model init = { count = 0 }試しに、最初のカウントを10からに変えてみましょう
-- MODEL type alias Model = { count : Int } init : Model init = { count = 10 }まとめ
- view 関数で Web ページを作る
- view 関数から表示を変化させたい値(Web ページの状態)を抜き出して Model を作る
- view 関数で Model から Web ページを作るようにする
- init 関数で Model の最初の値を作る
Msg と update 関数
次に、 -- UPDATE 以下を見てみましょう
https://ellie-app.com/9WqRVH9bQjma131行目-- UPDATE type Msg = Increment | Decrement update : Msg -> Model -> Model update msg model = modelMsg で「ユーザーが操作した、その次に起きること」を作る
画面のフォームに対して、ユーザーから何か操作があったとき、「その次に起きること」を表現してみましょう。
Msg は「ユーザーが操作した、その次に起きること」を表すものです。
今回は
- +ボタンがクリックされたとき、「カウントが増える」
- -ボタンがクリックされたとき、「カウントが減る」
の2つです。-- UPDATE type Msg = Increment | DecrementIncrement は「カウントが増える」ということを表し、Decrement「カウントが減る」ということを表しています。
さて HTML のイベントが起きたときに、Msg の値が通知されるようにしましょう。
-- VIEW view : Model -> Html Msg view model = div [] [ button [] [ text "+" ] , div [] [ text (String.fromInt model.count) ] , button [] [ text "-" ] ]-- VIEW view : Model -> Html Msg view model = div [] [ button [ onClick Increment ] [ text "+" ] , div [] [ text (String.fromInt model.count) ] , button [ onClick Decrement ] [ text "-" ] ]onClick 関数は、HTML のイベント属性である onclick を表しています。
そして、ここで通知された Msg の値が、次の update 関数に渡されます。
update 関数で「実際に何が起きるか」を作る
update 関数は、受け取った Msg に連動して「実際に何が起きるか」を表すものです。
具体的には、update 関数は、受け取った Msg によって分岐して Model を更新する関数です。それでは
- 受け取った msg が Increment ならば model.count を1増やす
- 受け取った msg が Decrement ならば model.count を1減らす
この2つの処理を書いてみましょう。-- UPDATE type Msg = Increment | Decrement update : Msg -> Model -> Model update msg model = case msg of Increment -> { model | count = model.count + 1 } Decrement -> { model | count = model.count - 1 }
case msg of
は JavaScript の switch 文のようなものです。6
msg が Increment のときと、Decrement のときで分岐することを表しています。また、
{ xxx | ~~ }
はレコードを更新する構文です。
例えば、{ model | count = model.count + 1}
は、 model.count + 1 を新しい model.count としたレコードを作る、という意味です。ここで作られた新しい Model が view 関数に渡り、Web の表示が更新されます。
まとめ
おめでとうございます!
ついに、カウンターが動くようになりました。
https://ellie-app.com/9WqWzcqp3nra1ここまでの手順をまとめてみましょう。
- view 関数からユーザーが操作した、その次に起きることを抜き出して Msg を作る
- view 関数で、HTML のイベントが起きたときに、Msg の値が通知されるようにする
- update 関数で、受け取った Msg によって処理が分岐し Model を更新する処理を作る
The Elm Architecutre
ここまでの一連の流れがつかめたでしょうか?
- init 関数で Model の最初の値を定めて、view 関数に渡される
- view 関数で 渡された Model によって Web ページが表示される
- ユーザーの操作があると Msg が通知され、update 関数に渡される
- update 関数で Msg によって処理が分岐し、 Model が更新されて view 関数に渡される
- 1.へ戻る
このようなサイクルを繰り返して、画面が動くようになりました!
これが The Elm Architecutre です。
そんなに難しくないでしょう?全体のまとめ
処理を作る流れ
- view 関数で Web ページを作る
- view 関数から表示を変化させたい値(Web ページの状態)を抜き出して Model を作る
- view 関数で Model から Web ページを作るようにする
- init 関数で Model の最初の値を作る
- view 関数からユーザーが操作した、その次に起きることを抜き出して Msg を作る
- view 関数で、HTML のイベントが起きたときに、Msg の値が通知されるようにする
- update 関数で、受け取った Msg によって処理が分岐し Model を更新する処理を作る
実際の処理の流れ
- init 関数で Model の最初の値を定めて、view 関数に渡される
- view 関数で 渡された Model によって Web ページが表示される
- ユーザーの操作があると Msg が通知され、update 関数に渡される
- update 関数で Msg によって処理が分岐し、 Model が更新されて view 関数に渡される
- 1.へ戻る
完成品
- 投稿日:2020-09-10T23:29:16+09:00
イベントハンドラー とイベントリスナーの違い(自分用メモ)
1.タグ内の属性として宣言する
2.要素オブジェクトのプロパティとして宣言する
3.addEventListenerメソッドを使って宣言する1.2がイベントハンドラー
3がイベントリスナーdocument.addEventListener('DOMContentLoaded',function(){
}, false);
内で定義されてるのが3のイベントリスナーってことにとりあえずしておく。
- 投稿日:2020-09-10T22:49:40+09:00
Kinx Tiny Typesetting - LaTeX 派? つか、知ってる?
Kinx Tiny Typesetting
こんにちわ。
今回は組版システムがメインです。TeX や LaTeXを使ってますか?それは良いですね。私はイマイマ 全く使ってません。好きですけど。
学生時代の論文書きには使ったものの、就職したら使わなくなってしまったあの懐かしくも美しいシステム、LaTeX。
この記事はそんな LaTeX に関係しつつ、私たちの Kinx に関連する、そんな内容です。
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。最近はだいぶ記事を書く時間がなく、生存確認的な記事ですが、ご容赦。書きたいことはいっぱいあるのですが。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
組版?
まずはこちらのPDFファイルからご覧ください。
https://github.com/Kray-G/kinx/blob/master/examples/typesetting/typesetting.pdf
これは Kinx 組版ライブラリによって生成されました。なんだそれ、というのが今回のお話です。
このライブラリはまだ完成しておらず、極めて初期の段階にありますが、割りとイケてる感じに出力できたので、勇み足風に紹介したくなってしまった、という記事でありんす。
なぜ作り始めたか
PDF ライブラリ実装したんですよ。libharu ラップして。でもですね、生の PDF 操作するツール作るのも、まああるとは思いますが、座標計算したり面倒なので、ルールにしたがって組版する仕組みはほしいですよね。
そう思って見渡すと、そういうのって TeX くらいですよね。あと SATySFi とか SILE とか見つけたんですが、SATySFi も SILE も Windows が弱点のご様子(SILE はビルドできたけど出力が正しくなかった…)。まあ、SILE はすごく良さそうでしたが、数式はまだ未対応。数式が必要か?は議論の余地はありますが、TeX っぽい感じ!を意識すると欲しいところ。
というわけで、またしても車輪の再発明に走りました。特長としては「スモールサイズで手軽に組版」。面倒な作業は一切なし。ここだけは守りたい。
LaTeX は不要(使ってない)
ちなみに LaTeX は使っていません。というと語弊があるかもしれませんね。実際、LaTeX 自体はインストールすらしてません。が、数式に関しては KaTeX 内蔵でゴニョゴニョしてます。
LaTeX システムは非常に巨大なので、優れたシステムだとは思いますが、ちょっと PDF 作りたいなー程度の要望にインストールするには本格的すぎる気がして。
Kinx のスモール・パッケージをインストールしたら「なんかそれなりのが付いてた!」、くらいがちょうどいい感じなので、そんな感じを目指してます。
おわりに
これはまだ完成してないシステムの紹介です。やることはまだたくさんあるので、これからです。目次とか Book Style とか。最終的には簡易マークアップからの変換がまず当面のゴール。基本 Markdown からは行ける感じで。
まだまだ発展途上の段階ですが、興味があったり応援してくださる方がいれば、ぜひぜひ Github スターください!やる気出します。(ただ、完成形はリポジトリ独立させるかも…)
同梱するとフォントだけで Kinx 本体のサイズ越えそうなので、別パッケージでアドオンできるようにしたいですね。パッケージ・マネージャーが必要だ。
ではまた次回!
- 投稿日:2020-09-10T22:23:27+09:00
javascript関数ドリル 初級編uniq関数の実相のアウトプット
uniq関数の課題内容
詳細はこちら
↓
https://js-drills.com/blog/without/uniq関数の取り組む前の状態
uniqメソットがどんなものか分からない状態
uniq関数に取り組んだ後の状態
自力でできた
uniq関数の実装コード(答えを見る前)
分からなった
if( !values.includes(candidateToPush) )が分からなかった
uniq関数の実装コード(答えを見た後)
function without(array, ...values) { const newArray = []; for(let i = 0; i < array.length; i++) { const candidateToPush = array[i]; // values : [1, 2] // array: [2, 1, 2, 3] // candidateToPush: 2, 1, 2, 3 if( !values.includes(candidateToPush) ) { newArray.push(candidateToPush); } } return newArray; } console.log( without([2, 1, 2, 3], 1, 2) ); //[3]
- 投稿日:2020-09-10T21:09:30+09:00
Three.jsでgltfを読み込んだ時に、カリングを有効にする。
カリングとは
面の片側のみ描画して負荷を下げる方法。
サイコロを現実世界に置いてみればわかると思うが、必ず6面あるうちの3面しか同時に見ることができない。そのため、裏側に位置する3面を描画しないようにする仕組みのこと。three.jsのデフォルトでは、materialから、表示するsideを指定することになる。
やり方
blenderで出力したものを読み込んだ時に、カリングがうまくONにならなかった。
多分loader側ではあんまりいじってないので、自前ですべてカリングを有効にしておこう。(new THREE.GLTFLoader()).load( 'model.glb' , gltf => { //キューにすべてのchildrenをいれる。 const targets = [...gltf.scene.children]; while(targets.length > 0) { let child = targets.pop(); for(let cc of child.children) { targets.push(cc); } if(child.type == "Mesh" && child.material) { child.material.side = THREE.FrontSide; } } });
- 投稿日:2020-09-10T20:29:21+09:00
javascript関数ドリル 初級編without関数の実相のアウトプット
zipObject関数の課題内容
詳細はこちら
↓
https://js-drills.com/blog/without/zipObject関数の取り組む前の状態
スプレッド演算子を使う発想がなかった。includeメソットを知らなかった
zipObject関数に取り組んだ後の状態
理解できた
zipObject関数の実装コード(答えを見る前)
分からなかった
zipObject関数の実装コード(答えを見た後)
function without(array, ...values) { const newArray = []; for(let i = 0; i < array.length; i++) { const candidateToPush = array[i]; // values : [1, 2] // array: [2, 1, 2, 3] // candidateToPush: 2, 1, 2, 3 if( !values.includes(candidateToPush) ) { newArray.push(candidateToPush); } } return newArray; } console.log( without([2, 1, 2, 3], 1, 2) ); // => [3]
- 投稿日:2020-09-10T20:18:47+09:00
Javascript range
function range(start, stop) { var array = []; var length = stop - start; for (var i = 0; i <= length; i++) { array[i] = start; start++; } return array; }console.log(range(1, 7)); // [1,2,3,4,5,6,7]
console.log(range(5, 10)); // [5,6,7,8,9,10]
console.log(range(-2, 3)); // [-2,-1,0,1,2,3]utils.jsに
export function range(start, stop) { const array = isNumber; const length = stop - start; for (let i = 0; i <= length; i++) { array[i] = start; start++; } return array; }
- 投稿日:2020-09-10T20:18:47+09:00
Javascriptにrangeを入れる方法
Nuxtjsプロジェクトにpythonのrange()のようなやつを入れたかったので以下のとおりで
utils.jsに
export function range(start, end) { /* generate a range : [start, start+1, ..., end-1, end] */ const len = end - start + 1; const a = new Array(len); for (let i = 0; i < len; i++) a[i] = start + i; return a; }使うときは range(26, 52)
そのままcomponentに追加する時は
range(start, end) {
/* generate a range : [start, start+1, ..., end-1, end] */
const len = end - start + 1
const a = new Array(len)
for (let i = 0; i < len; i++) a[i] = start + i
return a
}
})
- 投稿日:2020-09-10T20:00:14+09:00
コンポーネント間のデータのやりとりを簡単なTODOアプリでまとめる
Vuexに頼りすぎてコンポーネント間のデータの受け渡し方が曖昧だったので、簡単なサンプルアプリでまとめる
また、Vueで開発をしていてコンポーネントを細かく分けない事で以下の問題がよく起こったのでその反省
コンポーネントを分けない問題
・汎用性の悪さ
例えばフォームとボタンを一緒のコンポーネントに作ると、ボタンだけ使い回したい時にフォームまでついてきて汎用性が悪い。・同じような記述をしたコンポーネントファイルが増えDRYに反する
・1ファイルのコード量が増えて可読性が悪い
作成したコンポーネント
・ボタン
・入力フォーム
・ボタンと入力フォームをまとめたコンポーネントただ単にクリックイベントとボタン名を使いまわせるボタン
button.vue<template> <button @click="onClick">{{name}}</button> </template> <script> export default { props:{ name:{ type:String, default:"button" }, onClick:{ type:Function, required:true } }, } </script>入力フォーム。
propsの値はv-modelで直接変更するとエラーになる
computedでv-modelの変更を検知し
親コンポーネントへ入力された値(input)を送り、親側でpropsの値を更新するInput.vue<template> <input type="text" v-model="input"> </template> <script> export default { props:{ inputValue:String }, computed:{ // v-modelのinputの変更を検知 input:{ get(){ return this.$props.inputValue }, // 親コンポーネントにinputを送り出して親側でpropsの値を書き換える set(value){ this.$emit("setValue",value) } } } } </script>ボタンと入力フォームが存在するコンポーネント
入力フォームの値をボタンクリックで配列に格納し親コンポーネントへ渡すForm.vue<template> <div> <!-- $emitで渡ってきたイベントを実行 --> <!-- 子コンポーネントのpropsに$emitの引数で受け取った値をセット --> <Input @setValue="setValue" :inputValue = value /> <!-- Buttonコンポーネントのイベント発火とボタン名を設定 --> <Button :onClick="postTodo" name="add" /> </div> </template> <script> import Button from "@/components/Button.vue" import Input from "@/components/Input.vue" export default { data(){ return{ value:null, todos:[] } }, components:{ Button, Input }, methods:{ postTodo(){ this.todos.push(this.value) this.value="" //todoが追加された配列を親コンポーネントへ渡す this.$emit("setTodo",this.todos) }, setValue(value){ this.value=value } } } </script>Form.vueから受け取った配列todosをv-forでレンダリング
App.vue<template> <div id="app"> <h1>Todo</h1> <Form @setTodo="setTodo" /> <hr> <template v-for="(todo,index) in todos"> <li :key="index">{{todo}}</li> </template> </div> </template> <script> import Form from "@/components/Form.vue" export default { name: 'App', components: { Form }, data(){ return{ todos:null } }, methods:{ setTodo(todos){ this.todos=todos.reverse() }, } } </script>まとめ
・v-modelの値を渡したい時はcomputedで変更を検知して$emit経由で値を渡して親側からpropsを更新する
・$emitでどんどん親へ親へ渡していく。
・兄弟のデータを使う時は親のdataに保管したものを使う
- 投稿日:2020-09-10T19:22:38+09:00
株価指数の下落率に応じてLINE通知する。
積立投資をしているのですが、暴落時にはプラスで積立するのがより効率が良いです。
とある動画で、S&P500が先週対比にて-5%以上の時は買い増すチャンスということを知り、
S&P500の下落率が一定の基準値以下になった場合は、LINE通知できる様に実装しました。前提:
・実行環境:GAS
・LINE notify にてTOCKEN取得済み。
LINE通知等で検索すると、LINETOCKENの取得方法等確認が出来ます。
・S&P500の値は下記APIを利用
https://financialmodelingprep.com/developer/docs/
*1仕様:
S&P500の終値が1週間前に比べて4%以上落ちていたらLINEに通知する。実際のコード
//発行したtockenを利用 var lineToken = "***************"; function main() { //発行したAPIkeyを利用 var apiUrl = "https://financialmodelingprep.com/api/v3/historical-price-full/%5EGSPC?apikey=**************"; var apiOptions = { method : 'get' }; var responseApi = UrlFetchApp.fetch(apiUrl, apiOptions); var responseJson = JSON.parse(responseApi.getContentText()); var yesterDay = responseJson.historical[0].date var yesterDayClosePrice = responseJson.historical[0].close var lastWeekDay = responseJson.historical[4].date var lastWeekDayPrice = responseJson.historical[4].close const declineRation = ((yesterDayClosePrice/lastWeekDayPrice)-1)*100; const disPlayRation = Math.round(declineRation * 10) / 10 //if (declineRation <= -4) { var message = "チャンス! \n S&P500 の対先週終値が " + disPlayRation + ' % ' +'です!' + '\n 先週: '+ JSON.stringify(lastWeekDay) + ' 終値 :' + JSON.stringify(lastWeekDayPrice) + '\n 先日: '+ JSON.stringify(yesterDay)+ ' 終値: ' + JSON.stringify(yesterDayClosePrice); console.log(message); sendToLine(message); //} } function sendToLine(message) { var token = lineToken; var options = { "method": "post", "payload": {"message": message}, "headers": { "Authorization": "Bearer " + token }, "muteHttpExceptions" : true, }; UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options); }*1
公式サイトにある様に無料で叩ける回数が250回まで。
https://financialmodelingprep.com/developer/docs/pricing/困った点
困った点が一番が無料でS&Pの終値提供APIが全く見つからなかったことです。
有名どころだと
・Quandl
・alphavantageあたりですが、提供しているのが、月毎だったり、そもそも提供していなかったり。。。。
個別株は簡単に見つかるので需要があまり無いんですかね。。。
週に一度の計算のため1.2年は無料回数範囲ですが、
超える前に良いAPI見つけておきたいです。ちなみに、ライブラリを使うのが一番簡単ですね。
今回はGASを利用したかったので、APIでデータ取得できるものを探していましたが、
Python×herokuでも断然ありだと思います。
- 投稿日:2020-09-10T17:47:23+09:00
javascript関数ドリル 初級編zipObject関数の実相のアウトプット
zipObject関数の課題内容
詳細はこちら
↓
https://js-drills.com/blog/zipobject/zipObject関数の取り組む前の状態
関数は断片的にはわかっているが、どんな関数の種類などわからない状態
zipObject関数に取り組んだ後の状態
オブジェクトに文字列を入れるときは []になることを忘れていたので思い出せてよかった
zipObject関数の実装コード(答えを見る前)
自力では全然わかりませんでした
zipObject関数の実装コード(答えを見た後)
function zipObject(props = [], values = []) { const zippedObject = {}; for(let i = 0; i < props.length; i++) { const prop = props[i]; const value = values[i]; zippedObject[prop] = value; } return zippedObject; } console.log( zipObject(['a', 'b'], [1, 2]) ); // => { 'a': 1, 'b': 2 }
- 投稿日:2020-09-10T17:39:04+09:00
GASのcalendarで「CalendarApp.Calendar.createEventのメソッドのシグネチャと一致しません」というエラーが出る問題の解決方法
CalendarApp.Calendar.createEventのメソッドのシグネチャと一致しません。というエラーが出てしまう問題について、原因を調べた所
createEventの第2引数、第3引数のデータ型は本来Dateオブジェクトである必要がある。
自分はここで文字列形式のデータになってしまっていたので、date型に修正すると
期待通り動作した。
- 投稿日:2020-09-10T17:01:11+09:00
Sequelize で名前を指定してmigrationを実行する
sequelize db:migrate:status // でmigration nameの一覧表示 sequelize db:migrate --name <migration name> // migration 実行 sequelize db:migrate:undo --name <migration name> // rollback
- 投稿日:2020-09-10T17:01:11+09:00
Sequelize で名前を指定してmigrationをrollbackする
sequelize db:migrate:status // でmigration nameの一覧表示 sequelize db:migrate:undo --name <migration name> // rollback
- 投稿日:2020-09-10T16:27:05+09:00
input type="file" をまともにする
ファイルアップロードのUI(input type="file")は厄介です。ブラウザによって表示方法が異なるのにCSSでのカスタマイズが難しいし、Chrome以外では添付したファイルをキャンセルできないという問題もあります。ですが、JavaScriptを使えば、
- CSSでカスタマイズ可能
- 添付したファイル名を表示可能
- 添付したファイルをキャンセル可能
にできます。
元のHTML
<form method="POST" enctype="multipart/form-data"> <ul> <li><input type="file" name="file1"></li> <li><input type="file" name="file2"></li> </ul> <input type="submit" value="UPLOAD"> </form>
input type="file"
な要素が2つあります。なのでJavaScriptでコントロールするときには対象を特定する必要があります。修正版HTML
よくあるのは
input type="file"
な要素を非表示にした上でlabel
要素で囲み、label
のクリックでinput type="file"
のポップアップを連動させるというやり方です。ですが、JavaScript を使うのであればlabel
にこだわる必要はありません。むしろlabel
にはデフォルトで連動のアクションがあるため扱いが面倒です。
label
の代わりにspan
で囲むことにします。目印のためにclass="upload"
としています。<form method="POST" enctype="multipart/form-data"> <ul> <li><span class="upload"> <input type="file" name="file1"> <input disabled> </span></li> <li><span class="upload"> <input type="file" name="file1"> <input disabled> </span></li> </ul> <input type="submit" value="UPLOAD"> </form>
span
の中には2つinput
要素があります。一つは元々のinput type="file"
な要素、もう一つはファイル名を表示するために追加した要素です。追加した要素は表示のためだけに使うので、disabled
にしています。アイコンなどを追加したい場合はspan
要素の中に入れればクリック時にポップアップと連動します。追加のCSS
以下のCSSを追加します。
form .upload { display: inline-block; } form .upload input[type="file"] { display: none; } form .upload input[disabled] { pointer-events: none; }
span
要素はクリックを「受け止める」必要があるため、inline-block にします。元々のinput
要素は非表示にし、追加したinput
要素はクリックを「素通し」するのでpointer-events: none;
とします。この設定がないと Firefox では追加したinput
要素がクリックを「消費」してしまい、span
までクリックが伝わりません。コントロール用JavaScript
以下のJavaScriptを追加します。
$(function(){ $('.upload').on('click', function(){ $(this).find('input').val(''); $(this).find('input[type="file"]').trigger('click'); }); $('.upload input[type="file"]').on('click', function(event){ event.stopPropagation(); }); $('.upload input[type="file"]').on('change', function(){ if (this.files.length) { $(this).parent().find('input[disabled]') .val(this.files[0].name); } }); });
span
要素(class="upload"
)がクリックされたときは、内部にある2つのinput
要素をともにクリアし、input type="file"
な要素をクリックすることでポップアップを起動します。input type="file"
な要素がクリックされたときに親要素へのイベント伝播を停止します。これを行わないと再度span
がクリックされることになるので無限ループになってしまいます。- ポップアップから戻ったとき、ファイルが選択されていればそのファイル名を追加した方の
input
要素に表示します。jQueryを使ったので簡潔に書けました。生のDOM操作関数でも記述可能と思いますが、かなり面倒になると思います(私にはその根気はありません)。
- 完成品 (見映えはCSSで修正)
- 投稿日:2020-09-10T15:48:31+09:00
javascrpitで自動更新処理
60秒で自動更新
const timer = 60000 // ミリ秒で間隔の時間を指定 window.addEventListener('load',function(){ setInterval('location.reload()',timer); });イベント実行時に画面も更新
- /stopへリクエストして、5秒後に画面リロード
handleClickStop() { const request = axios.create({ baseURL: "http://localhost:8080" }) request.post("/stop") setTimeout(window.location.reload(),5000); }参考
- 投稿日:2020-09-10T14:43:10+09:00
vue/composition-apiでscrollの状態を扱うComposition Functionを作った
概要
上向きにスクロールしたときに表示して、下向きにスクロールしたら隠すフッターを表示する要件が現れたので、
useScroll
を作成して、スクロールの向きを絶えずリアクティブに返すComposition Functionとして扱ってみました。ソースコード
import { useWindowScroll } from '@vueuse/core' import { reactive, toRefs, watch, } from '@vue/composition-api' export const useScroll = () => { const { x, y } = process.browser ? useWindowScroll() : { ...toRefs(reactive({x: 0, y: 0})) } const state = reactive<{ isUp: boolean, isDown: boolean }>({ isUp: false, isDown: false, }) watch(() => y.value, (newY, oldY) => { state.isUp = newY < oldY state.isDown = newY > oldY }) return { x, y, ...toRefs(state), } }解説
こちらのFunctionには、vueuseというライブラリを使わせてもらっています。
composition-apiを使った、特にブラウザのネイティブAPIで扱える値に関してリアクティブに活用できるFunctionがたくさんあります。今回は
useWindowScroll
を利用させていただきました。
これは、windowへのEvent Listenerとして、ステート管理しているスクロールの高さを変更するハンドラを登録していることによって、リアクティブに座標の値を管理できるようにしています。composition-apiのwatchで、スクロールのy値を監視しており、変化したときの古い値との比較によって上昇中か、下降中かを判定しています。
使い方
こんな感じで書くと、スクロールの向きに応じて出たり消えたりするフッターが作れます。高さが70px決め打ちになっているのが少し悔やまれますが、Vue3でStyle周りの改善が入るらしいのでちょっとそれを心待ちにしていようと思っています。
<template> <footer :style="footerStyle" class="sync-scroll" :class="{ appear: isUp }"> <slot /> </footer> </template> <script lang='ts'> import { computed, defineComponent, } from '@nuxtjs/composition-api' import { useScroll } from '~/composables/utils/window/useScroll' export default defineComponent({ setup() { const scrollState = useScroll() const footerStyle = computed(() => { if (scrollState.isUp) { return { height: '70px', } } return { bottom: '-70px', height: '70px', } }) return { ...scrollState, footerStyle, } }, }) </script> <style lang='scss' scoped> @import '@/assets/css/variable.scss'; .sync-scroll { width: 100%; position: fixed; bottom: -70px; animation-name: hide; animation-duration: .4s; animation-timing-function: linear; &.appear { bottom: 0; animation-name: appear; animation-duration: .4s; animation-timing-function: linear; } } @keyframes appear { 0% { bottom: -70px; } 100% { bottom: 0; } } @keyframes hide { 0% { bottom: 0; } 100% { bottom: -70px; } } </style>注意点
もとの
useWindowScroll
が、スクロールのイベントハンドラにthrottleのような処理を噛ませていないっぽくて、全てのスクロールイベントに対してハンドラを呼び出しているようです。パフォーマンスを重視するならthrottleを噛ませたほうが良いと思います。最後に
composition-apiにハマって日々いろいろ試して発信しているTwitterアカウントはこちらです。
https://twitter.com/Meijin_garden
- 投稿日:2020-09-10T14:24:53+09:00
自分用メモ axios javascript
はじめに
今回は、javascriptのライブラリであり、HTTP通信を簡単に行えるaxiosについて基本だけ勉強したのでここでアウトプットしておきたいと思います。
プログラミング初心者のアウトプットですので、至らない点があると思いますので、暖かく見ていただければと思います。また違った点などあればご指摘していただければ幸いです。GET通信
axios.get('https://...').get.then(reponse => response.data).catch(err => console.log(err) //configオブジェクトを使うとき axios({ method: 'GET', url: 'https://...' }).then(reposne => ...これが基本の形になる。
パラメータ付き
const params = {name: 'yuuki'} axios.get('https://...', {params}).then(response => ... //configオブジェクトの場合 axios({ method: 'GET', url: 'https://...' params: {name: 'yuuki'} }).then(response => ...POST通信
const data = {firstName: ..., lastName: ...} axios.post('https://...',data).then(response => ... //configオブジェクトの場合 axios({ method: 'POST', url: 'https://...' data: {firstName: ..., lastName: ...} }).then(response => ...レスポンスの構造
axios使用後のレスポンスの構造は、以下
data, status, statusText, headers, config
dataのなかにgetしたい内容が入ってくる。(body)
バイナリーデータ
バイナリーデータとは、テキストデータ以外のデータになる。
厳密にいうと、コンピューターが見てわかるデータデフォルトはJSONで返ってくる。fetchのようにjson()メソッドを利用しなくても最初からJSON形式で返ってくる。
そしてバイナリーデータでのレスポンスを取得したい場合は,arrayBufferを使う
responseType: 'arrayBuffer'をヘッダーに追加する。
エラーハンドリング
axionsのエラー表示はステータスコードが200台以外で帰ってきた場合は、エラーを返すようになっている。
error.responseという形で帰ってくる。if文などでerror.responseが帰ってきたときの処理などを書いておくことがマナー。
しかしdefaultのエラー表示から変えることもできる
例えば、、、validationStatus: function(status){ return status > 500 }こうすると500以外は成功として認識され、axionsではthenの中で処理ができる。
他にもオプションでタイムアウトやカスタムヘッダなども追加できる
timeout: 1000 //カスタムヘッダー header: {X-SPECIAL-TOKEN: "abcde"}追加方法もこれまでのheaderやパラメータなどと一緒!
Basic認証
Basic認証とは、webサーバに付随している機能の一つでユーザー名とパスワードを入力するようにすること。これを知っていないとそのページにアクセスできないようになっている。
authオプションを使う
auth: {username: 'userName', passoword: 'pass1'}のように使う!!
まとめ
以上がaxiosについての今回の記事になります。基本だけしか理解しておらず深い知識などはまだまだなためこの記事も更新していければと思っています。何か至らない点がありましたら、はじめにも言いましたが、ご指摘していただけるとありがたいです。
参照
- 投稿日:2020-09-10T13:33:27+09:00
(小ネタ)discord.jsでメッセージから絵文字を取得する
はじめに
備忘録も兼ねて、discord.jsにてメッセージからUnicode絵文字を判別し、取得する方法を紹介します。
DiscordにおいてUnicode絵文字とは、各サーバーで登録されるカスタム絵文字以外の、初めから全てのユーザーができる絵文字のことです。
Discordでは絵文字にTwemojiを使用しており、それゆえにTwemojiと互換性があります。
そのため今回はTwemojiの絵文字判定処理に使用されているtwemoji-parserを用いたUnicode絵文字判定処理の方法を紹介します。
twemoji-parserはtwemoji同様、Twitter社によって管理されているライブラリなので、安心感があります。事前準備
適当にプロジェクトを用意していただき、twemoji-parserをインストールしておきます。
npm install twemoji-parser本題
今回はサンプルとして、
message
イベントで受け取ったメッセージ内に含まれるUnicode絵文字を、リアクションとして返すだけのコードを紹介します。
コードとしてはシンプルなため、解説は省かせて頂きます。const { Client } = require('discord.js'); // twemoji-parserから判定用の正規表現を取得(gオプション付き) const twemojiRegex = require('twemoji-parser/dist/lib/regex').default; const client = new Client(); client.on('message', message => { // メッセージから正規表現でUnicode絵文字を取得 const mathEmojis = message.content.match(twemojiRegex); for (const emoji of mathEmojis) { // 取得したUnicode絵文字をリアクションで返す message.react(emoji) .catch(console.error); } }); client.login('Your bot token') .catch(console.error);注意点
twemoji-parserの正規表現にはgオプションが付いています。
ですから、使用目的によってはそのまま利用できないため、自前で正規表現を再生成する必要があります。
例えば、単にgオプションを外したい場合、このようにします。const twemojiRegex = require('twemoji-parser/dist/lib/regex').default; const onceRegex = new RegExp(twemojiRegex.toString().slice(1, -2));※スマートな方法が思いつかなかったので、より良い方法があれば教えてください。
あとがき
メッセージ内のUnicode絵文字を判別し取得する処理は、自前で書こうと思うとなかなかの至難の技です。
twemoij-parserを使用すればUnicode絵文字の複雑な組み合わせも、しっかり1文字と判定されるため、複雑なことを考えずに済みます。
また、これを応用すればスプレッド構文でも実現できなかった、実態に即したUnicodeのパースも行えそうです。
- 投稿日:2020-09-10T12:56:53+09:00
Hooks(useRef) + TypeScriptで親から子コンポーネントの関数を発火する
前提
Reactにおいて親コンポーネントから子コンポーネントを操作(DOMをさわる、関数を発火させる)のはアンチパターン。
いつもの話ですが、ref を使った手続き的なコードはほとんどの場合に避けるべきです。
by https://ja.reactjs.org/docs/hooks-reference.html#useimperativehandlerefを使わずに実装するのであれば、状態を親コンポーネントに寄せるか、あるいはReduxやRecoilに頼るしかない。だけど、それでもどうしてもやりたいという場合は下記の通り。
親コンポーネント
import React, { useRef } from 'react'; const Parent = () => { const ref = useRef(null); // 初期化 const hogeFunction = () => { ref.current.fire(); //refオブジェクトからfire()を実行 } return ( <Child ref={ref}> // refオブジェクトをそのまま渡す ) }子コンポーネント
import React, { FC, useImperativeHandle } from 'react'; interface Props { ref: RefObject<void>; } const Child: FC<Props> = ({ ref }) => { useImperativeHandle(ref, () => ({ fire() { // 発火させたい処理 } })); // ...(省略)... }
useImperativeHandle
の使い方は下記の通り。useImperativeHandle(ref, createHandle, [deps])参考
- 投稿日:2020-09-10T11:03:18+09:00
JavaScriptの変数定義
JavaScriptの学習を始めたのでその日学んだことをアウトプットしていきます。
JavaScriptとはプログラミング言語の1つで、ページを移動せずに画面の表示を切り替えたり
画面を更新せずに、サーバーと通信することができます。
本題の変数の定義方法ですが、var、const、let、といった3つ存在します。
① var
varとは再定義、再代入が可能な書き方です。
console.log(sample)とするとおはようが出力されます。
このような結果になります。
ただし、varは現在開発現場においてあまり使用されておらず、下記の2つがよく使用されるそうです。
②const
constは後から書き換えることのできない変数を定義する書き方です。
ただし再代入、再定義は共に不可です。再代入、再定義共に不可なのでこのようにエラーが表示されます。
③let
letは後で書き換えることができる変数を定義する書き方になります。
再代入は可能ですが、再定義は不可です。とこのようになります。
- 投稿日:2020-09-10T10:08:08+09:00
Next.js+TypeScript+AWS Amplify+RecoilでToDoリストを作る
本記事ではNext.js+TypeScript+AWS Amplify+Recoilを使って、モダンなToDoリストを作る方法を紹介します。
作ったアプリケーションは公開しています。
https://master.d182t7iqbd44r9.amplifyapp.comGithubリポジトリを公開しますので、不具合や不適切な実装を見つけた場合はドシドシIssueかPull-Requestいただけると幸いです。
https://github.com/yuuu/next-ts-amplify-recoil-todolist
背景
私自身普段はRuby on Railsを使って開発しています。JavaScriptは正直まだ苦手です。
Railsは爆速でアプリを開発出来る点が魅力的ですが、一方でモバイルアプリとの連携やリッチなUIが求められる案件では、フロントエンドとバックエンドを分離した構成にせざるをえないケースがあります。
そのような構成だと、かえってRailsがリッチ過ぎるとも感じており、AWS Amplifyのようにバックエンドをスピーディーに構築してくれるサービスを一度使ってみたいと思っていました。そのため、Next.js+AWS Amplisyで簡単なアプリを作ってみて、いわゆるモダンなアプリ開発のノウハウを学んでみることにしました。
使用する技術要素
- Next.js(React.js)
- AWS Amplify
- TypeScript
- Recoil
- React Hook Form
プロジェクトの作成
Next.jsのexamplesにwith-typescript-eslint-jestが公開されているので、これをベースにしました。
$ create-next-app ✔ What is your project named? … next-ts-amplify-recoil-todolist ✔ Pick a template › Example from the Next.js repo ✔ Pick an example › with-typescript-eslint-jest # 省略Amplifyのセットアップ
続いて、AWS Amplifyをセットアップします。
初期化
amplify init
を実行して初期化します。$ cd next-ts-amplify-recoil-todolist $ amplify init ? Enter a name for the project todolist ? Enter a name for the environment dev ? Choose your default editor: Visual Studio Code ? Choose the type of app that you\'re building javascript Please tell us about your project ? What javascript framework are you using react ? Source Directory Path: src ? Distribution Directory Path: out ? Build Command: npm run-script build ? Start Command: npm run-script start ? Do you want to use an AWS profile? Yes ? Please choose the profile you want to use (使用するprofile)Hostingのときにハマらないよう、Distribution Directory Pathは
out
にしています。
詳細は こちらの記事 を参照ください。ホスティング(CI/CD含む)
経験上、後からホスティングを追加するとトラブルシューティングが難しくなるので、できるだけ早い段階でCI/CDによるホスティングを設定しておきます。
❯ amplify add hosting ? Select the plugin module to execute Hosting with Amplify Console (Managed hosting with custom domains, Continuous deployment) ? Choose a type Continuous deployment (Git-based deployments) ? Continuous deployment is configured in the Amplify Console. Please hit enter once you connect your repository # ブラウザでAWS Amplify Consoleが表示されるので諸々設定をした後、Enterを入力 Amplify hosting urls: ┌──────────────┬──────────────────────────────────────────────┐ │ FrontEnd Env │ Domain │ ├──────────────┼──────────────────────────────────────────────┤ │ master │ https://master.d14mfq14xzgfle.amplifyapp.com │ └──────────────┴──────────────────────────────────────────────┘
build
の出力先をout
に変更するため、手元の環境でpackage.json
を一部修正する必要があります。
詳細は こちらの記事 を参照ください。また、Amplify Consoleの「ビルドの設定」からamplify.ymlを次のように修正します。
amplify.ymlversion: 1 backend: phases: build: commands: - '# Execute Amplify CLI with the helper script' - amplifyPush --simple frontend: phases: preBuild: commands: - yarn install build: commands: - yarn run build artifacts: baseDirectory: out files: - '**/*' cache: paths: - node_modules/**/*さらに、「ビルドの設定」→「Build image settings」→「Edit」→「Live package updates」で
Amplify CLI
をlatest
とする必要があります。これをしていないと、デプロイに失敗するので要注意です。デプロイが終わり、表示されたURLにアクセスすると、ページが表示されます。
認証基盤
以下コマンドで認証基盤を追加します。
$ amplify add auth ? Do you want to use the default authentication and security configuration? Default configuration ? How do you want users to be able to sign in? Username ? Do you want to configure advanced settings? No, I am done. # 省略 $ amplify push ✔ Successfully pulled backend environment production from the cloud. Current Environment: production | Category | Resource name | Operation | Provider plugin | | -------- | -------------------- | --------- | ----------------- | | Auth | besttodolistf818478c | Create | awscloudformation | | Hosting | amplifyhosting | No Change | | ? Are you sure you want to continue? Yes # 省略GraphQL API
予めGraphQLのスキーマファイルを作成しておきます。
./schema.graphqltype Todo @model { id: ID! name: String! completed: Boolean! timestamp: AWSTimestamp! }以下コマンドでバックエンドのGraphQL APIを追加します。
$ amplify add api ? Please select from one of the below mentioned services: GraphQL ? Provide API name: todolist ? Choose the default authorization type for the API Amazon Cognito User Pool Use a Cognito user pool configured as a part of this project. ? Do you want to configure advanced settings for the GraphQL API No, I am done. ? Do you have an annotated GraphQL schema? Yes ? Provide your schema file path: ./schema.graphql # 省略念の為mockサーバを起動して動作確認をしておきます。
このとき、クライアントのコードも生成されます。$ amplify mock api # 省略 Running GraphQL codegen ? Choose the code generation language target typescript ? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.ts ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes ? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 ? Enter the file name for the generated code src/API.ts ? Do you want to generate code for your newly created GraphQL API Yes # 省略 AppSync Mock endpoint is running at http://xxx.xxx.xxx.xxx:20002表示されたURLにアクセスすると、Graphiqlの画面が表示され、GraphQLのリクエストを試すことができます。
特に問題がなければ、
amplify push
しておきましょう。❯ amplify push ✔ Successfully pulled backend environment production from the cloud. Current Environment: production | Category | Resource name | Operation | Provider plugin | | -------- | -------------------- | --------- | ----------------- | | Api | bestTodolist | Create | awscloudformation | | Hosting | amplifyhosting | No Change | | | Auth | besttodolistf818478c | No Change | awscloudformation | ? Are you sure you want to continue? Yes # 省略 ? Do you want to update code for your updated GraphQL API Yes ? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types? This will overwrite your current graphql queries, mutations and subscriptions Yes # 省略フロントエンドの実装
npmパッケージのインストール
以下コマンドでインストールします。
$ yarn add @material-ui/core @material-ui/icons aws-amplify @aws-amplify/ui-react react-hook-form recoil
Lint/Formatterのignoreを追加
Next.jsがexportしたファイルやAWS Amplifyで自動生成したコードはLint/Formatterの対象外にしておきます。
.eslintignore**/node_modules/* **/out/* **/.next/* src/aws-exports.js.prettierignorenode_modules .next yarn.lock package-lock.json public out一覧ページの実装
以下ファイルを所定のディレクトリへコピペしてください。
ページ
ベースとなる
_app.tsx
と_document.tsx
も必要です。
_document.tsx
は、まだFunction Componentでは書けないようです。https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/pages/_app.tsx
https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/pages/_document.tsx
https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/pages/index.tsxコンポーネント
各ページの部品となるコンポーネントを作成します。
https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/src/component/Header.tsx
https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/src/component/Footer.tsx
https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/src/component/Todo.tsxストア
正直TodoListのレベルであれば、ストアを使う必要は無いのですが、前々から使ってみたいと思っていたRecoilを使うことにしました。
https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/src/store/todos.ts
テーマ
Material UIのベースとなるテーマを定義します。
https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/src/theme.ts
動作確認
CI/CDが無事に完了し、ページにアクセスするとログイン画面が表示されます。
Create Account
でアカウント作成するとログインができるようになります。
無事、一覧画面が無事表示されました。その他の画面の実装
新規登録画面
次のファイルを
pages
に追加します。https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/pages/todos/new.tsx
フォームは編集画面でも使うためコンポーネント化しておきます。
https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/src/component/Form.tsx
詳細画面
次のファイルを
pages
に追加します。https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/pages/todos/%5Bid%5D.tsx
編集画面
次のファイルを
pages
に追加します。https://github.com/yuuu/next-ts-amplify-recoil-todolist/blob/master/pages/todos/%5Bid%5D/edit.tsx
まとめ
手元の環境でアプリを開発して画面を揃えるところまでは順調に進んだのですが、CI/CDでハマりどころが多く苦労しました。
ここまでの環境が揃っていれば、あとはスピーディーに開発していけそうな気がしています。ぜひ、お試しください。
- 投稿日:2020-09-10T09:50:31+09:00
【Nuxt.js+Firebase】ログイン不要!気軽に投稿できるオススメのカレー共有サイトを作りました!
カレーの時代到来
近年、カレーブームがますます加速しているように思われます。
ちょうど今、西武池袋で行われてるカレーイベントでは連日行列が続き、
売り切れ商品も多数あるとのこと!かくいう私も、カレーフリーク。
カレー専用のInstagramを開設し、食べ歩いたカレーを投稿しています。今回、静的サイトを作る練習として、Nuxt.jsとFirebaseを使って、カレー屋さんの共有サイトを作ってみました!
食べログやInstagramと違い、アカウントは不要、匿名で気軽に投稿できるのが特徴です♪
下記にリンクを貼っているので、ぜひ投稿してみてください!カレー共有サイト「Curry Freak」
サイトURL ▶▶▶ https://curryfreak.ml/
「新しいCurryを追加」のページから、投稿画面が確認できます。
入力が完了すると、投稿ボタンが出現!
(アップロードが完了する前に投稿するとエラーになるので)
投稿ボタンを押すと、「Curry一覧」ページに遷移して、
投稿したカレー屋さん情報をご覧いただけます♪
作り方
まずNuxt.jsでプロジェクトを作成(参考記事)
UIフレームワークを選べるので、今回Buefyを選択しました。
▽
yarn add firebase
でFirebaseのライブラリを追加
▽
yarn dev
で動作確認しながら、ページやコンポーネントを作成
▽
UIをちょこっと修正
▽
yarn generate
を実行し、distフォルダをNetlifyでデプロイ
▽
独自ドメインを設定して、完成♪(参考記事)ページなどのソースコードはgithubにあげたのでご覧ください!
ソースコード ▶▶▶ https://github.com/twtjudy1128/CurryFreak
つまずいたところ
Firebaseの呼び出し
序盤で「Firebase App named '[DEFAULT]' already exists」というエラーが出て進まず。
こちらの記事を拝見し、
何度もFirebaseを初期化して呼び出してしまっていることが原因だとわかったので、
以下のように初期化のコードを修正したら、すぐエラーが消えました♪// Initialize Firebase if (firebase.apps.length === 0) { firebase.initializeApp(config); }V-modelの使い方
v-onとv-bindをまとめて書くことができるv-model。
フォームで色んな人が使ってるのを見て、私も投稿画面で使ってみました。<div class="postform"> <div> <input v-model="title" placeholder="店名"><br> <input v-model="name" placeholder="名前"><br> <input v-model="memo" placeholder="ひとこと"><br> <input v-show="!image_url" type="file" id="image_file" @change="onFileChange" accept="image/*" required/> <b-button type="is-warning" v-on:click='post' v-show="show"><b>投稿</b></b-button> </div> </div>ところが、以下のように真っ赤になっちゃいました><
調べると、対応するデータが定義されていなかったので、script部分で、以下のように定義。
data(){ return { image_url: null, title:'', name:'', memo:'', downloadURL:'', show: false, };すると今度は、以下のエラー
The “data” option should be a function that returns a per-instance value in component definitions.
子コンポーネントでは、dataをオブジェクトではなく、関数として定義する必要があるとのこと・・・(難しい)
というわけで、以下のようにちょこっと変更しただけでエラーが綺麗に消えました!data:function(){ return { image_url: null, title:'', name:'', memo:'', downloadURL:'', show: false, };【参考記事】
▶Vue.jsのv-modelを正しく使う
▶【Vue.js】The “data” option should be a function that returns a per-instance value in component definitions.というエラーについて画像とテキストを一緒に投稿する
1番苦戦したところです。笑
上記のような構造を目指して、あーだこーだやってみたのですがエラー続きで心折れかけました。笑
その時に、GyazoやimgurのAPIを使う方法を見つけたのでトライしてみることに。
ところが、GyazoAPIはアクセス権限で引っかかり使えず…。imgurで試してみたのですが、下記のようなエラーが出ました…(今だ解決しておらず)
かなり苦戦していたのですが、 @tkyko13 さんにご協力いただき、
本来やりたかったFirebase StorageとCloud Firestoreを使った方法でうまく投稿できるようになりました。(大感謝)コード長くなるので、ソースコードのpost.vueをご覧いただけると幸いです!
ソースコード ▶▶▶ https://github.com/twtjudy1128/CurryFreak心残りなPOINT
・投稿日時も入れればよかった
・投稿した順に表示できるようにしたい
・画像をアップロードしている間、「アップロード中」のクルクルみたいのを出したい
・UIをもっと綺麗にしたい(フレームワークは便利だけどカスタムのコツがまだ掴めてない)
・ロゴを作りたい
・もう1つページを増やして、おふざけ要素作りたかった色々やりたいこと挙げるとキリがないですね・・・
でも、手こずりながらも、また1つアウトプットできたことが嬉しいです。あなたのオススメのカレー屋さんを教えてください♪
最後までご覧いただき、ありがとうございます!
匿名で簡単に投稿できるので、是非あなたのオススメカレーを投稿してみてくださいね!カレー共有サイト「Curry Freak」 ▶▶▶ https://curryfreak.ml/
みんなでカレー食べて、免疫つけて、今日も1日がんばりまっしょー!!!!!
(*^^)v「よろしければLGTMも宜しくお願いします!」
<9/10 18:28追記>
もうこんなに投稿集まってきました~ありがとうございます!
こういう機能も欲しいなどフィードバックもいただけて嬉しいです!
バシバシ投稿よろしくお願いします!
※私の独断で不適切だと思った画像は随時削除しております。ご了承ください。
- 投稿日:2020-09-10T06:09:48+09:00
え!? わずか3分でローカルにTypeScriptの実行環境を!?
できらぁ!(様式美)
ということでローカルにTypeScriptの実行環境を作ります。すぐできます。
TypeScriptを使うだけなら、TypeScript playground等を使えばいいと思うのですが、「○○のパッケージを試したい。ついでだからTypeScriptも使いたい」という欲張りさんはローカルに環境構築したくなることもあるでしょう。え? codesandbox? 知らんなぁ。
とにかくローカルにTypeScriptの実行環境を作っていきます。ゴールは「コンパイルして出来たjsファイルをnodeコマンドで実行するところ」までです。
事前準備
以下は事前に準備できてるとします。出来てない方は適当にググってください。
- node, npm(yarn)
ローカルにTypeScriptの実行環境を作成
ここから本題です。
あと私はyarn派なのでyarnを使います。プロジェクトの作成
プロジェクトディレクトリを作成して、package.jsonを作りましょう。
$ mkdir typescript_try $ cd typescript_try/ $ yarn init
yarn init
後に色々聞かれますが、とりあえず全部Enterで良いです。(ちゃんと設定したい人はしてください)TypeScriptのインストール
$ yarn add -D typescript @types/nodetypescript等はもちろん開発でしか使わないので、-Dはつけましょう(すぐ忘れる)
tsconfig.json
これが無いとコンパイル出来ないので作ります。ルートディレクトリに置いて下さい。
$ touch tsconfig.jsontsconfig.json{ "compilerOptions": { "lib": ["ESNext"], "module": "CommonJS", "outDir": "dist", // コンパイル後に生成されるJSファイルの置き場所をTSCに指示 "sourceMap": true, "strict": true, "target": "ES2015" }, "include": ["src"] // TSCがTypeScriptファイルを見つけるためにどのディレクトリを探せば良いか?の指定 }なおこの内容は、オライリーのTypeScript本の2.3.1 tsconfig.json に記載された内容をベースに作成しました。(
yarn tsc --init
でも内容は異なりますが最低限のものを作れます。こっちの方が普通かも?)詳細は割愛しますが、以降の説明に関連する2つのパラメータに関してはjsonのコメントに説明を記載しました。なおTSCはTypeScriptのコンパイラのことです。
他の項目に関してはググってください。もしくはオライリー本を買って下さい! 超良書です。
またこの時点では、"src"/"dist" フォルダが無いため、エディタによってはエラーが表示されるかもしれませんが、そこは一旦スルーして下さい。
TSファイルの作成
実行したいTypeScriptファイルを作成します。
tsconfig.jsonのincludeで指定した通り、srcディレクトリを作成して、その下に作って下さい。$ mkdir src $ touch src/index.tssrc/index.tsconst hello: string = 'Hello TypeScript!' console.log(hello)コンパイル後に生成されるJSファイルの置き場所を作成
ディレクトリを作るだけでOKです。
$ mkdir distもし先ほどtsconfig.jsonでエラーが出ていた場合は、この後エラーが消えていることを確認して下さい。
TSファイルのコンパイル
作成したTypeScriptファイルをコンパイルします。
$ yarn tsc yarn run v1.22.4 $ node_modules/.bin/tsc ✨ Done in 1.39s.生成されたJSファイルはdist下に保存されているはずです。確認してみましょう。
dist/index.js"use strict"; const hello = 'Hello TypeScript!'; console.log(hello); //# sourceMappingURL=index.js.mapそれっぽく出来てますね!
生成されたJSファイルの実行
nodeコマンドで生成されたJSファイルを実行します。
$ node dist/index.js Hello TypeScript!
これでローカル環境でTypeScriptのコードを実行できました。簡単でしたね!
さいごに
「手元でJavaScript周りのパッケージの動作確認等をする時、サラッとTypeScript使えてたらカッコよくない?」という不純な気持ちで書きました。
ただパッケージによっては、別途TypeScript用の設定が必要だったりするのでそこはご注意くださいm(_ _)m
- 投稿日:2020-09-10T02:15:17+09:00
DOMとは
プログラミング勉強日記
2020年9月9日
JSについて調べていくうちにDOMを知り、JSを扱う上で絶対に知らないといけない仕組みだと思ったのでまとめる。DOMとは
Document Object Modelの略で「ドム」と呼ばれ、HTMLやXML文書を取り扱うためのAPI。つまり、プログラムからHTMLやXMLを自由に操作するための仕組み。(APIについてはこちらでまとめている)
DOMではHTMLやXML文書をノードと呼ばれる階層的な構造として識別し、JSなどの様々なプログラミング言語やスクリプトから扱いたいノードを特定して操作できるようにする仕組みを提供する。特徴
DOMに以下の3つの特徴がある。
- ツリー構造と呼ばれる階層構造を持つ
- それぞれノードで説明される
- WEBページとJSなどのプログラミング言語をつなぐ
階層構造をとる
階層構造は組織図的なもの。
HTMLの階層構造は以下のようになる。以下の例では<body>
を頂点として下にいくつかの<section>
と<p>
で構成されている。これはHTMLで階層構造を構築した場合の一例であり、この階層構造を定義しているものがDOMと呼ばれる仕組みを使っている。各要素はノードで表現される
DOMではノードと呼ばれる用語が出てくる。
以下のようにノードは各要素自体のことを表す。特定のノードを基準としたときに、その上にあるノードを親ノード、その下にあるノードを子ノード、同じ階層にあるノードを兄弟姉妹ノードと表現する。
参考文献
- 投稿日:2020-09-10T01:46:57+09:00
【Nuxt.js + Firebase】写真を投稿するとAIがハッピー指数を測定してランキング付けするシステムを作ったよ
作ったもの
タイトルの通り、写真を投稿すると、どれくらいハッピーなのかAIがハッピー指数を算出します。
写真はパクタソさんより※「ハッピー指数」としましたが、これって「指数」って言ってもいいものだろうか不安になりましたが、頭の悪そうな語感が気に入ったのでこのまま「ハッピー指数」とします。
そして、同時にハッピー指数のランキングを表示します。
写真はパクタソさんよりおお、眩しすぎる。
という訳で、
あなたが世界でどれくらい幸せなのか、調べてみましょう。公開URL
ウェブアプリとして公開していますので、是非お試してください。
ちなみにこのドメインはこちらで取得しました。
freenom環境
フレームワーク :Nuxt.js
CSSフレームワーク:BULMA
開発プラットフォーム:Firebase技術面
表情解析
アップロードした写真を、face-api.js というTensorFlow.jsで学習済みの顔認識の機械学習モデルのライブラリに投げて、表情からハッピー度を取得します。
ハッピー度は0から1の間で、大きいほどハッピー度が高いです。faceapi.resizeResults(detections, displaySize)[0].expressions.happy = 0.999991774559021ハッピー度はこんな感じで取得されます。
小数点が15桁くらいあるので、1000倍して見やすくしました。(ご参考)自分の過去記事
【忙しい現代人のために】表情で扇風機を操作するシステムを作ったよ写真およびデータ保存
ここでハマったことを別記事に書きました。
【Firebase + Nuxt.js】FirebaseStorageへの画像アップロードでハマったところハマったところ
Nuxt.js、Vue.js
リストの中でのイメージの表示方法
こちらを見て解決しました。ありがとうございました。
【Vue.js】imgタグのsrc要素は指定の仕方によって読み込み方が違うたった、これだけなんですけど、凄くハマりました。
<img :src="data.picurl"/>
非同期処理のコードの書き方
以下のようなコードのことです。
毎回、頭がおかしくなりそうです。
=> を多用すると、訳わからなくなります。
じっくりと勉強しなければ。Pic.vuemethods: { post(pic){ 〜中略〜 // 画像をStorageにアップロード storageRef.put(file).then(() => { let debug_document = document.getElementById("happyScore"); debug_document.innerHTML = "しばらくお待ちください"; // アップロードした画像のURLを取得 const picurl = firebase.storage().ref(fileName).getDownloadURL().then((url) => { const happyScoreGet = getFaceData( document.getElementById('attachedFile')).then((happyScore) => { let debug_document = document.getElementById("happyScore"); let realhappyScore = happyScore; happyScore = Math.floor(happyScore * 1000); //1000倍 debug_document.innerHTML = "ハッピー指数: "+String(happyScore)+"点"; //firestoreにデータを保存 const setScore = docRef.set({ name: this.name, happyScore: happyScore, realhappyScore: realhappyScore , fileName: fileName , picurl: url }); //ランキング作成へ this.get(); }); }).catch((error) => { console.log(error) }) }) } }
face-api.js のファイルのままデプロイすると怒られた
[BABEL] Note: The code generator has deoptimised the styling of /pages/face-api.js as it exceeds the max of 500KB.500KB以上はダメらしいので、
https://github.com/justadudewhohacks/face-api.js/
に書いてある通り、npmでインストールしました。
npm i face-api.js
face-api.jsの機械学習モデルの場所はstaticフォルダへ
同じディレクトリに保存してはいけません。
デプロイされません。
ハマったところは、その他、たくさんあったけど、書ききれないです。
やりたかったけど出来なかったこと
・送信ボタンを付けたかった
(添付ファイルを選んだ瞬間に送信される)
・画像サイズ制限
・画像をリサイズして保存する
・ハッピー指数がでた瞬間に順位が出るようにコード
index.vue<template> <div class="container"> <div> <br><br> <h1 class="title"> ハッピー・ランキング </h1> 写真を投稿するとハッピー指数を判定して、ランキングします。<br> ハッピー指数は1000点が最高得点です。<br><br> ※投稿写真は作者が管理しているクラウドサーバーに保存されますので、ご注意ください。 <br><hr><br><br> <client-only placeholder="Loading..."> <Pic /> </client-only> </div> </div> </template> <script defer src="face-api.js"></script> <script defer src="scripts.js"></script> <script> import Pic from '~/components/Pic.vue' export default { components: { Pic } } </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; background-color: pink; } .title { font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: bold; font-size: 30px; color: #35495e; letter-spacing: 1px; } #happyScore{ font-size: 30px; font-weight: bold; } .list{ padding-top: 50px; } </style>index.vue<template> <div> 投稿者名:<input v-model="name" placeholder="投稿者名"> <br><br> <input @change="post" type="file" data-label="画像の添付"> <br> <img id="attachedFile" width=350 v-show="uploadedImage" :src="uploadedImage" /> <div id="happyScore"></div> <br><hr> <div class="list"> <h1 class="title"> ハッピー指数 トップ100 </h1> <br> <ul v-for="(data, index) in allData" :key="data.id" class="menu-list" > <li> 順位: {{index + 1}} 位<br> ハッピー指数 : {{data.happyScore}} 点 <br> 投稿者名:{{data.name}} <br> <img width=350 :src="data.picurl"/> </li> <br><br> </ul> </div> <br> </div> </template> <script> import firebase from "firebase/app"; import "firebase/firestore"; import 'firebase/storage'; import * as faceapi from 'face-api.js'; import uuid from 'uuid'; export default { components: {}, data(){ return{ db: {}, allData: [], name: '', fileName: '', picurl: '', uploadedImage: '', happyScore: '', realhappyScore: '', testId: '' } }, methods: { //初期化、設定 //各人の数値を入れること init: () => { const config = { apiKey: "", authDomain: "", databaseURL: "", projectId: "", storageBucket: "gs://xxxxxx-99999.appspot.com", messagingSenderId: "", appId: "", measurementId: "" }; // Initialize Firebase firebase.initializeApp(config); }, post(pic){ const file = pic.target.files[0]; if(!file.type.match('image.*')) { alert("画像ファイルでお願いします"); return; } //イメージファイル描画 let reader = new FileReader(); reader.onload = (pic) => { this.uploadedImage = pic.target.result; }; let imagefiles = pic.target.files || pic.dataTransfer.files; reader.readAsDataURL(imagefiles[0]); let attachedFile = document.getElementById('attachedFile'); const testId = firebase.firestore().collection('pics').doc().id; //ユニークなIDを生成 const docRef = firebase.firestore().collection('pics').doc(testId); const fileName = uuid(); //ファイル名は他と被らないように uuid ライブラリを使って動的に生成 const storageRef = firebase.storage().ref(fileName); // 画像をStorageにアップロード storageRef.put(file).then(() => { let debug_document = document.getElementById("happyScore"); debug_document.innerHTML = "しばらくお待ちください"; // アップロードした画像のURLを取得 const picurl = firebase.storage().ref(fileName).getDownloadURL().then((url) => { const happyScoreGet = getFaceData( document.getElementById('attachedFile')).then((happyScore) => { let debug_document = document.getElementById("happyScore"); let realhappyScore = happyScore; happyScore = Math.floor(happyScore * 1000); //1000倍 debug_document.innerHTML = "ハッピー指数: "+String(happyScore)+"点"; //firestoreにデータを保存 const setScore = docRef.set({ name: this.name, happyScore: happyScore, realhappyScore: realhappyScore , fileName: fileName , picurl: url }); //ランキング作成へ this.get(); }); }).catch((error) => { console.log(error) }) }) }, //データ取得 get: function(){ this.allData = []; //スコアの降順に100個取得 firebase.firestore().collection('pics').orderBy('realhappyScore', 'desc').limit(100).get().then(snapshot => { snapshot.forEach(doc => { this.allData.push(doc.data()); }) }); } }, mounted(){ //ページ読み込み時に実行される this.init(); }, } //表情取得 async function getFaceData(img) { await faceapi.nets.tinyFaceDetector.load("/models") ;//モデル読み込み await faceapi.nets.faceLandmark68Net.load("/models") ;//モデル読み込み await faceapi.nets.faceRecognitionNet.load("/models") ;//モデル読み込み await faceapi.nets.faceExpressionNet.load("/models") ;//モデル読み込み const detectionsWithLandmarks = await faceapi.detectAllFaces(img,new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks(); if (detectionsWithLandmarks.length == 0){ alert('人間じゃないよ'); return(0) }else{ const displaySize = { width: attachedFile.width, height: attachedFile.height } //1つの顔だけなのでfaceapi.detectAllFacesではなくて detectSingleFaceでよいはずが、本件はdetectAllFacesを使った。 const detections = await faceapi.detectAllFaces(attachedFile , new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions() const resizedDetections = faceapi.resizeResults(detections, displaySize) return(resizedDetections[0].expressions.happy); //ハッピー指数を返す } } </script>
番外編 (うちわ受け)
現在、一緒にProtoOut Studioで学んでる受講生が今までQiitaで取り上げた人物で試してみました。
Juri Tawaraさん
代表作:ジェイソン・ステイサムで妄想するのが日課になっていたので、いっそBOTにしてみた。
[UhRhythm](https://qiita.com/UhRhythm)さん
【Vue.js】さ迷うハロオタがお誕生日カレンダーを作った
- 投稿日:2020-09-10T01:46:57+09:00
【Nuxt.js + Firebase】写真を投稿するとAIがハッピー指数を測定してランキング付けするリア充向けのシステムを作ったよ
作ったもの
タイトルの通り、写真を投稿すると、どれくらいハッピーなのかAIがハッピー指数を算出します。
写真はパクタソさんより※「ハッピー指数」としましたが、これって「指数」って言ってもいいものだろうか不安になりましたが、頭の悪そうな語感が気に入ったのでこのまま「ハッピー指数」とします。
そして、同時にハッピー指数のランキングを表示します。
写真はパクタソさんよりおお、眩しすぎる。
キラキラする。
まさにリア充向けシステムだ。という訳で、
あなたが世界でどれくらい幸せなのか、調べてみましょう。公開URL
ウェブアプリとして公開していますので、是非お試してください。
ちなみにこのドメインはこちらで取得しました。
freenom環境
フレームワーク :Nuxt.js
CSSフレームワーク:BULMA
開発プラットフォーム:Firebase技術面
表情解析
アップロードした写真を、face-api.js というTensorFlow.jsで学習済みの顔認識の機械学習モデルのライブラリに投げて、表情からハッピー度を取得します。
ハッピー度は0から1の間で、大きいほどハッピー度が高いです。faceapi.resizeResults(detections, displaySize)[0].expressions.happy = 0.999991774559021ハッピー度はこんな感じで取得されます。
小数点が15桁くらいあるので、1000倍して見やすくしました。(ご参考)自分の過去記事
【忙しい現代人のために】表情で扇風機を操作するシステムを作ったよ写真およびデータ保存
ここでハマったことを別記事に書きました。
【Firebase + Nuxt.js】FirebaseStorageへの画像アップロードでハマったところハマったところ
Nuxt.js、Vue.js
リストの中でのイメージの表示方法
こちらを見て解決しました。ありがとうございました。
【Vue.js】imgタグのsrc要素は指定の仕方によって読み込み方が違うたった、これだけなんですけど、凄くハマりました。
<img :src="data.picurl"/>
非同期処理のコードの書き方
以下のコードのような非同期処理が苦手です。
then(() =>{ とか、Promiseとか ansy async とか await とか・・・
毎回、頭が混乱してしまいます。
じっくりと勉強しなければ。
face-api.js のファイルのままデプロイすると怒られた
[BABEL] Note: The code generator has deoptimised the styling of /pages/face-api.js as it exceeds the max of 500KB.500KB以上はダメらしいので、
https://github.com/justadudewhohacks/face-api.js/
に書いてある通り、npmでインストールしました。
npm i face-api.js
face-api.jsの機械学習モデルの場所はstaticフォルダへ
同じディレクトリに保存してはいけません。
デプロイされません。
ハマったところは、その他、たくさんあったけど、書ききれないです。
やりたかったけど出来なかったこと
・送信ボタンを付けたかった
(今のは、添付ファイルを選んだ瞬間に送信される)
・画像をリサイズして保存したかった
・ハッピー指数がでた瞬間に順位が出るようにしたかったコード
index.vue<template> <div class="container"> <div> <br><br> <h1 class="title"> ハッピー・ランキング </h1> 写真を投稿するとハッピー指数を判定して、ランキングします。<br> ハッピー指数は1000点が最高得点です。<br><br> ※投稿写真は作者が管理しているクラウドサーバーに保存されますので、ご注意ください。 <br><hr><br><br> <client-only placeholder="Loading..."> <Pic /> </client-only> </div> </div> </template> <script defer src="face-api.js"></script> <script defer src="scripts.js"></script> <script> import Pic from '~/components/Pic.vue' export default { components: { Pic } } </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; background-color: pink; } .title { font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: bold; font-size: 30px; color: #35495e; letter-spacing: 1px; } #happyScore{ font-size: 30px; font-weight: bold; } .list{ padding-top: 50px; } </style>index.vue<template> <div> 投稿者名:<input v-model="name" placeholder="投稿者名"> <br><br> <input @change="post" type="file" data-label="画像の添付"> <br> <img id="attachedFile" width=350 v-show="uploadedImage" :src="uploadedImage" /> <div id="happyScore"></div> <br><hr> <div class="list"> <h1 class="title"> ハッピー指数 トップ100 </h1> <br> <ul v-for="(data, index) in allData" :key="data.id" class="menu-list" > <li> 順位: {{index + 1}} 位<br> ハッピー指数 : {{data.happyScore}} 点 <br> 投稿者名:{{data.name}} <br> <img width=350 :src="data.picurl"/> </li> <br><br> </ul> </div> <br> </div> </template> <script> import firebase from "firebase/app"; import "firebase/firestore"; import 'firebase/storage'; import * as faceapi from 'face-api.js'; import uuid from 'uuid'; export default { components: {}, data(){ return{ db: {}, allData: [], name: '', fileName: '', picurl: '', uploadedImage: '', happyScore: '', realhappyScore: '', testId: '' } }, methods: { //初期化、設定 //各人の数値を入れること init: () => { const config = { apiKey: "", authDomain: "", databaseURL: "", projectId: "", storageBucket: "gs://xxxxxx-99999.appspot.com", messagingSenderId: "", appId: "", measurementId: "" }; // Initialize Firebase firebase.initializeApp(config); }, post(pic){ const file = pic.target.files[0]; if(!file.type.match('image.*')) { alert("画像ファイルでお願いします"); return; } //イメージファイル描画 let reader = new FileReader(); reader.onload = (pic) => { this.uploadedImage = pic.target.result; }; let imagefiles = pic.target.files || pic.dataTransfer.files; reader.readAsDataURL(imagefiles[0]); let attachedFile = document.getElementById('attachedFile'); const testId = firebase.firestore().collection('pics').doc().id; //ユニークなIDを生成 const docRef = firebase.firestore().collection('pics').doc(testId); const fileName = uuid(); //ファイル名は他と被らないように uuid ライブラリを使って動的に生成 const storageRef = firebase.storage().ref(fileName); // 画像をStorageにアップロード storageRef.put(file).then(() => { let debug_document = document.getElementById("happyScore"); debug_document.innerHTML = "しばらくお待ちください"; // アップロードした画像のURLを取得 const picurl = firebase.storage().ref(fileName).getDownloadURL().then((url) => { const happyScoreGet = getFaceData( document.getElementById('attachedFile')).then((happyScore) => { let debug_document = document.getElementById("happyScore"); let realhappyScore = happyScore; happyScore = Math.floor(happyScore * 1000); //1000倍 debug_document.innerHTML = "ハッピー指数: "+String(happyScore)+"点"; //firestoreにデータを保存 const setScore = docRef.set({ name: this.name, happyScore: happyScore, realhappyScore: realhappyScore , fileName: fileName , picurl: url }); //ランキング作成へ this.get(); }); }).catch((error) => { console.log(error) }) }) }, //データ取得 get: function(){ this.allData = []; //スコアの降順に100個取得 firebase.firestore().collection('pics').orderBy('realhappyScore', 'desc').limit(100).get().then(snapshot => { snapshot.forEach(doc => { this.allData.push(doc.data()); }) }); } }, mounted(){ //ページ読み込み時に実行される this.init(); }, } //表情取得 async function getFaceData(img) { await faceapi.nets.tinyFaceDetector.load("/models") ;//モデル読み込み await faceapi.nets.faceLandmark68Net.load("/models") ;//モデル読み込み await faceapi.nets.faceRecognitionNet.load("/models") ;//モデル読み込み await faceapi.nets.faceExpressionNet.load("/models") ;//モデル読み込み const detectionsWithLandmarks = await faceapi.detectAllFaces(img,new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks(); if (detectionsWithLandmarks.length == 0){ alert('人間じゃないよ'); return(0) }else{ const displaySize = { width: attachedFile.width, height: attachedFile.height } //1つの顔だけなのでfaceapi.detectAllFacesではなくて detectSingleFaceでよいはずが、本件はdetectAllFacesを使った。 const detections = await faceapi.detectAllFaces(attachedFile , new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions() const resizedDetections = faceapi.resizeResults(detections, displaySize) return(resizedDetections[0].expressions.happy); //ハッピー指数を返す } } </script>
番外編 (内輪ネタ)
現在、一緒にProtoOut Studioで学んでる受講生が今までQiitaで取り上げた人物で試してみました。
- 投稿日:2020-09-10T00:58:27+09:00
みんなで物語をつくりながら連想力を鍛えてアイデア発想力を磨こう
アイデア発想力とは?
アイデアとは、情報と情報の掛け合わせであると言われています。
その掛け合わせるために必要な力が、連想力です。例えば、情報Aと情報Bの関連性を発見したり、情報Aと情報Bをベースにホップ・ステップ・ジャンプで飛躍してみたりするような連想によって、新しいアイデアを生み出していきます。
なので、アイデアを生み出す力を磨くには、連想力を磨くことが大事です。
じゃあ、どうやると磨いていけるのか?
言葉遊びゲーム「空文字アワー」とは?
その1つの方法が、言葉遊びゲーム「空文字アワー」です。
このゲームは、ある一文から連想した情報を文に追加することで、
新たな物語をつくっていきます。まあやってみるとわかると思うので、
詳細はこちらをご覧いただき、
ぜひ最新の文に情報を追加してみてください。使った技術
・nuxt.js
・Vue.js
・FirebaseのCloud Firestore実装の仕方
今回は、初めてnuxt.jsってやつを使いました。
正直使いこなすには程遠いですが、それでも、nuxt.js、Vue.js、Cloud Firestoreを使い、独自ドメインでNetlifyで公開まで持っていけたのは進歩です。nuxt.jsの流れ
yarn create nuxt-app [任意のプロジェクト名]今回のUI frameworkは、
UI framework: Bulmaを使用。
ひとまず形ができたら、
$ yarn devをして、ローカルサーバーからサイトが閲覧できるようにしながら、
pagesやComponentsの中のファイルを作りました。で、それができたら、
$ yarn generateする。
これによって、distファイルができるので、そのファイルごとNetlifyに取り込むと、すぐにWEBアプリが公開できました。
独自ドメインでの公開の流れ
まずは無料で独自ドメインを取得します。色々無料で取得する方法があるようですが、今回はfreenomで取得しました。
freenomで取得する際の流れはこちらを参照しました。
ドメインを取得できたら、My domainのURLや情報をNetlifyの方に入力すると、少し時間はかかかるものの、わあしはすぐににWEBが独自ドメインで公開できます。
nuxt.jsのpages
index.vue<template> <div class="container"> <div> <Logo /> <h1 class="title"> 【言葉ゲーム】空文字アワー <p>~ないものつなぎ~</p> </h1> <div class = "content-explain"> <p>ある簡単な文に( )があります。ここに「言葉」を入れ、さらに( )を加えます。 次の人も同じことをする。これを全員で繰り返していくゲームです。 「空文字アワー」は、どんどんつないで変化を起こすのが醍醐味です。 最終的にどんなストーリーができあがるかは、みなさんのセンス次第。</p> </div> <div class = "content-explain"> <h2>進め方</h2> <ol> <li>最初の一文と( )を提示します。</li> <li>次に答える人が( )に言葉を加え、自分の言葉を加えた( )を外します。</li> <li>さらに( )を好きな位置に加えます。これを期間内繰り返します。</li> </ol> </div> <div class = "content-explain2"> <h2>ルール</h2> <ol> <li>連続して同じ人が答えることはできません。</li> <li>連続しない限り、期間中何度でも回答できます。</li> <li>手前の全ての回答をコピペして残してください。</li> <li>元の文を修正することはできません。</li> <li>文意が伝わるように展開していきましょう。</li> </ol> </div> <div class = "content-explain2"> <h2>例はこちら</h2> <p>【Hiro】東京では( )雨が降っていた。</p> <p>【お名前】( )東京では季節はずれの雨が降っていた。</p> <p>【お名前】高層ビルの立ち並ぶ東京では季節はずれの雨が( )降っていた。</p> <p>【お名前】高層ビルの立ち並ぶ東京では季節はずれの雨がしとしとと降っていた( )。</p> <p>【お名前】高層ビルの立ち並ぶ東京で( )は季節はずれの雨がしとしとと降っていたのを見て昨年のことを思い出した。</p> </div> <div class = "content-explain2"> <h2>今回のテーマ:桃太郎</h2> <img src = "../image01.jpg"> <client-only placeholder="Loading..."> <Memo /> </client-only> </div> <div class = "footer"> </div> </div> </div> </template> <script> export default {} </script> <style> h2{ font-size:30px; } .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } .content-explain{ margin: 40 auto; height: 150px; } .content-explain2{ margin: 40 auto; height: 200px; } .title { font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: 300; font-size: 50px; color: #35495e; letter-spacing: 1px; } .subtitle { font-weight: 300; font-size: 42px; color: #526488; word-spacing: 5px; padding-bottom: 15px; } .links { padding-top: 15px; } .footer{ height: 200px; } </style>memo.vue<template> <div> ★★★つくられた物語★★★ <!-- {{allData}} --> <ul v-for="data in allData" :key="data.id" class="menu-list" > <li> {{data.name}} / {{data.answer}} </li> </ul> <p> <input v-model="name" placeholder="名前"> <input v-model="answer" placeholder="回答"> <button v-on:click='post'>送信</button> </p> </div> </template> <script> import firebase from "firebase/app"; import "firebase/firestore"; export default { components: {}, data(){ return{ db: {}, allData: [], name: '', answer: 'ここに回答', } }, methods: { init: () => { const config = { apiKey: "AIzaSyBvouEQqs3Cqz_F-re7SCW-FLvPuISQsnc", authDomain: "protoout-2359e.firebaseapp.com", databaseURL: "https://protoout-2359e.firebaseio.com", projectId: "protoout-2359e", storageBucket: "protoout-2359e.appspot.com", messagingSenderId: "1085072592944", appId: "1:1085072592944:web:30da6171b08c3734979df5", measurementId: "G-T3FMEWMY87" }; // Initialize Firebase firebase.initializeApp(config); }, post: function(){ const testId = firebase.firestore().collection('memos').doc().id; //ユニークなIDを生成 const docRef = firebase.firestore().collection('memos').doc(testId); const setAda = docRef.set({ name: this.name, answer: this.answer }); this.get(); }, get: function(){ this.allData = []; firebase.firestore().collection('memos').get().then(snapshot => { snapshot.forEach(doc => { // console.log(doc); this.allData.push(doc.data()); }) }); } }, mounted(){ this.init(); this.get(); }, } </script> <style> </style>
- 投稿日:2020-09-10T00:25:56+09:00
importを使ってみる
環境
初書:2020/09/10前書き
javascriptにも名前空間というのがないのか調べていたところ、importというのを見つけたので、
名前空間ではないがそれっぽい動作になりそうなので使ってみる。前提条件
・import / exportはES6以降でしか対応していない。そのため、IE対応のHPを作成しようと考えている場合は使えない。
・import / exportを使用した場合、宣言に関わらずstrict モードになる。
・ローカル環境では動作しない。(httpもしくはhttpsから始まれば使える。Finderなどでhtmlファイルダブルクリックで開くようなものは動かない、らしい)記述方法
ライブラリ側
ライブラリとして提供したいものにexport関数を付ける
lib.jsexport function id(val){ return document.getElementById(val); } export var pi = 3.14;ちなみにエクスポート可能なものは、var, let, const, function, class
また、他にもexportする方法はいくつかあるので、詳細はMDN exportで利用側
使う方は何ていうのが正解なのだろうか。。?
まぁとりあえず、こちらの方は上記のライブラリをインポートする必要がある
今回は上記のlib.jsと、使う側のmain.jsは同じフォルダにあると仮定する。main.jsimport {id, pi} from "./lib.js"; console.log(id("aa").innerHTML); console.log(pi);もしくは
main.jsimport * as lib from "./lib.js"; console.log(lib.id("aa")); console.log(lib.pi);上の場合は、宣言された名前の通りに使用する事が可能かつ必要なもののみインポート可能で、下の場合は、複数のライブラリを読み込む際に名前の競合を避ける事ができる。
ちなみに*は"全ての"という意味。ここを{id, pi}
に置き換えることは出来ない。
こちらも他にimportする方法はいくつかあるので、詳細はMDN import使ってみる
使う際は、main.jsのみ読み込めばいい。また
type="module"
を付ける必要がある。
で、このmoduleというのが普通のjavascriptと少し異なる箇所になる。詳細は、JavaScript モジュールを読んでいただくとして、普通のscriptを読み込むのと異なり、
・ローカルで実行出来ない(前提条件に記述したもの)
・自動的にstrictモードになる(前提条件に記述したもの)
・defer属性を付けなくても遅延実行される
・グローバル環境からアクセス出来ない
という制約が出来ます。1ということでhtmlの記述
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> </head> <body> <div id="aa">aaaaaa</div> <script type="module" src="main.js"></script> </body> </html>これで、コンソール上には
<div id="aa">aaaaaa</div>
と3.14
が表示されているはず。注意点
"使ってみる"の箇所の太字欄にも記載してある通り、グローバル環境からアクセス出来なくなる。
そのため、例えばデバッグのためにコンソールから変数にアクセスしようとしても失敗する。
また、main.js
とmain2.js
のように複数読み込んでも、import/exportの関係では無い場合はお互いの変数や関数にアクセスすることは出来ない。
逆にいうと、ファイルの全体を(() => {})()
で囲わなくてもグローバル変数を荒らさない。
また、main.js内からグローバル変数にアクセスすることは可能。参考サイト
・JavaScriptのimport文を使ってみた
・JavaScript 別ファイルからクラスをインポートしたい
・import
・export