- 投稿日:2019-05-30T22:28:09+09:00
botuiを試してみた
botuiとは
botuiとは、手軽にチャットボットを作ることができるjavascriptライブラリのこと。
CDNでも読み込むことができ、webが繋がる環境であればとても簡単に導入することができるのが素晴らしいです。最近、botuiに触れる機会があったので、忘れないように書き留めておこうと思います。
はじめに、必要なファイルをインポートする
botuiを利用する場合、ファイルを落としてきても良いですが、
CDN経由でjavascriptとcssを読み込む方こともできます。
そこは好みや実行環境に応じてそれぞれで使い分けだと思います。
今回はCDN経由で読み込んでいます。インポートするファイルは次の4ファイル
インポートファイル.html<link rel="stylesheet" href="https://unpkg.com/botui/build/botui.min.css" /> <link rel="stylesheet" href="https://unpkg.com/botui/build/botui-theme-default.css" /> <script src="https://cdn.jsdelivr.net/vue/latest/vue.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/botui/0.3.9/botui.min.js"></script>botuiは、裏でvue.jsを使用しているみたいで、vue.jsもインポートしなければ動かない。
ちなみにIEのバージョンが古かったりして、promiseが使えない場合は、以下のファイルのいずれかをインポートすれば使えるようになる可能性があります。
(どれも同じものなので、お好みでいずれかをインポートしてpromiseを使えるようにしてみましょう)priomis.html<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.js"></script> <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script> <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script>使い方
以下のようにdivタグにidを指定して、そのタグ内でブロックを作成することで準備完了
botui.html<div id="bot_app" style="padding-left: 40%; padding-right: 40%;"> <bot-ui></bot-ui> </div>あとは、javascriptを書いていくだけですが、書き方も結構シンプル。
以下のように、BotUIをnewして引数にdivタグのidを指定しする。
肝心なチャットの部分は、botui.message.bot({}).then(function(){ 別の処理 })
でチャットをつなげていく感じです。
(名前空間は、定義していなくても全然問題ないです。)botui.jsvar BOTUI = BOTUI || {}; BOTUI.botui = new BotUI("bot_app"); ... BOTUI.init = function(){ BOTUI.botui.message.bot({ delay: 300, content: "Hello !!" }) .then(function(){ BOTUI.user(); }); } ... BOTUI.init();一応動くものをgithubに置いていますので、よかったら見てみてください。
エラーなく起動すると以下のようにチャットが始まります。
以上となります。
さらに詳しく知りたい人は公式サイトを参照してください!注意点
botuiでページリンクを表示させたいときは注意が必要。
最新のチャットで止まっているときは、リンクとして機能するのですが、チャットが進んでいくと、リンクが機能しなくなります。でも、全体的にめっちゃ簡単にチャットが作れるので、やっぱり素晴らしいライブラリですよね!!
- 投稿日:2019-05-30T22:11:44+09:00
S3にアップロードした画像をAjaxで取得する
やりたいこと
デバッグ等で、S3にアップロードされているかどうかをチェックした後に、
URLを差し替えて画像を表示したいというニッチなニーズに遭遇。普通に何も考えずに実装すると
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
という感じに怒られる。
ちゃんとヘッダを設定して、クロスドメインを有効にしましょうという話。S3のCORS設定
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>AjaxでcrossDomainを有効にする
_.each($('.s3-image-debugger'), function(img, _idx) { var $img = $(img); var src = $img.attr('src'); if (src && src.match(/s3-ap-northeast-1.amazonaws.com/)) { $.get(src, {crossDomain: true}).fail(function() { $img.attr('src', src.replace('production', 'test')); }); } });かなり急いで作ったので雑ですが、意図した挙動になりました
![]()
- 投稿日:2019-05-30T18:25:29+09:00
Create an argo workflow(custom resource) with Javascript Kubernetes Client
Introduction
Describes how to create argo workflow (Custom Resource) with Javascript Kubernetes Client.
I saw the kubernetes-client/javascript/issues#144,but I felt it did not explain enough, so I will share it here.How to
I explain using argo.
I don't explain KubeConfig etc. because it is not related to custom resource.If you need further explanation, please see the following document.
- https://github.com/kubernetes-client/javascript/blob/master/README.mdlet kc = new k8s.KubeConfig(); kc.loadFromOptions(kubeConfig); let kc = await k8sCli.getClient(); let k8sApi = await kc.makeApiClient(k8s.Custom_objectsApi); //NOTE.1 let yaml = await readManifestFile(workflow.file); // NOTE.2 k8sApi.createNamespacedCustomObject('argoproj.io', 'v1alpha1', 'default' , 'workflows', yaml).then((res) => { // any. }).catch((e) => { // any. });NOTE.1 Use Custom_objectsApi
kc.makeApiClient(k8s.Custom_objectsApi)Use
Custom_objectsApias an argument for makeApiClient.
you can call createNamespacedCustomObject which allows you to specify the custom resource group and version.NOTE.2 createNamespacedCustomObject
k8sApi.createNamespacedCustomObject('argoproj.io', 'v1alpha1', 'default' , 'workflows', yaml).then((res) => { // any. }).catch((e) => { // any. });The arguments for createNamespacedCustomObject can be understood by checking the contents of crd.
$ kubectl get crd workflows.argoproj.io -o json |jq -r ".spec" { "conversion": { "strategy": "None" }, "group": "argoproj.io", # ★group "names": { "kind": "Workflow", "listKind": "WorkflowList", "plural": "workflows", # ★plural "shortNames": [ "wf" ], "singular": "workflow" }, "scope": "Namespaced", "version": "v1alpha1", # ★version "versions": [ { "name": "v1alpha1", "served": true, "storage": true } ] } $Refernece
- 投稿日:2019-05-30T18:17:36+09:00
ゼロから始める生体認証webアプリケーション作成(3)localhost上での画像加工
はじめに
はじめに謝らせていただきます...
前回の記事で使ったopencv4nodejsですが....localホスト上で使う方法がわからなかったのでクビになりました。
代わりに本家opencvさんを使います。1.node.js+express+jage環境構築
Expressの開発環境構築~デバッグ環境構築 ここを参考にしましょう。
私が書くより絶対わかりやすい。expressの中身はこんな感じになっています。
作ったスクリプトなどはpublicにHTML(拡張子が.jade似合っているけど...)はviewsに入れます。
他もいじれるけど、私みたいな初心者は触らないに限る。2.スクリプトの修正
受け取った画像を100x100に縮小して、グレイスケール化します
processing_cv.jset imgElement = document.getElementById('imageSrc'); let inputElement = document.getElementById('fileInput'); inputElement.addEventListener('change', (e) => { imgElement.src = URL.createObjectURL(e.target.files[0]); }, false); imgElement.onload = function() { let input_img = cv.imread(imgElement); let resize_img = new cv.Mat(); let dsize = new cv.Size(100, 100); // 100x100にリサイズ cv.resize(input_img, resize_img, dsize, 0, 0, cv.INTER_AREA); input_img.delete(); let gray_img = new cv.Mat(); //グレイスケール化 cv.cvtColor(resize_img,gray_img, cv.COLOR_RGBA2GRAY, 0); cv.imshow('canvasOutput', gray_img); resize_img.delete(); gray_img.delete(); }; function onOpenCvReady() { document.getElementById('status').innerHTML = 'OpenCV.js is ready.'; }前回とやっていることは同じですが....ずいぶん長くなってしまいます。このスクリプトの保存場所はpublic/javascripts/です。
3.layout設定
opencv公式チュートリアルにあったテンプレートを参考にjade仕様に書き換えました。
layout.jadedoctype html html head meta(charset='utf-8') title= title link(rel='stylesheet', href='/stylesheets/style.css') body h2 Hello OpenCV.js p#status OpenCV.js is loading... div .inputoutput img#imageSrc(alt='No Image') | .caption | imageSrc input#fileInput(type='file', name='file') | .inputoutput canvas#canvasOutput | .caption canvasOutput script(src="/javascripts/processing_cv4.js",type='text/javascript') script(async='', src='https://docs.opencv.org/3.4.1/opencv.js', onload='onOpenCvReady();', type='text/javascript')4.表示の確認
localhost:3000に繋ぐとこんな感じに表示されます
次に有名なlennaの画像を与えてみます。
縮小してグレイスケール化した画像が出力されたことが確認できました。
まとめ
localhost上での画像加工を行うことができた。
次回は、少しjavascriptから離れて、顔認証システムを機械学習を用いて実装します。
- 投稿日:2019-05-30T18:04:37+09:00
Selenium で web サーバ上のファイルの中身を取得する
はじめに
Selenium を使用して, web サーバ上のファイルの中身を取得する方法です.
ダウンロードボタンを Selenium に押させて, ファイルを実際にダウンロードした後に中身を取得することも可能ですが,
その場合, ダウンロードしたファイル名を特定する必要があり, それが困難だったりします.そこで, JavaScript の XMLHttpRequest() を使いファイルの中身1を取得する方法を, ケース毎のサンプルコードで紹介します.
ケース1: href の場合
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="https://localhost/files/hoge.csv">download</a> </body> </html>main.php$webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub'); $webDriver->get('https://localhost/index.html'); $url = $webDriver->findElement(WebDriverBy::tagName('a'))->getAttribute('href'); $responses = $webDriver->executeScript(implode('', ['var xhr = new XMLHttpRequest();', 'xhr.open("GET", "' . $url . '", false);', 'xhr.overrideMimeType("text/plain; charset=Shift_JIS");', 'xhr.send();', 'return xhr.responseText;'])); var_dump($responses); $webDriver->quit();ケース2: form method="get" の場合
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="https://localhost/files" method="get"> ... <input type="submit" value="download"> </form> </body> </html>main.php$webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub'); $webDriver->get('https://localhost/index.html'); $responses = $webDriver->executeScript(implode('', ['var form = document.querySelector("form");', 'var xhr = new XMLHttpRequest();', 'xhr.open("GET", form.action + "?" + new URLSearchParams(new FormData(form)).toString(), false);', // form.method => get, form.action => 'https://localhost/files/' 'xhr.overrideMimeType("text/plain; charset=Shift_JIS");', 'xhr.send();', 'return xhr.responseText;'])); var_dump($responses); $webDriver->quit();ケース3: form method="post" の場合
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="https://localhost/files" method="post"> ... <input type="submit" value="download"> </form> </body> </html>main.php$webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub'); $webDriver->get('https://localhost/index.html'); $responses = $webDriver->executeScript(implode('', ['var form = document.querySelector("form");', 'var xhr = new XMLHttpRequest();', 'xhr.open(form.method, form.action, false);', // form.method => post, form.action => 'https://localhost/files' 'xhr.overrideMimeType("text/plain; charset=Shift_JIS");', 'xhr.send(new FormData(form));', 'return xhr.responseText;'])); var_dump($responses); $webDriver->quit();注意事項
スクレイピングを禁止しているサイトもありますのでご利用の際はお気をつけくださいませ.
今回, web サーバ上のファイル = CSV ファイル を想定 ↩
- 投稿日:2019-05-30T17:59:31+09:00
Javascript 配列の最大値と最小値を求める
何度かいても忘れるやつ
MAX/MINを求めたい配列listがあるとしてmin.jsconst Min = list.reduce((a,b)=>a<b?a:b)max.jsconst max = list.reduce((a,b)=>a>b?a:b)
- 投稿日:2019-05-30T17:42:54+09:00
Nuxt.jsでカレンダーを自作してみた
概要
チームで運用するスケジュール表を自作しようと思い、NuxtとAPIで作成できないか試行錯誤してみた内容を
記録しておく為の記事になります。CSSの説明は省きますが、完成イメージはこんな感じの物を紹介していきます。
※今回API通信の説明は行いません。また、Nuxt.jsの環境構築などはできている物と仮定します。
pages
index.vue<template> <div> <Calendar /> </div> </template> <script> import Calendar from '~/components/schedule/Calendar.vue' export default { components: { Calendar } } </script>今回は機能の一部となる
components/schedule/Calendar.vueを読み込んでそれを表示させているだけです。store
スケジュールを表示するための『日付情報』などを保持する「schedule.js」ファイルと
選択されているチームの情報を表示するための『メンバー情報』などを保持する「member.js」ファイルを作成しました。schedule.jsの説明
schedule.jsexport const state = () => ({ year: new Date().getFullYear(), month: new Date().getMonth()+1, today: new Date().getDay() }) export const mutations = { changeCalendar(state, add){ if(state.month + add > 12){ state.year += add state.month = 1 }else if(state.month + add < 1){ state.year += add state.month = 12 }else{ state.month += add } } }states
まず、「schedule.js」ファイルで保持するstatesは3つで、
『year:年』『month:月』『today:日付』です。そのままですね
初期値は当日の「年・月・日」を取得するようにしてあります。mutations
mutaionは、store内のstateを変更するための関数です。
changeCalendarでは引数で渡されている値と現在の『state.month』を足した値によって処理を分けています。
後程HTML部分で説明しますが、ここの引数は「-1」か「1」が渡されるようになっています。
『state.month』と『引数add』が「12」より多かった場合、「1」より小さかった場合は
『state.year』の方に『add』を足すことで、年数を変更するようにし、『state.month』を1月か12月にしています。
それ以外の場合は、『state.month』にそのまま『add』を足しています。member.jsの説明
member.jsexport const state = () => ({ // チーム名とチームに所属するメンバーのkey teams:[ { key:0, name:"チーム①", member:[0,1], flag:false }, { key:1, name:"チーム②", member:[2,3], flag:false }, { key:2, name:"チーム③", member:[4,5,6], flag:false } ], // メンバーのkeyと名前 members:[ { key: 0, name: 'テスト マン1'}, { key: 1, name: 'テスト マン2'}, { key: 2, name: 'テスト マン3'}, { key: 3, name: "テスト マン4"}, { key: 4, name: 'テスト マン5'}, { key: 5, name: 'テスト マン6'}, { key: 6, name: 'テスト マン7'} ], // 選択されているチームのメンバー selectMember:[] }) export const mutations = { // チーム選択時呼ばれるmutation selectTeam(state, select){ state.teams.map((val) => { val.flag = false }) // フラグを立てる state.teams[select].flag = true // 選ばれているチームのメンバーのIDを格納 state.selectMember = state.teams[select].member } }states
「member.js」で保持するstatesも3つです。それぞれの概要はコメントアウトを参照してください。
「state.teams」は『name:チーム名』『member:チームに所属しているメンバーのキー』
『flag:スケジュール表に表示しているか』という構造になっています。「state.members」は『key:ユーザーキー』『name:ユーザーネーム』という構造です。
「state.selectMember」は選択されているチームメンバーのキーを格納する用のstateになります。
mutations
『selectTeam』では選択されたチームの
まず、map関数で「state.teams」の『flag』を全てfalseにします。
その後、選択されたチームの『flag』をtrueにしています。後程説明しますが、
この『flag』はclass名の出し分けで使われるものになります。次に、『state.selectMember』に選ばれているチームのメンバーキーを格納します。こちらも後程説明しますが、
このキーの数だけループを回し、htmlを出力する処理を行います。components
下記例で記載はしてありますが、CSSの説明については省かせていただきます。
schedule/Calendar.vue<template> <div class="mainschedule"> <div class="heading"> <div class="schedule-ttl">schedule</div> </div> <div class="teams"> <div class="teambox" v-for="team in teams" :key="team.key" :class="{active: team.flag}" @click="showMember(team)"> {{ team.name }} </div> </div> <div class="schedule-month"> <span class="arrow" @click="changeMonth(-1)"><<</span> {{ schedule.year }}年{{ schedule.month }}月 <span class="arrow" @click="changeMonth(1)">>></span> </div> <div class="calendar-box"> <table> <tr> <th class="name-field"></th> <th v-for="day in lastday" :key="day">{{ day }}</th> </tr> <tr> <td class="name-field"></td> <td v-for="day in lastday" :key="day">{{ week[(firstweek + day) % 7] }}</td> </tr> <tr v-for="num in selectMember" :key="num"> <td class="name-field">{{ members[num].name }}</td> <td v-for="day in lastday" :key="day"></td> </tr> </table> </div> </div> </template> <script> export default { computed: { teams () { return this.$store.state.member.teams }, members () { return this.$store.state.member.members}, selectMember () { return this.$store.state.member.selectMember}, schedule () { return this.$store.state.schedule }, // 月初曜日の取得 firstweek () { return new Date(this.$store.state.schedule.year, this.$store.state.schedule.month-1, 1).getDay()-1 }, // 月末の日数の取得 lastday () { return new Date(this.$store.state.schedule.year, this.$store.state.schedule.month, 0).getDate() } }, methods: { showMember(team) { this.$store.commit('member/selectTeam',team.key) this.selectMember = team.member }, changeMonth(add) { this.$store.commit('schedule/changeCalendar', add) } }, data() { return{ week: ["日", "月", "火", "水", "木", "金", "土"] } } } </script> <style lang="scss"> .mainschedule { padding: 10px; .heading{ padding: 25px; .schedule-ttl { font-size: 32px; text-align: center; border-bottom: solid 2px #b2bec3; } } .teams { display: flex; justify-content: center; .teambox { display: flex; justify-content: center; border: solid 1px #dfe6e9; padding: 10px; margin: 10px; width: 300px; &.active { background: #dfe6e9; color: #2d3436; } } } .schedule-month { font-size: 24px; padding: 10px; text-align: center; .arrow { font-size: 20px; cursor: pointer; } } .calendar-box{ display: flex; justify-content: center; align-items: center; } table { th,td { width: 35px; height: 35px; text-align: center; border: solid 1px; } .name-field { width: 150px; } } } </style>script部分
script部分<script> export default { computed: { teams () { return this.$store.state.member.teams }, members () { return this.$store.state.member.members}, selectMember () { return this.$store.state.member.selectMember}, schedule () { return this.$store.state.schedule }, // 月初曜日の取得 firstweek () { return new Date(this.$store.state.schedule.year, this.$store.state.schedule.month-1, 1).getDay()-1 }, // 月末の日数の取得 lastday () { return new Date(this.$store.state.schedule.year, this.$store.state.schedule.month, 0).getDate() } }, methods: { showMember(team) { this.$store.commit('member/selectTeam',team.key) this.selectMember = team.member }, changeMonth(add) { this.$store.commit('schedule/changeCalendar', add) } }, data() { return{ week: ["日", "月", "火", "水", "木", "金", "土"] } } } </script>computed
ページロード時に実行される関数です。それぞれの関数で呼び出しているのは下記の様なものになります。
関数名 内容 teams Storeのmember.js内の『state:teams』を
呼び出しています。members Storeのmember.js内の『state:members』を
呼び出しています。selectMember Storeのmember.js内の『state:selectMember』を
呼び出しています。schedule Storeのschedule内の『state』を
丸ごと呼び出しています。firstweek Storeのschedule内の『state:year』と
『state:month』を元に表示する月の
最終日の曜日を取得しています。lastday Storeのschedule内の『state:year』と
『state:month』を元に表示する月の
月末日を取得しています。computedでStore内のstateを呼び出すことによって、html文内で毎回長々と呼び出し分を書く必要がなくなります。
firstweekとlastdayの取得の仕方の詳細は省かせていただきます。methods
イベントによって実行される関数を定期しています。
関数名 内容 showMember Storeのmember.js内の『mutations:selectTeam』
を呼び出しています。
引数には選択したチームのidが渡されています。changeMonth Storeのchedule.js内の『mutations:changeCalendar』
を呼び出しています。
引数には『<<』を選択したら「-1」
『>>』を選択したら「1」を渡しています。methods内ではStore内の情報を直接変更できなくなっています。
そのため、それぞれのmutationsにcommitを行い、stateを書き換える必要があります。data
ページ内で使用できる変数を作成しています。曜日の配列はわざわざstateで管理する程でもないと
思ったのでこちらでweekという変数として設定しました。html部分
<div class="teams"> <div class="teambox" v-for="team in teams" :key="team.key" :class="{active: team.flag}" @click="showMember(team)"> {{ team.name }} </div> </div> <div class="schedule-month"> <span class="arrow" @click="changeMonth(-1)"><<</span> {{ schedule.year }}年{{ schedule.month }}月 <span class="arrow" @click="changeMonth(1)">>></span> </div> <div class="calendar-box"> <table> <tr> <th class="name-field"></th> <th v-for="day in lastday" :key="day">{{ day }}</th> </tr> <tr> <td class="name-field"></td> <td v-for="day in lastday" :key="day">{{ week[(firstweek + day) % 7] }}</td> </tr> <tr v-for="num in selectMember" :key="num"> <td class="name-field">{{ members[num].name }}</td> <td v-for="day in lastday" :key="day"></td> </tr> </table> </div>チーム選択出力部分
まず、チーム選択部分出力の説明です。
<div class="teams"> <div class="teambox" v-for="team in teams" :key="team.key" :class="{active: team.flag}" @click="showMember(team)"> {{ team.name }} </div> </div>
v-for="team in teams" :key="team.key"
v-forで『teams( computed内にある関数で取ってきたstate )』がなくなるまでループを回しています。
phpのforeach文で言うと、下記の様な事をしていると考えていただければ良いかと思います。foreach(teams as team){ echo "hoge"; }v-forを使用する際にはkeyの設定が必要になります。今回は『teams.key』をkeyに設定しています。
:class="{active: team.flag}"
:classでclassを付けるか付けないかの条件分岐をする事ができます。今回の場合、『team.flag』が
trueだった場合には、「active」というクラスが付与されるようになっています。
このクラスでどのチームが選択されているか分かりやすいようCSS調整しています。
@click="showMember(team)"
この要素がクリックされた時に実行される『methods』の関数を指定しています。
この場合はshowMemberの関数が呼び出され、引数で選択された『team』の情報が渡されます。
{{ team.name }}
それぞれのチームの名前を表示しています。月変更部分
<div class="schedule-month"> <span class="arrow" @click="changeMonth(-1)"><<</span> {{ schedule.year }}年{{ schedule.month }}月 <span class="arrow" @click="changeMonth(1)">>></span> </div>
<span class="arrow" @click="changeMonth(-1)"><<</span>
『 << 』部分がクリックされた時に『methods』内のchangeMonth関数が呼ばれるように指定しています。
ここの場合は先月に戻りたいので、引数には「-1」を渡しています。カレンダー部分
<table> //日付 <tr> <th class="name-field"></th> <th v-for="day in lastday" :key="day">{{ day }}</th> </tr> //曜日 <tr> <td class="name-field"></td> <td v-for="day in lastday" :key="day">{{ week[(firstweek + day) % 7] }}</td> </tr> //メンバー <tr v-for="num in selectMember" :key="num"> <td class="name-field">{{ members[num].name }}</td> <td v-for="day in lastday" :key="day"></td> </tr> </table>日付・曜日
日付と曜日部分の
v-forは全く同じで、computedで取得したlastday( 表示月の最終日 )
の回数分だけループを回しています。
<th class="name-field"></th>
選択されたチームの名前が曜日の下の行から入るので、空文字で設置してあります。
{{ week[(firstweek + day) % 7] }}
曜日の上記箇所については、script内のdata()で設定した『week』変数を使用しています。
firstweekはcomputedで取得した月初日の曜日の引数です。こちらと現在の日付を足して7で割ることで
現在の曜日に対応する配列番号を指定する事ができます。メンバー
<tr v-for="num in selectMember" :key="num">
ここでは<tr>をselectMemberの数だけループを回しています。
<td class="name-field">{{ members[num].name }}</td>
上記でselectMemberに対応するメンバーの名前を取得し表示しています。
<td v-for="day in lastday" :key="day"></td>
ここのループは日付・曜日部分のループと全く同じで、中身は空で渡しています。以上で完成です
!
おわりに
かなり長々となってしまいましたが、最後まで読んでくださった皆様ありがとうございました!
中々複雑な構造になってしまいましたが、もっとスマートなStoreの呼び出し方や、
v-forの回数を減らせる記述方法などがあれば教えていただきたいです。
- 投稿日:2019-05-30T17:03:57+09:00
Catapultのトランザクションを解剖してみよう!
今回は送信に成功したトランザクションの中身を解剖していきます。
まずは、Catapult Transaction Viewer を作成したのでそちらをご覧ください。
http://xembook.net/xembook-sdk/snapshot/20190526/examples/270_transaction.html?hash=047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFDmeta
hight 456616,0 compact(hight) 456616 hash 047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFD id 5CEF883528174C00014328AA index 0 merkleComponentHash 047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFDカッコつきの値はViewerでコンバートをかけたものが表示されておりAPIのレスポンスには含まれません。
hight : 何番目のブロックに刻まれたかです。uint64で指定
compact(hight): uint64 を 計算して普通の10進数で表記します。
hash:このトランザクションのハッシュ値です。
id:このノードが管理するトランザクションの管理IDです。アクセスするノードが変わればこのid値も異なるためあまり使用しません。
index:なんの値でしょうかね?
merkleComponentHash:こちらも勉強不足でまだわかりません。transaction
deadline 952376648,23 Date(deadline) 2019-05-30 17:37:04 maxFee 0,0 compact(maxFee) 0 message type 0 message payload 48656C6C6F212058454D426F6F6B2D73646B hexToUtf8(message payload) Hello! XEMBook-sdk recipient 90E332F8DAE05D06D1B5247BC9AAF848CB4EB602B02B860979 base32.encode(recipient) SDRTF6G24BOQNUNVER54TKXYJDFU5NQCWAVYMCLZ signature 51A480353E63596294835A0ACD26CE5B5F173CEE6ED5877FC1C34557E2D1755EBB3FC43EEDC0F49DCD7FE281E1A19C6D523CB7587F306FE61A2E54E31131640A signer C695653F97C566574A181C135AE8FDD52B9386D094377B6F46882D78DE527EAB publicKeyToAddress(signer,0x90) SDRTF6G24BOQNUNVER54TKXYJDFU5NQCWAVYMCLZ type 16724 type.toString(16) 0x4154 version 36867deadline:トランザクションの有効期限です。uint64型
Date(deadline):2016-04-01 00:00 から deadline のミリ秒数だけ経過した時間を西暦表示しています。
maxFee:手数料のことかと思われます。uint64型
compact(maxFee):手数料を10進数数値で表示しています。
message type:0が平文メッセージ、1が暗号化メッセージです。
message payload:メッセージをHEX変換したものです。
hexToUtf8(message payload):HEX変換されたメッセージをutf-8に変換したものです。
recipient:受け取りアドレスをデコードしたものです。
base32.encode(recipient):受け取りアドレスをbase32でエンコードしたものです。MIJIN_TESTはSから始まります。
signature:トランザクションを署名したときのハッシュ値です。
signer:署名者の公開鍵です。
publicKeyToAddress(signer,0x90):署名者のエンコードアドレスです。
type:使用するトランザクションタイプ?です。
version:使用するトランザクションのバージョン?です。mosaics
id [853116887,2007078553] toHex(id) 77A1969932D987D7 amount 0,0 campact(amount) 0id:mosaicを一意に識別するidです。uint64型
toHex(id):10進数で表記するととても長くなるので、16進数のHEX値で表記します。
amount:送信するmosaic量です。uint64型
compact(amount):10進数表記です。大体こんな内容になりました。
次回からはいろいろなトランザクションを発行していきたいと思います!関連記事
- 投稿日:2019-05-30T17:03:57+09:00
Catapultのトランザクションを解剖しよう!
今回は送信に成功したトランザクションの中身を解剖していきます。
まずは、Catapult Transaction Viewer を作成したのでそちらをご覧ください。
http://xembook.net/xembook-sdk/snapshot/20190526/examples/270_transaction.html?hash=047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFDこの内容はCatapult APIの /transaction/{hash}の出力を必要な項目についてはコンバートをかけて見やすくしたものです。
生データはこちら(ノードの調子により接続できない場合があります。)
http://40.90.163.184:3000/transaction/047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFDmeta
height 456616,0 compact(height) 456616 hash 047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFD id 5CEF883528174C00014328AA index 0 merkleComponentHash 047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFDカッコつきの値はViewer側でコンバートをかけたものが表示されておりAPIのレスポンスには含まれません。
height : 何番目のブロックに刻まれたかです。uint64で指定
compact(height): uint64 を 計算して普通の10進数で表記します。
hash:このトランザクションのハッシュ値です。
id:このノードが管理するトランザクションの管理IDです。アクセスするノードが変わればこのid値も異なるためあまり使用しません。
index:なんの値でしょうかね?
merkleComponentHash:こちらも勉強不足でまだわかりません。transaction
deadline 952376648,23 Date(deadline) 2019-05-30 17:37:04 maxFee 0,0 compact(maxFee) 0 message type 0 message payload 48656C6C6F212058454D426F6F6B2D73646B hexToUtf8(message payload) Hello! XEMBook-sdk recipient 90E332F8DAE05D06D1B5247BC9AAF848CB4EB602B02B860979 base32.encode(recipient) SDRTF6G24BOQNUNVER54TKXYJDFU5NQCWAVYMCLZ signature 51A480353E63596294835A0ACD26CE5B5F173CEE6ED5877FC1C34557E2D1755EBB3FC43EEDC0F49DCD7FE281E1A19C6D523CB7587F306FE61A2E54E31131640A signer C695653F97C566574A181C135AE8FDD52B9386D094377B6F46882D78DE527EAB publicKeyToAddress(signer,0x90) SDRTF6G24BOQNUNVER54TKXYJDFU5NQCWAVYMCLZ type 16724 type.toString(16) 0x4154 version 36867deadline:トランザクションの有効期限です。uint64型
Date(deadline):2016-04-01 00:00 から deadline のミリ秒数だけ経過した時間を西暦表示しています。
maxFee:手数料のことかと思われます。uint64型
compact(maxFee):手数料を10進数数値で表示しています。
message type:0が平文メッセージ、1が暗号化メッセージです。
message payload:メッセージをHEX変換したものです。
hexToUtf8(message payload):HEX変換されたメッセージをutf-8に変換したものです。
recipient:受け取りアドレスをデコードしたものです。
base32.encode(recipient):受け取りアドレスをbase32でエンコードしたものです。MIJIN_TESTはSから始まります。
signature:トランザクションを署名したときのハッシュ値です。
signer:署名者の公開鍵です。
publicKeyToAddress(signer,0x90):署名者のエンコードアドレスです。
type:使用するトランザクションタイプ?です。
version:使用するトランザクションのバージョン?です。mosaics
id [853116887,2007078553] toHex(id) 77A1969932D987D7 amount 0,0 campact(amount) 0id:mosaicを一意に識別するidです。uint64型
toHex(id):10進数で表記するととても長くなるので、16進数のHEX値で表記します。
amount:送信するmosaic量です。uint64型
compact(amount):10進数表記です。大体こんな内容になりました。
次回からはいろいろなトランザクションを発行していきたいと思います!関連記事
- 投稿日:2019-05-30T16:40:40+09:00
ラジオボタンの選択状態に合わせてテキスト表示を変更させる(DOM変換)
おはこんばんちわ。
DOMビギナーのオジサンです。現在、JavaScriptでミニアプリを作成しています。
そのアプリでは、ラジオボタンの選択状態により、テキストが表示されたり、非表示になる機能があります。今回は、1~9までの数字を、ラジオボタンで選んだ属性(奇数・偶数)で表示させるコードを作ってみました!
1.HTML(index.html)
まずはinputタグで3つのラジオボタンを作りました。
「すべて」は、1~9までの数字を表示させます。
「奇数」は1~9までの奇数を、「偶数」は1~9までの偶数を表示させます。なお、プログラムの起動時には「すべて」で表示させたいのでchecked属性を入れました。
またラジオボタンを排他的にしたいので、name属性も入れています。本当はこのあたりもDOMでやりたかったのですが、JavaScriptがメチャクチャ長くなりそうなのでやめました(笑)
scriptタグの前のdivタグは、JavaScriptで1~9の数字を表示させるハコになります。
DOMで入れられるよう、number-listというクラス名を付けています。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>ラジオボタンによる表示切り替え</title> <link rel="stylesheet" href="css/styles.css"> </head> <body> <p>状態を選んでください</p> <input type="radio" name="displayButton" checked="checked">すべて <input type="radio" name="displayButton">奇数 <input type="radio" name="displayButton">偶数 <div class="number-list"></div> <!-- 1~9の数値をjsで出力させる --> <script src="js/main.js"></script> </body> </html>2.CSS(css/styles.css)
ラジオボタンが「偶数」をチェックしているときは奇数を非表示に、「奇数」をチェックしているときは偶数を非表示にします。
そのため、テキストを非表示にするhiddenクラスを用意しました。
こちらはJavaScriptで動的に使います。/* 非表示にするためのクラス */ .hidden { display: none; }3.JavaScript(js/main.js)
number-listクラスを持つdivタグの子要素に、1~9の数値を持ったdivタグを入れます。
なお、ここで作った1~9の数値を入れたdivタグにnumbersというクラスを持たせました。
これは、後で出てくる奇数・偶数判定で使います。'use strict'; // number-listクラスの子要素に、1~9の数値を入れて出力する const numberList = document.getElementsByClassName('number-list')[0]; for (let i=1; i<10; i++) { const div = document.createElement('div'); div.classList.add('numbers'); div.textContent = i; numberList.appendChild(div); }次にラジオボタンのチェック状態に合わせて、1~9までの奇数や偶数を表示させる関数を作りました。
奇数を選ぶと、偶数を持っているdivタグにhiddenクラスを付けて非表示にするといった方法です。
なお、どのボタンを選んでも、先にhiddenクラスをremoveする命令を入れています。
例えば、奇数のあとに偶数を選んだ時、それまで非表示だった偶数を表示させる必要があります。
hiddenクラスを取り除くことで、偶数が表示できるようになります。// 関数:ラジオボタンのチェック状態に合わせて表示/非表示を切り替える function displayChange() { //ラジオボタンのチェック状態を定数に入れる const allCK = document.querySelectorAll('input[type="radio"]')[0]; const oddCK = document.querySelectorAll('input[type="radio"]')[1]; if(allCK.checked == true) { //ラジオボタンが「すべて」をチェックしている時は、hiddenクラスを取る for (let i=1; i<10; i++) { const number = document.getElementsByClassName('numbers')[i-1]; number.classList.remove('hidden'); } } else if(oddCK.checked == true) { //ラジオボタンが「奇数」をチェックしている時は、偶数にhiddenクラスを付ける for (let i=1; i<10; i++) { const number = document.getElementsByClassName('numbers')[i-1]; number.classList.remove('hidden'); if(i % 2 === 0) { number.classList.add('hidden'); } } } else { //ラジオボタンが「偶数」をチェックしている時は、奇数にhiddenクラスを付ける for (let i=1; i<10; i++) { const number = document.getElementsByClassName('numbers')[i-1]; number.classList.remove('hidden'); if(i % 2 !== 0) { number.classList.add('hidden'); } } } }最後にラジオボタンのチェックが変更されたら、前述の関数が発動されるよう、addEventListenerを入れました。
//ラジオボタンのチェックを変更したときに関数を発動する const displayButton = document.getElementsByName('displayButton'); for(let i=0; i<displayButton.length; i++){ displayButton[i].addEventListener('change', displayChange, false); }4.最後に
うまくコードの説明ができていないところがあるかもしれないので、後で見直そうと思います。
もし、分かりにくいところがありましたら、ご指摘いただきますと助かります。拙文に最後までお目通しいただき、ありがとうございました!
- 投稿日:2019-05-30T16:16:50+09:00
Babelを用いた超最低限のReactのコンパイル環境の構築手順
備忘録として残します。
npmは入ってる前提。
■やり方
初期化コマンドでpackage.jsonを作成
#npm init でいくつか質問されるが面倒なので-yオプションでとりあえず全てYesで答えて自動作成 npm init -yトランスパイラ(バベル)インストール
npm install --save-dev babel-cli上記コマンド実行後、package.jsonの「devDependencies」に「babel-cli」が追加されていればトランスパイラのインストール完了
package.json{ "name": "name", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.26.0" } }React関係のプラグインをインストール
#babel-preset-react #babel-preset-es2015 #babel-preset-stage-2 npm install --save-dev babel-preset-react babel-preset-es2015 babel-preset-stage-2
babel-preset-reactbabel-preset-es2015babel-preset-stage-2の3つのプラグインのインストールが完了package.json{ "name": "name", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1" } }コンパイルするプリセットを「.babelrc」に登録する。
ちなみにwindowsで「.」始まりのファイル名にする場合は.ファイル名.でリネームできます。.babelrc{ "presets": ["react", "es2015","stage-2"] }あとちょっとで終わりです。
次にトランスパイル用のコマンドを用意。
今回は手動コンパイル用と、ファイルの更新を監視して自動コンパイルしてくれる用の2つ用意します。
後者はIDEでコーディング用です。
いちいちjsx編集→手動コンパイル→ブラウザで動作確認はさすがに面倒ですので。package.json{ "name": "name", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "babel ビルド元フォルダパス -d ビルド先フォルダパス", "buildauto": "babel -w ビルド元フォルダパス -d ビルド先フォルダパス" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1" } }これで完了です。
後は下記コマンドを実行すればコンパイルできます。#手動 npm run build#自動 #ファイルの変更を監視して、自動コンパイルしてくれます。 #コーディング中はこれで。 npm run buildauto当然ですがコンソールが開いている間だけ自動コンパイルしてくれます。
以上です。
- 投稿日:2019-05-30T16:16:50+09:00
超最低限のReactのコンパイル環境の構築手順
備忘録として残します。
npmは入ってる前提。
■やり方
初期化コマンドでpackage.jsonを作成
#npm init でいくつか質問されるが面倒なので-yオプションでとりあえず全てYesで答えて自動作成 npm init -yトランスパイラ(バベル)インストール
npm install --save-dev babel-cli上記コマンド実行後、package.jsonの「devDependencies」に「babel-cli」が追加されていればトランスパイラのインストール完了
package.json{ "name": "name", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.26.0" } }React関係のプラグインをインストール
#babel-preset-react #babel-preset-es2015 #babel-preset-stage-2 npm install --save-dev babel-preset-react babel-preset-es2015 babel-preset-stage-2
babel-preset-react、babel-preset-es2015、babel-preset-stage-2の3つのプラグインのインストールが完了package.json{ "name": "name", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1" } }コンパイルするプリセットを「.babelrc」に登録する。
ちなみにwindowsで「.」始まりのファイル名にする場合は.ファイル名.でリネームできます。.babelrc{ "presets": ["react", "es2015","stage-2"] }あとちょっとで終わりです。
次にトランスパイル用のコマンドを用意。
今回は手動コンパイル用と、ファイルの更新を監視して自動コンパイルしてくれる用の2つ用意します。
後者はIDEでコーディング用です。
いちいちjsx編集→手動コンパイル→ブラウザで動作確認はさすがに面倒ですので。package.json{ "name": "name", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "babel ビルド元フォルダパス -d ビルド先フォルダパス", "buildauto": "babel -w ビルド元フォルダパス -d ビルド先フォルダパス" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1" } }これで完了です。
後は下記コマンドを実行すればコンパイルできます。#手動 npm run build#自動 #ファイルの変更を監視して、自動コンパイルしてくれます。 #コーディング中はこれで。 npm run buildauto当然ですがコンソールが開いている間だけ自動コンパイルしてくれます。
以上です。
- 投稿日:2019-05-30T15:37:32+09:00
Webpack で ビルド時、環境ごとに読み込む環境変数を分けたい
概要
ほとんどの Laravel などのバックエンドフレームワークは環境変数ファイルが用意され、環境ごとに読み込まれる環境変数を動的に変えることができる機能が備わっています。
しかし、javascript の場合は環境変数を環境ごとに読み込む機能を自分で設定するか npm パッケージ を利用するしかなかったので、自分的ベストな方法をまとめておきます。結論
Webpack の alias 機能を使えばパッケージやライブラリなしで環境変数を分けることができます!!!!!!
設定方法
今回編集するファイルは下記ファイルになります。
webpack.config.js package.json前提環境
開発環境は node サーバをローカルにたてて開発。
本番、ステージサーバは CI・CD を利用してビルド後デプロイするといった環境で行いました。フォルダ構成
フォルダ構成は下記のとおり Webpack を利用した際の一般的な javascript の開発構成です。
環境変数をステージ毎に切り替えるための .env フォルダを用意しています。このフォルダにステージごとの環境変数を定義した js ファイルを用意します。.env |_ dev.js |_ stg.js |_ prd.js dist |_ main.js src |_ index.js node_modules package-lock.json package.json webpack.config.js環境変数ファイルの作成
.env フォルダ内に環境変数用の javascript ファイルを作成します。
dev.jsexport default { apiUrl : 'https://dev-example.com' }prd.jsexport default { apiUrl : 'https://example.com' }このような感じでステージごとの環境変数を設定していきます。
package.json の scripts 設定
package.json に
npm run <ステージ>の実行コマンドを定義します。
ステージごとに環境変数定義ファイルを動的に読み込むためにNODE_ENV=stgを設定します。
NODE_ENVはwebpack.config.jsファイルで参照できる環境変数になります。環境変数の名前は何でも大丈夫です。
下記のようにステージごとに渡してあげる環境変数を変えてあげましょう。package.json{ "scripts": { "stg": "NODE_ENV=stg webpack --mode production", "prd": "NODE_ENV=prd webpack --mode production", "start": "NODE_ENV=dev webpack-dev-server --mode development --hot --inline --watch-content-base" }, // 省略webpack の設定
webpack.config.js に alias の設定を行います。
webpack.config.jsconst environment = process.env.NODE_ENV || 'dev'; module.exports = { mode: 'development', resolve: { alias: { userEnv$: path.resolve(__dirname, `.env/${environment}.js`), }, }, // 省略 }alias は 深くネストされた javascript ファイルのパスをいちいち書くのがめんどくさい人用にエイリアスとして設定できるようにしてくれる Webpack の機能です。
この alias を利用してステージ毎に環境変数ファイルを動的に読み込むように設定しています。
process.env.NODE_ENVは package.json で定義した環境変数が取得できます。
この環境変数によって、読み込みたい環境変数が定義されたファイルを動的に変更します。
※環境変数を定義したファイルまでのパスは絶対パスで指定しましょう。userEnv$: path.resolve(__dirname, `.env/${environment}.js`)以上で設定は終わりです。
利用方法
javascript 側で環境変数を利用するときは下記のように import してあげましょう。
test.jsimport userEnv from 'userEnv'; console.log(userEnv.apiUrl);あとはローカルや CI・CDで npm コマンドを実行しましょう。
// ローカル開発環境 npm run start // CI・CDスクリプト npm run stg npm run prdこれでステージごとに環境変数を読み込むことができました。
環境変数を動的に切り替えるパッケージが結構あるみたいですが alias を使う方法だとパッケージいらずに環境変数を切り替えられるので簡単に利用できると思います。※ ちなみに node サーバではなくフロント側の環境変数の設定方法なので漏れてはいけない key などの情報は設定しないでね。全部漏れちゃいますので
環境変数用のパッケージに
cross-envやdotenvなどがありますがそれらを利用する場合の違いやメリット・デメリットがよくわかってないのでわかる人いたら教えてくださいm(_ _)m
- 投稿日:2019-05-30T15:17:16+09:00
素のJSでのスムーススクロール
どうしたいか
ページ内リンクをスムーズなアニメーションでスクロールさせたい。
条件
- スムーズな動作であること
- jQueryやライブラリは、不必要に用いないこと
- なるだけシンプルに、汎用的な構造にすること
どうしたのか
window.scrollを用いて解決しました。
MDNから引用しますと下記の部分です。スクロールの動作を変更する
スクロールの仕方を変えたいなら、options で指定してくださいwindow.scroll({
top: 1000,
behavior: "smooth"
});プロパティについて
プロパティ 説明 top 左上を基準とした、表示させたい文書の水平軸上のピクセル left 左上を基準とした、表示させたい文書の垂直軸上のピクセル behavior smooth、 instant、 auto のうちどれか一つを含む文字列。初期値は auto ※behaviorのinstantはautoと同動作で、一瞬で移動します。基本、smoothかautoかのどちらかを選択する形になるのかなと思います。
例
ページトップのボタンしかないし、定期的な改修もないよって場合なら、シンプルにこれだけで大丈夫。
index.js// EventTargetには、対象となる要素の参照を入れください。 EventTarget.addEventListener('click', (e) => { e.preventDefault(); window.scroll({ top: 0, behavior: 'smooth' }); });ただ、ちょっと汎用的とは言い難いので、私の場合は下記のようにしました。
index.js// aタグ要素の参照を取得 const links = document.querySelectorAll('a[href^="#"]'); // 各aタグにクリックイベントを設定 for ( let i = 0; i < links.length; i++ ) { links[i].addEventListener('click', (e) => { // デフォルトのイベントをキャンセル e.preventDefault(); // 対象(aタグ)のY軸の絶対座標を取得 const elemY = links[i].getBoundingClientRect().top; // 現在のスクロール量を取得 const scrollY = window.pageYOffset; // 対象までのスクロール量を算出 const top = elemY - scrollY; window.scroll({ top: top, // スクロール量の設定 behavior: 'smooth' // スクロール動作の設定 }); }); }最後に
このやり方を知るまでは、velocity.jsを使って、スムーススクロールを実装していました。
簡単なWEBサイトでもjQueryを入れて、前述のvelocity.jsを入れて・・・。ということをしていたので、かなり無駄なことをしていたなと思います。ちなみにこのやり方は、今どきのスムーズスクロール(2019年版) - to-R Mediaでも解説されていました。さすが西畑先生です。
- 投稿日:2019-05-30T14:52:22+09:00
[Vue.js]filterをコンポーネントのscript内で使えるようにするメモ
やりたいこと
filterをコンポーネントのscript内で使えるようにしたい
ダメな例
<script> export default { name: 'ThePieGraph', methods: { fillData() { this.datacollection = { datasets: [ { data: [ 変数 | filter ], // ↑★ここにfiletrを置きたいが、こういう書き方だとダメ }, ], } }, }, }やったこと
いい例
src直下にpluginsディレクトリを切って、その中にfilter.jsを置く
prototypeを用いると、コンポーネントのscript内でも、関数としてfilterを呼び出すことができる
/** * 配列をオブジェクトから名前抽出 * @param {String} key * @param {{}} obj * @returns {String} */ const objToName = (key, obj) => { if (obj[key] !== undefined) { return obj[key] } return key } export default { install(vue) { // 数値を小数点に変換する const decimalPointShaping = value => { // console.log('decimalPointShaping',value) if (!Number(value)) { return value } return Math.round(value) } vue.filter('decimalPointShaping', decimalPointShaping) // 全体から平均的なパーセントを割り出す const percentageCalculation = (value, sum) => { console.log('percentageCalculation', value, sum) return (value / sum) * 100 } vue.filter('percentageCalculation', percentageCalculation) // APIの結果から人柄の結果を変換する const personalityConversion = value => objToName(value, big4) vue.filter('personalityConversion', personalityConversion) // APIの結果からbig5の結果を変換する const big5Conversion = value => objToName(value, big5) vue.filter('big5Conversion', big5Conversion) // 全体から平均的なパーセントを割り出し、かつ数値を整数に整形する const percentAndDecimal = (value, sum) => decimalPointShaping(percentageCalculation(value,sum)) /********************** 対象の記述↓ **********************/ // フィルターをコンポーネントスクリプト内で使えるようにする vue.prototype.$customFilter = { decimalPointShaping, personalityConversion, big5Conversion, percentageCalculation, percentAndDecimal, } /********************** 対象の記述↑ **********************/ }, }使い方
<script> export default { name: 'ThePieGraph', methods: { fillData() { this.datacollection = { datasets: [ { data: [ this.$customFilter.percentAndDecimal(this.big4_average,this.big4_sum), this.$customFilter.percentAndDecimal(this.big4_reserved,this.big4_sum), this.$customFilter.percentAndDecimal(this.big4_role_models,this.big4_sum), this.$customFilter.percentAndDecimal(this.big4_self_centered,this.big4_sum), ], //★↑ここ }, ], } }, }, }
- 投稿日:2019-05-30T14:49:03+09:00
はじめての Nuxt.js
Nuxt はずっと前から気にはなってて、でも「今回は SSR しないからいいや」とか「今回は静的サイトジェネレーターで作るからいいや」とか思って避けてきたけど、なんか最近 Nuxt 使うって話を 3 回くらい聞いたので、ちょっと使ってみて構成とか眺めてみようと思う
先に使ってみた感想を述べておくと
- Nuxt の公式ドキュメントがちゃんと作られているので、これだけ見れば大体解決した
- 後述してますが TypeScript とか Vuex とかとても簡単に導入できるので感動した
- webpack 周りが隠蔽されてる
- 前に VueCLI v2 使ってみた時はけっこうビルド周りがむき出しになってた気がする(VueCLI v3 ではこの辺解決されてるっぽい)
- Vue で SSR する時に Nuxt を使うという印象を持っていたが、SSR モードか SPA モードか選べたので考えが違った
- フロントエンドチームが多いチームであれば、Angular などのガッチリ系フレームワークも検討しようと思っていたが、Vue が好きなら Nuxt でもいいかもしれない
- TypeScript のサポートも昔よりはかなり良くなってて、Vue v3 がリリース(今年リリースされる?)されたら Vue 自体が TypeScript で作り直されるので、さらに Vue + Nuxt + TypeScript 環境が良くなっていくのではないかと思う
今回作業したリポジトリは以下
https://github.com/kurosame/nuxt-boilerplateインストール
create-nuxt-appを使うと良さそう
npx でいけるとかすばらしいnpx create-nuxt-app nuxt-boilerplateプロジェクト名は GitHub のリポジトリ名にした
? Project name nuxt-boilerplate ? Project description My groundbreaking Nuxt.js project ? Use a custom server framework express ? Choose features to install Progressive Web App (PWA) Support, Linter / Formatter, Prettier, Axios ? Use a custom UI framework vuetify ? Use a custom test framework jest ? Choose rendering mode Universal ? Author name kurosame ? Choose a package manager yarnとりあえず全部入れといたが、以下に該当する場合はインストールから除外してもいいと思う
(でも環境を CLI ツールで管理する時点で環境周りをメンテナンスすることは基本的に無いと思うので、全部入れてしまっても問題無いと思う)
- SSR しない
? Use a custom server framework None ? Choose rendering mode SPA
- モバイル対応しない
? Choose features to install Progressive Web App (PWA) Supportを選択しない
- HTTP リクエストしない(ほぼ無いと思うが)
? Choose features to install Axiosを選択しない
- デザインは全部自前
? Use a custom UI framework Noneサーバーの Node フレームワークはあまり詳しくないが、特に今使っているのが無ければ Express か Koa あたりを選んでおけば良いと思う
Vuetify は 1 年くらい使ってるが、かなり完成度高い UI フレームワークと思っているので、入れた
Linter や Prettier や Jest などのテストフレームワークは必須で入れておいた方が良い実行
package.json を開くと、いくつかスクリプトが設定してあるが、とりあえず以下を実行
yarn dev2 回目以降は
node_modules/.cacheの下にbabel-loaderのキャッシュを作ってる影響からか、少し速くなっている
でもホットリローディングをサポートしているので、1 回起動すれば遅さは気にならない
nodemonを使って./serverディレクトリ内のファイルを監視してサーバの再起動を行っている画面が表示されたら HTML ソースを見てみると、
server-rendered="true"及び HTML の各要素がそのままレンダリングされていると思う
ちゃんと SSR されていることが分かる
ちなみに SPA モードであれば、JS が実行されてから画面がレンダリングされるので、読み込む JS ファイルのみが指定してあり、HTML はスタイルなどを除けばほとんど空であるまた、実行すると
.nuxtというディレクトリができており、サーバを立ち上げた時に読み込ませるコンテンツが格納されている
./server/index.jsを見てみると、Nuxt のインスタンスを作成して、Promise を返すnuxt.renderを Express に Middleware としてセットしているしばらく開発で使うのは、dev スクリプトだけで良さそう
以下の他のスクリプトはある程度 Nuxt でアプリケーション作った後にちゃんと使ってみようと思う
- build
- minify して
.nuxt/dist/に格納- start
- production モードでサーバを立ち上げる
- generate
- Vue を静的コンテンツとしてビルドして
dist/に格納してくれる- Netlify や Firebase などでそのままホスティングできる(はず)
- VuePress 使ったことがあれば、それ
使ってみる
ちょっとだけ使ってみてやったことや思ったことを書いてみようと思う
TypeScript を使う
yarn add -D @nuxt/typescript ts-node
tsconfig.jsonがあればnuxtコマンドで勝手にデフォルト値を書いてくれるらしいtouch tsconfig.json npx nuxt先程インストールした
ts-nodeは Node で TS をトランスパイルせずに実行できるツールでこれが無いとnpx nuxtが動かないこれで導入は完了
後は、拡張子を
.jsから.tsに変えて中身を書き換えていく
以下はserver/index.jsを TS に修正している例server/index.tsimport NuxtConfiguration from '@nuxt/config' ... // Import and Set Nuxt.js options const config: NuxtConfiguration = require('../nuxt.config.js') config.dev = !(process.env.NODE_ENV === 'production') ...TS 導入前は上記の
config.devが any 型だったが、導入後はちゃんと型定義の boolean 型を参照しているpackage.json も忘れずに修正
package.json"scripts": { - "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server", + "dev": "cross-env NODE_ENV=development nodemon server/index.ts --watch server", - "start": "cross-env NODE_ENV=production node server/index.js", + "start": "cross-env NODE_ENV=production ts-node server/index.ts", }Vue コンポーネントの TS 化についてはドキュメントでは
vue-property-decoratorの使用を薦めているyarn add vue-property-decorator
layouts/default.vueを例に修正すると<script lang="ts"> import { Component, Vue } from 'vue-property-decorator' @Component class Default extends Vue { clipped: boolean = false drawer: boolean = false fixed: boolean = false items: { icon: string; title: string; to: string }[] = [ { icon: 'apps', title: 'Welcome', to: '/' }, { icon: 'bubble_chart', title: 'Inspire', to: '/inspire' } ] miniVariant: boolean = false right: boolean = true rightDrawer: boolean = false title: string = 'Vuetify.js' } export default Default </script>ちなみに
vue-property-decoratorを使わなくても書ける<script lang="ts"> import Vue from 'vue' export default Vue.extend({ data(): { clipped: boolean drawer: boolean fixed: boolean ... } { return { clipped: false, drawer: false, fixed: false, ... } } }) </script>Vuex を使う
Nuxt をインストールした時にルート直下に store ディレクトリができており、既に Vuex 入ってる感があるが、中身は空である
Nuxt は store ディレクトリが存在すれば Vuex をインポートしてくれて、store をルートインスタンスに流してくれるらしいなので
- Vuex を手動で入れる必要がない
- どのコンポーネントからでも
this.$storeで参照できるまた、モジュールモードとクラシックモードの 2 つのモードがあるが、クラシックモードは廃止予定らしい
肥大化してくるとモジュールごとに State が参照できた方が良いので、モジュールモードで良さそうNuxt 側でモジュールモードとクラシックモードのどちらで Vuex インスタンスが作られるかの判定は
store/index.(js|ts)が存在して、Store インスタンスをエクスポートしていれば、クラシックモード
それ以外はモジュールモードモジュールモードで
store/index.(js|ts)という名前で作った場合、ルートモジュールという扱いで Store インスタンスの直下に State が存在する
store/モジュール名.(js|ts)という名前で作った場合、名前付きモジュールとして Store インスタンスの modules プロパティ配下に State が存在するそれぞれの State を参照する場合
- ルートモジュールは
this.$store.state.counterで参照できる- ルート以外は
this.$store.state.todos.listで参照できる
- 上記は
store/todos.(js|ts)というファイルを作った場合ちなみに私は上記のルートモジュールと呼ばれてる Store インスタンスの直下に State を置くのは 1 度もやったことない
以下のファイルを store ディレクトリ配下に追加
store/sample.tsinterface IState { counter: number } export const state: IState = { counter: 123 }簡単すぎる例だが、ファイルを追加するだけで Store に counter が保持できる
そして以下のように参照する
適当なVue<script> export default { mounted() { console.log(this.$store.state.sample.counter) // 123 } } </script>また、Vuex のヘルパー関数も使える
適当なVue<script> import { mapState } from 'vuex' export default { computed: { ...mapState({ sample: 'sample' }) }, mounted() { console.log(this.sample.counter) // 123 } } </script>Linter とコードフォーマッターを使う
TS を使っているが、TSLint は非推奨となるらしいので、ESLint の TS パーサーを使う
.eslintrc.jsを見る限り ES(JS)だけの Linter で良ければ、Nuxt をインストールした時点で出来てるので、後は rules や extends でルールを追加するだけで良さそう
package.jsonを見るとeslint-plugin-prettierとeslint-config-prettierが依存パッケージとしてインストールされており、.eslintrc.jsの extends に prettier が設定してあるので、ESLint と Prettier の競合は気にしなくて良さそうTS 用にLinterを使う場合の修正箇所は以下
yarn add -D @typescript-eslint/eslint-plugin.eslintrc.jsparserOptions: { - parser: 'babel-eslint' + parser: '@typescript-eslint/parser' }, - plugins: ['prettier'], + plugins: ['@typescript-eslint', 'prettier'],package.json"scripts": { - "lint": "eslint --ext .js,.vue --ignore-path .gitignore .", + "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore .", }また、ESLint のルールだと以下のような import は誤検知して、「no-unused-vars」を出してしまう
import NuxtConfiguration from '@nuxt/config'この場合、以下のように ESLint のルールは握りつぶして、
@typescript-eslintの方のルールを有効化する必要があるらしい
こちらの記事に書いてありますが、いくつかこのような誤検知するルールがあるらしいので、その都度同様の対応が必要.eslintrc.jsrules: { 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'error' }Airbnb の ESLint ルールを入れてみる
yarn add -D eslint-config-airbnb-base.eslintrc.jsextends: [ 'airbnb-base', ... ]いい感じに動いているが、Vue とか Vuex は Nuxt に直接依存しており、こちらで
package.jsonに書く必要が無いため、以下のような Lint エラーが出てる.../nuxt-boilerplate/pages/index.vue 63:1 error 'vuex' should be listed in the project's dependencies. Run 'npm i -S vuex' to add it import/no-extraneous-dependencies .../nuxt-boilerplate/plugins/vuetify.js 1:1 error 'vue' should be listed in the project's dependencies. Run 'npm i -S vue' to add it import/no-extraneous-dependencies上記は以下のように対応可能
.eslintrc.jssettings: { 'import/core-modules': ['vue', 'vuex'] },他にも Nuxt と ESLint で競合するルールはありそう
vue-router を使う
Nuxt ではエントリーポイントとなる JS などで routes を書いて
new VueRouterをやる必要はない
pages ディレクトリ配下にルーターから直接呼ばれるコンポーネントを配置するだけで良い以下の 2 つを配置
pages/sample/index.vue<template> <span>サンプルインデックス!</span> </template>pages/sample/test.vue<template> <span>サンプルテスト!</span> </template>それぞれ以下のようにしてルーティングさせる
適当なVue<nuxt-link to="/sample">Go sample index</nuxt-link> <nuxt-link to="/sample/test">Go sample test</nuxt-link>ファイル名が
index.vueであれば、ディレクトリ名が path になり、それ以外のファイル名であれば、ディレクトリ名/ファイル名が path になる
先程の Vuex とルールが似ているパラメータ付きの動的ルーティングもファイル名もしくはディレクトリ名の先頭にアンダースコアを付けることで実現できるらしい
nuxt-link というコンポーネントは router-link を extends したコンポーネントとなっており、nuxt-link を使わずに今まで通り
<router-link>を使って実装することも可能
ただし、nuxt-link を使うと画面表示領域にリンクがあれば、そのリンク先のコンテンツを先読みしてくれるので、遷移が速くなる試しに、以下のコードを適当な Vue の下の方に配置して、ファーストビューから見えないようにして、スクロールしてリンクを表示させると
index.vueだけプリフェッチされることが分かる
Chrome DevTool の Network タブで確認すると分かりやすい適当なVue<nuxt-link to="/sample">Go sample index</nuxt-link> <router-link to="/sample/test">Go sample test</router-link>その他
選択した UI フレームワークでサンプルコードが作られている
私は Vuetify を選んだのだが、割としっかりサンプルコードが Vuetify で作られてて、全部の UI フレームワーク分作られてると思うとちゃんとしてるなって思った
テストのサンプルがほぼ無い
Logo コンポーネントの 1 ケースしかテストのサンプルコードが無い
E2E のサンプルコードは無いでも最近は Vue のテスト周りのエコシステムが充実してきて、ドキュメントも豊富なので、問題ないと思う
Nuxt のアップデートは簡単にできるのだろうか
Angular みたいなマイグレーション機能はたぶん無いと思うが、Nuxt とか CLI 系のツールを使っているとアップデートが楽にできるとかなりありがたい
簡単にネットで調べた限りは Nuxt を最新にして出たエラーを潰してるっぽいさいごに
主にツールの導入になってしまい、まだコードはほとんど書いてないのですが、これから ToDo アプリ程度のものは作ってみて、Nuxt を学んでみようかと思います
続きは別記事でまた書こうかなと思いますフロントエンドは全部 1 から作る派なのですが、たまに自動で環境作ってくれる系ツールを使うとそのお手本のような構成に勉強になることがあります
また、今後フロントエンドの環境を作る上で活かせることもあると思うので、試しに使ってみるのもありかなと思いました
- 投稿日:2019-05-30T13:58:49+09:00
Javascriptで電卓を作ろう 第4回 小数点を正しく入力しよう
目次
- HTMLのマークアップ
- CSS
- Javascriptで四則演算
- 6)打った数字をコンソールに出力させよう。
- 7)打った数字と記号を変数に格納しよう。
- 8)計算結果を出力させよう。
- 8-1)「=」を押した時の処理
- 8-2) 計算結果と計算途中を、計算結果の画面に表示させよう。
- 最初に「+ ÷ - × =」が押せないようにする。
- 二回連続で計算できるようにしたい。
- Cボタン(リセットボタン)を押した時の処理
- BS(バックスペース)ボタンを使えるようにする
第4回不具合の改善2(小数点を正しく入力しよう)(今回の記事はここ)
今回は、電卓これらの機能をつけてみます。
- 12.12.や12..12のように小数点を間違って入力できてしまう。
- 電卓と同じように「.4」と入力したら0.4としたい。12.12.や12..12のような正しくない小数は入力できないようにする。
対処法
整数入力中、小数入力中を変数(mode)で定義します。
modeという変数を用意して、integer_mode、decimal_modeを代入して状態を管理します。
a)整数入力中(integer_mode)
- 「.」小数点を押すことができる。
b)小数入力中(decimal_mode)
- 「+ ÷ - ×」が押されるまでは、「.」小数点を押すことができない。(12.12.4や12..12とならないようにしたい)
これらを踏まえて、このこのように書いていきます。
小数点を押したら、
- 小数入力中mode==='demimal_mode'とする。
- このmodeでは、小数点は押せないようにする。
「+ ÷ - ×」、「=」「C」を押したら、
- 整数入力中mode==='integer_mode'に戻す。
'use strict' { const num_bth = document.querySelectorAll('.num_bth'); let output_sub = document.getElementById('output_sub');//計算結果を表示する場所 const output_total = document.getElementById('output_total');//計算過程を表示する場所 let total = 0;//計算式を表す変数 let state = 'start';//最初の状態を定義 // 1)計算する前の最初の状態(start) // 2)数字を入力している最中(calculation) // 3)「+ ÷ - × =」を押した直後(calBtn) // 4)「=」を教えて計算が終わった直後(finish) // 変数stateに、star,calculation, calBtn, finishを代入して状態を管理します。 let mode = 'integer_mode'; //最初は整数入力モード // 変数modeに、整数入力中integer_mode、小数入力中decimal_modeを定義します。 // 1-9の数字ボタンを押した時 const one_nine = document.querySelectorAll('.one_nine'); one_nine.forEach(index => { index.addEventListener('click', () => { if(state === 'start') { //最初:totalに打った数字を代入する total = index.dataset.indexId; }else if(state === 'finish') { //計算後:リセット処理後に、totalに打った数字を代入する reset(); total = index.dataset.indexId; }else if(state === 'calculation'){ //計算中totalに打った数字を追加して、totalに代入する。 total += index.dataset.indexId; } output_sub.textContent = total; state = 'calculation'//数字を入力している状態にする。 }) //click })//forEach // 0の数字ボタンを押した時 const zero = document.getElementById('zero'); zero.addEventListener('click', () => { console.log(zero.dataset.indexId) if(total === 0) { total = zero.dataset.indexId; }else{ total += zero.dataset.indexId; } output_sub.textContent = total; state = 'calculation'//数字を入力している状態にする。 }) //click // 「.」小数点ボタンを押した時 const point = document.getElementById('point'); point.addEventListener('click', () => { console.log(point.dataset.indexId) if(mode === 'decimal_mode'){ return; } if(total === 0) { total = point.dataset.indexId; }else{ total += point.dataset.indexId; } output_sub.textContent = total; state = 'calculation'//数字を入力している状態にする。 mode = 'decimal_mode'; //小数入力モードに変更 }) //click //「+ ÷ - ×」ボタンを押した時 const cal = document.querySelectorAll('.cal'); cal.forEach(index => { index.addEventListener('click', () => { if(state === 'start') { return;//最初記号は押せない }else if(state === 'calculation'){ total += index.dataset.indexId;//計算中はtotalに打った記号を追加し、totalに代入する。 }else if(state === 'finish'){ //計算後は前の計算結果をtotal に代入して計算しなおす。 total = output_total.textContent; total += index.dataset.indexId; output_total.textContent = 0 } console.log(index.dataset.indexId) output_sub.textContent = total; state = 'calBtn'//演算記号を入力している状態する。 mode ='integer_mode'//整数モードに戻す }) //click })//forEach //イコールを押した時 const equal_btn = document.getElementById('equal_btn'); equal_btn.addEventListener('click',() =>{ console.log(eval(total)); output_total.textContent = eval(total); state = 'finish'//計算が終わった状態にする。 mode ='integer_mode'//整数モードに戻す }); //Cボタン(リセットボタン)を押した時の処理 const clear = document.getElementById('clear') clear.addEventListener('click', () => { reset(); }) //リセットを行う関数 function reset() { total = 0; output_sub.textContent = 0; output_total.textContent = 0; mode ='integer_mode'//整数モードに戻す } //BSボタン(バックスペース)を押した時の処理 const bs = document.getElementById('bs') bs.addEventListener('click', () => { console.log('BS') if(state ==="finish") { return;//計算後は、bsを押せない。 }else{ // 一文字目から、最後から二文字目までをtotalに代入(最後の一文字を除きtotalに代入する) total = output_sub.textContent.slice(0, -1); output_sub.textContent = total; } }) }電卓と同じように「.4」と入力したら0.4としたい。
【条件】
①
- 最初state==='start
- 計算終了後state==='finish'
の時に小数点が押されたらtotal=0とした後に
total = 0 + .とする。②
- 演算記号入力直後state==='calBtn'
の時に小終点が押されたら
total += 0とした後に
total = total + .とする。こうすることで「.」の前に0を挿入できる。
// 「.」小数点ボタンを押した時 const point = document.getElementById('point'); point.addEventListener('click', () => { console.log(point.dataset.indexId) if(mode === 'decimal_mode'){ return; //小数点入力モードではもう一度小数点を押せない } //「.4」と入力したら0.4としたい。(1)+(2)で0.4となる if(state==='start'||state==='finish') { total = 0;//(1)最初と計算終了直後なら、0を入力 }else if(state==='calBtn'){ //これを入れないと、0.4+0.4と打つと0.4+00.4となる。 if(output_sub.textContent.slice(-1)!=='0'){ total += 0;//(1')演算記号入力直後なら、今までの計算結果に0を入力 } } total += point.dataset.indexId;//(2)「.」を入力 output_sub.textContent = total; state = 'calculation'//数字を入力している状態にする。 mode = 'decimal_mode'; //小数入力モードに変更 }) //click残りの課題
- 最初に0を連続で押せてしまう。
- 記号ボタン(+×÷-)を連続で押せてしまい、計算ができない。(連続で押した場合は、最後に押した記号ボタンが採用されるようにしたい。 )
- 12.12.や12..12のように小数点を正しくなく入力できてしまう。- 割り算等で小数点以下が異様に長い。
- 電卓と同じように「.4」と入力したら0.4としたい。- BSで文字を消した時、小数、整数モード、記号入力モードが切り替わらない。
現時点のコード
HTML
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>電卓</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class = "container"> <!-- 計算結果を出力する場所--> <div id = "output_total" class="output ">0</div> <!-- 計算過程を出力する --> <div id = "output_sub" class="output active">0</div> <!-- ボタンを配置する場所--> <div class = 'input'> <section class = 'row'> <div id = "clear">C</div> <div class = "num_bth"></div> <div class = "num_bth" id="bs">BS</div> <div class = "num_bth cal" data-index-id= '/'>÷</div> </section> <section class = 'row'> <div class = "num_bth one_nine " data-index-id = 9 >9</div> <div class = "num_bth one_nine " data-index-id = 8 >8</div> <div class = "num_bth one_nine " data-index-id = 7 >7</div> <div class = "num_bth cal" data-index-id = '*'>×</div> </section> <section class = 'row'> <div class = "num_bth one_nine " data-index-id = 6 >6</div> <div class = "num_bth one_nine " data-index-id = 5 >5</div> <div class = "num_bth one_nine " data-index-id = 4 >4</div> <div class = "num_bth cal" data-index-id = '-'>-</div> </section> <section class = 'row'> <div class = "num_bth one_nine " data-index-id = 3 >3</div> <div class = "num_bth one_nine" data-index-id = 2 >2</div> <div class = "num_bth one_nine" data-index-id = 1 >1</div> <div class = "num_bth cal" data-index-id = '+'>+</div> </section> <section class = 'row'> <div class = "num_bth" data-index-id = 00 >00</div> <div class = "num_bth" id="zero" data-index-id = 0 >0</div> <div class = "num_bth" id="point" data-index-id = . >.</div> <div id = 'equal_btn'>=</div> </section> </div> </div> <script src="script.js"></script> </body> </html>CSS
.container { width: 200px; margin: 50px auto; border: 2px solid black; } .output { width: 200px; height: 50px; line-height: 50px; text-align: right; padding: 5px; margin-bottom: 5px; border:2px solid black; box-sizing: border-box; } .input { width:200px; border: 2px solid black; } .row { display: flex; justify-content: space-between; border: 2px solid red; width: 200px; } /*rowの範囲がわかりやすいように赤の枠を作っています。*/ .num_bth, #clear, #equal_btn{ width: 60px; height: 30px; text-align: center; line-height: 30px; border-radius: 30%; background: lightgray; cursor: pointer; margin: 5px; }Javascript
'use strict' { const num_bth = document.querySelectorAll('.num_bth'); let output_sub = document.getElementById('output_sub');//計算結果を表示する場所 const output_total = document.getElementById('output_total');//計算過程を表示する場所 let total = 0;//計算式を表す変数 let state = 'start';//最初の状態を定義 // 1)計算する前の最初の状態(start) // 2)数字を入力している最中(calculation) // 3)「+ ÷ - × =」を押した直後(calBtn) // 4)「=」を教えて計算が終わった直後(finish) // 変数stateに、star,calculation, calBtn, finishを代入して状態を管理します。 let mode = 'integer_mode'; //最初は整数入力モード // 変数modeに、整数入力中integer_mode、小数入力中decimal_modeを定義します。 // 1-9の数字ボタンを押した時 const one_nine = document.querySelectorAll('.one_nine'); one_nine.forEach(index => { index.addEventListener('click', () => { if(state === 'start') { //最初totalに打った数字を代入する total = index.dataset.indexId; }else if(state === 'finish') { //計算後は、リセット処理後に、totalに打った数字を代入する reset(); total = index.dataset.indexId; }else if(state === 'calculation'){ //計算中totalに打った数字を追加して、totalに代入する。 total += index.dataset.indexId; } output_sub.textContent = total; state = 'calculation'//数字を入力している状態にする。 }) //click })//forEach // 0の数字ボタンを押した時 const zero = document.getElementById('zero'); zero.addEventListener('click', () => { console.log(zero.dataset.indexId) if(total === 0) { total = zero.dataset.indexId; }else{ total += zero.dataset.indexId; } output_sub.textContent = total; state = 'calculation'//数字を入力している状態にする。 }) //click // 「.」小数点ボタンを押した時 const point = document.getElementById('point'); point.addEventListener('click', () => { console.log(point.dataset.indexId) if(mode === 'decimal_mode'){ return; //小数点入力モードではもう一度小数点を押せない } //「.4」と入力したら0.4としたい。(1)+(2)で0.4となる if(state==='start'||state==='finish') { total = 0;//(1)最初と計算終了直後なら、0を入力 }else if(state==='calBtn'){ //これを入れないと、0.4+0.4と打つと0.4+00.4となる。 if(output_sub.textContent.slice(-1)!=='0'){ total += 0;//(1')演算記号入力直後なら、今までの計算結果に0を入力 } } total += point.dataset.indexId;//(2)「.」を入力 output_sub.textContent = total; state = 'calculation'//数字を入力している状態にする。 mode = 'decimal_mode'; //小数入力モードに変更 }) //click //「+ ÷ - ×」ボタンを押した時 const cal = document.querySelectorAll('.cal'); cal.forEach(index => { index.addEventListener('click', () => { if(state === 'start') { return;//最初記号は押せない }else if(state === 'calculation'){ total += index.dataset.indexId;//計算中はtotalに打った記号を追加し、totalに代入する。 }else if(state === 'finish'){ //計算後は前の計算結果をtotal に代入して計算しなおす。 total = output_total.textContent; total += index.dataset.indexId; output_total.textContent = 0 } console.log(index.dataset.indexId) output_sub.textContent = total; state = 'calBtn'//演算記号を入力している状態する。 mode ='integer_mode'//整数モードに戻す }) //click })//forEach //イコールを押した時 const equal_btn = document.getElementById('equal_btn'); equal_btn.addEventListener('click',() =>{ console.log(eval(total)); output_total.textContent = eval(total); state = 'finish'//計算が終わった状態にする。 mode ='integer_mode'//整数モードに戻す }); //Cボタン(リセットボタン)を押した時の処理 const clear = document.getElementById('clear') clear.addEventListener('click', () => { reset(); }) //リセットを行う関数 function reset() { total = 0; output_sub.textContent = 0; output_total.textContent = 0; mode ='integer_mode'//整数モードに戻す } //BSボタン(バックスペース)を押した時の処理 const bs = document.getElementById('bs') bs.addEventListener('click', () => { console.log('BS') if(state ==="finish") { return;//計算後は、bsを押せない。 }else{ // 一文字目から、最後から二文字目までをtotalに代入(最後の一文字を除きtotalに代入する) total = output_sub.textContent.slice(0, -1); output_sub.textContent = total; } }) }
- 投稿日:2019-05-30T12:57:47+09:00
Promiseを使ってループで直列実行する
直列動作のサンプル
function PromiseMaker(i) { return function() { return new Promise(function(resolve, reject) { setTimeout(function(){ console.log("i:" + i); resolve(); }, 100); }); } }; var p = Promise.resolve(); for(var i=0; i < 15; i++) { p = p.then(PromiseMaker(i)); }動作しないパターン
var p = Promise.resolve(); for(var i=0; i < 15; i++) { p.then(PromiseMaker(i)); }後者が動作しない理由
p.thenが返すPromiseに対してthenを繋げなければいけないのに、
最初のPromiseに対してすべて繋がってしまっているので、
並列で実行されてしまうということでした。参考: http://p-baleine.hatenablog.com/entry/2014/03/14/085536
こういった形が必要なければ、素直にawait/asyncやPromise.allを使う方が良いかもしれません…。
- 投稿日:2019-05-30T12:32:10+09:00
Vuetifyのv-iconに自作SVGアイコンを(楽に)使いたい!!
TL;DR
SVGアイコンをWebfontにしてstyle.cssをインポートすると
<v-icon>{名前}</v-icon>で使えるようになるよ!Vuetifyのリファレンスがかなりアレ
Vuetifyのv-iconってFontAwesomeとかのアイコンを利用できるじゃないですか。
でも自作のSVGアイコンも使いたいですよね。一応リファレンスにやり方が載っています。
簡単に訳すと、
SVGファイルを単一コンポーネントにしてVue.useの段階でセットすれば$vuetify.icons.{名前}で利用できるよ!
ということです。
追加したいアイコンが100個ぐらいあった場合、1個1個単一コンポーネントにしてVue.useで読み込ませるのは非常に不便ですしコードが長くなるしで割と最悪です。そこで色々調べていたところ,このような投稿がありました。
Vuetify Custom Icons Documentation is Horrendous : vuetifyjs
一番最後の投稿に
Well, for anyone from the future who stumbles across this, I found a way to do it without using Vuetify at all. So that site that I found, fontello.com, gives a css file that references the icons by class name, as I alluded to above. This class name is set on the site when you download the zip. So if you just import the css file as I did above, and then you can just reference the icon by name (with the prefix you used) in a v-icon and it should work. If anyone sees this and is having trouble getting it to work, shoot me a message. I'm usually pretty good about answering within a couple days.というコメントがあります。
fontello.comというサイトでcssファイルを使ってimportすれば読み込めるぞ!という内容です。
色々あって今回はfontello.comではなく同じ様なことをしてくれるicomoon.ioで試してみます。
サイトにアクセス
Icon Font & SVG Icon Sets ❍ IcoMoon
アクセスしたら右上のIcoMoon Appをクリックしましょう
でこんな感じになったら自作SVGアイコンをドラッグアンドドロップ
アップロードしたアイコンが表示されたら、Webfontを生成したいアイコンをクリック(複数可能です)
クリックしたら右下のGenerate Fontをクリックして先に進みましょう
この画面では アイコンの名前とコードを決められます。
コードは一意に定まる様にし、名前はわかりやすい名前をつけます。ここでの名前は実際に使用する際に使われます設定が終わったら右下のDownloadボタンをクリックするとicomoon.zipファイルのダウンロードが始まります
Vueのプロジェクトに追加
解凍したicomoonフォルダをsrc/assetsに配置しましょう。
だいたいこんな感じになると思います。早速使ってみましょう
今回はVueのプロジェクトにvuetifyを入れたあと、HelloWorld.vueを編集し動作の確認を行います。
style.cssをインポートしての中に
icon-{名前}を入れて、実行してみましょう。僕は先程 svgという名前とemoiという名前をアイコンに付けているので使用する際は上記の様になります。
ここで実行してみましょう
うまくいってますね。 これでアイコンを表示させることが出来ました。
追加の設定
インポートしたstyle.cssを見ると、colorの設定があります。
v-icon側でcolorを設定したい場合、style.cssにあるcolorの設定を削除する必要があります。
実際の運用
親コンポーネントでimportするのはアレなのでv-iconをラッピングしたoreore-v-iconみたいな単一コンポーネントを作ってそこでインポートするのが運用としては正しいと思います。
- 投稿日:2019-05-30T11:18:09+09:00
初めてのフロントエンド。
はじめに
私は今までバックエンドとしてサーバー周りだったり、apiがどうのこうのを主にGolangで作業をしてたのですが、この度フロントエンドをやってみることに。
表側もかなり大変ですね ~
javascriptとは
Javaとは別物の言語でオブジェクト指向型スクリプト言語だが、クラスの概念もあると。
なんだか面白そうだと思いました。
今回は引数でmethodやendopoint,パラメーター,必要なcolumn情報(DB)を渡してレスポンスを得る関数を作成しただけですので、クラス等はまた今度。
XMLHttpRequest
さて、早速調べていくのですが、早速いいもの見つけました。
XMLHttpRequestというのがあるじゃないですか。XMLHttpRequestとは。
Ajax(非同期通信)で使われる組み込みオブジェクトのことで、サーバーへ通信リクエストが送れるようになる。とのことです。
コーディング開始
もうこれ使うしかないなと思って、早速調べながらコーディングを始めました。
で、一番最初に書いたコードがこちら ...loginApiRequest.jsfunction get_login_info_response(method, endpoint, parameters) { // XMLHttpRequestオブジェクトを作成 var request = new XMLHttpRequest(); // Httpリクエストに必要な情報 request.open(method, endpoint); request.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); request.responseType = 'json'; // 実行 request.onload = function() { response_from_database = this.response; console.log(response_from_database); true_or_false = response_from_database['is_admin']; console.log(true_or_false); return true_or_false }; // Httpリクエストを送信(引数指定で特定の情報のみを送信) request.send(parameters); }「js始めて全然月日経ってないのこのコード割と使えるんじゃ!?」
って思ってましたが、色々と不備が多いです。まず一番やらかしてるのは、.onload内のスコープで返り値出そうとしていることですよね。
これは絶望的です、やっちゃってますね。これhtml側から関数呼んでも、スコープがズレてるのでコンソールには"undefined"と表示されちゃうんです。
そして、処理が一方通行です。http.statusでステータスコードを判別して処理を書くべきです。401が渡された時にエラーも何も表示されないとユーザー側は何が起こったか理解できないじゃないかと。
返り値をちゃんともらうのに結構苦労しました。
こんなコードも書きました。loginApiRequest.jsa = request.onload = function () { ... } return a血迷っちゃったんでしょうか。
.onloadで取得した結果をそのままreturnしようとしたんです ...
反省してます。いくつもの試行錯誤を重ね、最終的にたどり着いたコードが以下です。
loginApiRequest.jsfunction get_login_info_response(method, endpoint, parameters, callback) { // XMLHttpRequestオブジェクトを作成 var request = new XMLHttpRequest(); // Httpリクエストに必要な情報 request.open(method, endpoint); request.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); request.responseType = 'json'; // 実行 request.onload = function() { response_from_database = this.response; // ステータスコードによって処理を変更 if (request.status === 401) { alert("IDかパスワードが違います。"); console.log(response_from_database); } else if (request.status === 200) { true_or_false = response_from_database['is_admin']; console.log(true_or_false); } callback(true_or_false) }; // Httpリクエストを送信(引数指定で特定の情報のみを送信) request.send(parameters); }最終的にたどり着いた答えはcallback関数でした。
ここでcallback関数について書いても長くなるだけなので、
callback関数とはをご参照くださいませ。これでhtml側からうまく返り値を受け取れて、trueかfalseによってログインの処理ができるようになりました。
まとめ
割と綺麗にコードが書けたんじゃないかなと自画自賛しちゃいましたが、javascriptが達者な方から見たらどうなんでしょうか ... 気づいた方がいらっしゃいましたらお気軽にご指摘していただけると幸いです。
ご参考にさせていただいた主なサイト様
https://sbfl.net/blog/2019/02/08/javascript-callback-func/#i
https://qiita.com/sirone/items/412b2a171dccb11e1bb6
こちらの記事は特に大変参考になりましたありがとうございます。
- 投稿日:2019-05-30T08:48:00+09:00
GoogleMapへのリンクURL作成方法を調べた
サイトにGoogleMapを埋め込む場合、Googleが提供しているJavaScriptのAPIを使用するのが一般的です。
そうではなく、a要素で作るような単なるリンクを作成したい、という場合にどういう規則でURLを作成すればよいのか調べてみたものです。
やってみると意外と楽しいです。
前提
システム側で、マップを作成するための住所や緯度経度、建物名を保持していて、URLを自動生成するものとします。つまり、一つ一つの所在地をGoogleMapで調べてリンクを取得したり、短縮URLを取得していく方法は使いません。
また、下記の内容は2019年5月29日時点で調べたものです。仕様変更等はご了承ください。
公式ドキュメント
https://developers.google.com/maps/documentation/urls/guide?hl=ja
Maps URLsという文書があり、そこに書いてあります。公式ドキュメントを引用すると、簡単な解説
Using Maps URLs, you can build a universal, cross-platform URL to launch Google Maps and perform searches, get directions and navigation, and display map views and panoramic images.
Google翻訳結果
GoogleマップのURLを使用すると、Googleマップを起動して検索を実行したり、方向やナビゲーションを取得したり、マップビューやパノラマ画像を表示したりするための、ユニバーサルでクロスプラットフォームのURLを構築できます。
使用について
You don't need a Google API key to use Maps URLs.
Google翻訳結果
Maps URLを使用するのにGoogle APIキーは必要ありません。
との記述がある通り、URLを使う場合Google API keyは不要です。手間なく扱えるようです。各OSでGoogle Mapアプリがインストールされている場合どういう挙動をするか、などについても書いてあります。
以下、上記ドキュメントに基づいて記載します。
作れるリンクの種類
Search(検索)
特定の場所にピンを立てる、または一般的な検索のリクエスト
https://www.google.com/maps/search/?api=1¶metersという形式仕様上、ズーム指定できません。
Direction(道順)
指定した場所間の道順を表示するリクエスト
https://www.google.com/maps/dir/?api=1¶metersという形式Display a map(地図を表示)
SearchやDirectionと異なり、ピンや道順を表示せずに、単に地図を表示するリクエスト
https://www.google.com/maps/@?api=1&map_action=map¶metersという形式Display a Street View panorama(ストリートビュー表示)
ストリートビューのパノラマ画像表示のリクエスト
https://www.google.com/maps/@?api=1&map_action=pano¶metersという形式試してみる
東京駅をターゲットにしてみます。GoogleMapで検索すると、緯度経度は
35.6812362,139.7649361となっているので、それを使いましょう。検証には
Edgeを使っています。Search
パラメータは、
queryまたはquery_place_idです。どちらかが必須です。query_place_idの取得方法は調べ切れませんでした。https://www.google.com/maps/search/?api=1&query=東京駅
ウィンドウ小さくした影響か、ピンは隠れていますが出てきます。日本語でそのまま検索しても、一応問題なく使えます。検証は必要かと思います。
次に緯度経度です。
https://www.google.com/maps/search/?api=1&query=35.6812362,139.7649361
こちらも問題ないですね。
東京駅周りの書店、なんて指定もできます。キーワードを
+で区切ります。https://www.google.com/maps/search/?api=1&query=東京駅+書店
Direction
必須パラメーターは、
パラメーター名 意味 originスタート地点 destination目的地 です。
Searchと同じく、place_idも使えるようです。東京駅から横浜駅に行ってみます。
https://www.google.com/maps/dir/?api=1&origin=東京駅&destination=横浜駅
おお…これはすごい。ちゃんと出る。
以下、オプションのパラメーターを見ていきます。
travelmode
初期に表示される移動方法を指定できるようです。
- 車(driving)
https://www.google.com/maps/dir/?api=1&origin=東京駅&destination=横浜駅&travelmode=driving
おお…!
他にも指定できます。指定しない場合は、ルートやユーザー設定に基づいて選択されるそうです。
dir_action=navigate
経路案内してくれるパラメーターのようです。現在位置や区間に基づいて行われるようで、詳細な検証ができませんでした…。
経路案内できない場合は、このパラメーター無視するよ、と書いてあります。waypoints
経由地を加味したルート検索を行ってくれます。
waypointsに指定するのは、originやdestinationと同じく、建物名や緯度経度等になります。複数指定する場合は|で区切ります。経由地計算も、行えない場合は無視されるそうです。
Display a map
https://www.google.com/maps/@?api=1&map_action=map
と、パラメータ無しで開けます。この場合は現在地等に基づいた表示になるのだろうと思います。
以下、オプションのパラメーターを見ていきます。
center
中心点を指定します。緯度経度での指定になります。
https://www.google.com/maps/@?api=1&map_action=map¢er=35.6812362,139.7649361
東京駅が中心になりました。
zoom
地図の倍率を指定します。0~21だそうです。最大にしてみましょう。
https://www.google.com/maps/@?api=1&map_action=map¢er=35.6812362,139.7649361&zoom=21
最大は大きすぎますね。
basemap
地図タイプ指定です。roadmap(通常、未指定の場合これ)、satellite(衛星)、terrain(地形図)の三種類です。
- satellite
https://www.google.com/maps/@?api=1&map_action=map¢er=35.6812362,139.7649361&&basemap=satellite
- terrain
https://www.google.com/maps/@?api=1&map_action=map¢er=35.6812362,139.7649361&&basemap=terrain
東京で地形図を出しても、高低差が無いのであまり意味が無いですね…
Display a Street View panorama
基本URLは
https://www.google.com/maps/@?api=1&map_action=panoです。が、これ以上パラメーターを指定しない場合は単にマップが開きます。以下のいずれかが必須です。
パラメーター 内容 viewpoint表示する地点の緯度経度 panoパノラマID 両方指定した場合、
panoで指定したIDのパノラマが見つからない場合にviwepointを使用する、という挙動になるそうです。どちらでもパノラマ画像が見つからない場合は、マップのデフォルト挙動をするそうです。試してみましょう。
https://www.google.com/maps/@?api=1&map_action=pano¶meters&viewpoint=35.6812362,139.7649361
思いっきり、駅構内みたいですね。
追加パラメーターも見ていきます。
heading
デフォルトを北として、そこから何度向き直すか、という指定です。
-90だと、左を向くようです。つまりプラスだと(自分が)時計回りするイメージみたいです。
pitch
上を向くか下を向くか、という感じです。-90~90の範囲で指定します。
headingと同じ位置で45度上を向いてみました。なるほど。
fov
ズーム倍率、のようです。値が小さいほどズーム倍率が大きくなる(近くなる)ようです。
余談
これらのURLを使って、ブラウザからGoogleMapにアクセスすると、URLが別のもの(GoogleMapが使用するもの)に置き換わります。そのURLはGoogleの仕様によって変わりうると想定されます。
なので、URLを生成する場合はAPIに準拠した形で生成するほうがよさそうですね。
おためし:Geolocation APIと組み合わせる
デザインは適当です。また、細かいエラーチェック等は割愛しています。
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <button id="mapper">現在地を取得して経路を出す</button> <script src="main.js"></script> </body> </html>上のようなHTMLを用意して、
main.jsconst btn = document.getElementById("mapper"); btn.addEventListener("click", e => { if (navigator.geolocation) { //Geolocation APIを利用できる環境向けの処理 const navi = navigator.geolocation; navi.getCurrentPosition((pos) => { const latlng = `${pos.coords.latitude},${pos.coords.longitude}`; const link = `https://www.google.com/maps/dir/?api=1&origin=${latlng}&destination=東京駅`; const anchor = document.createElement("a"); anchor.text = "GoogleMapで経路を見る" anchor.target = "_blank"; anchor.href = link; document.body.appendChild(anchor); }); } else { //Geolocation APIを利用できない環境向けの処理 } });上のようなJavaScriptを用意します。すると、
ユーザーの許可を得て現在位置を取得した上で、Directionを指定してGoogleMapを呼び出す、なんてこともできますね。
使い方次第でいろいろ便利かもしれない、と思った次第です。
- 投稿日:2019-05-30T05:14:01+09:00
ReactやVueなどのJavascriptフレームワークからRailsの多対多モデルに対して、関連付けも含めて一気に作ってみた
こんにちは!@hairgaiです。
突然ですが、とあるTeamを作成するときにMemberを一緒に所属させたい!と思うことって一日に一回くらいありませんか?
今回は、それを実装するときにJavascriptからRailsへのパラメータで若干悩んだので、それを共有しようかなと思います。前提
よくある多対多です。
- Teamモデル
has_many :team_membershas_many :members, through: :team_members- TeamMemberモデル
belongs_to :teambelongs_to :member- Memberモデル
has_many :team_members: :destroyhas_many :teams, through: :team_membersReact 16.8.6、Rails 5.2.2
実装
Memberが数人いて何かTeamを作るときに、一度作成して後から所属させるのは面倒なので、当然ながら「Teamを作るときにMemberを所属させつつ作れたらいいのに」と思うと思います。
なので、フロントでTeamを作成するときに、既に登録済みのMemberを一覧で表示して、それを選択させることでパラメータを同時に送らせることにします。こんな感じですね。
また、Railsでは、モデルを作成するときに、その多対多関係にあるモデルを一緒に関連付けることが可能です。
なので、上記のように「Teamを作るときにMemberを所属させつつ作れたらいいのに」と思ったら、pry(main)> Member.create!(email: 'a@example.com') => #<Member:0x00007fb1498f04d8 id: 1, email: "a@example.com", # ... pry(main)> team = Team.create!(name: 'hoge', member_ids: [1]) => #<Team:0x00007fb142722860 id: 1, name: "hoge", # ... pry(main)> team.members => [#<Member:0x00007fb149951af8 id: 1, email: "a@example.com", # ... ]のように、
[モデル名]_idsというパラメータに配列を渡すと関連付けることが出来ます。
なので、フロント側でこのパラメータが渡ってくるようにURLパラメータを生成すれば良いということですね。パラメータ送りたい
ググると色々な記事が出てくる通り、JavascriptからURLにパラメータ用のクエリを乗っけるときは、qsを使いました。
qsは、Javascriptの連想配列(Hash)からURLパラメータを生成してくれるライブラリで、例えばimport qs from 'qs'; // or var qs = require('qs'); const params = { team: { name: 'hoge' }}; qs.stringify(params) // => 'team%5Bname%5D=hoge'という感じで使います。
なので(今回はパラメータの話なので解説しませんが)、フロント側でいい感じにパラメータを連想配列として生成します。// 実際はフレームワーク上でユーザの入力に合わせて作りますが、下記のような連想配列が生成されます。 const params = { team: { name: 'hoge', member_ids: [2, 3] } } qs.stringify(params) // => 'team%5Bname%5D=hoge&team%5Bmember_ids%5D%5B0%5D=2&team%5Bmember_ids%5D%5B1%5D=3'これをライブラリ等を用いてRailsに送ってみると…(axiosを使いました)
15: def update 16: binding.pry => 17: @team.update!(team_params) # ... [1] pry(#<Api::V1::TeamsController>)> params => <ActionController::Parameters {"team"=>{"id"=>"1", "name"=>"hoge", "member_ids"=>{"0"=>"1", "1"=>"2", "2"=>"3"}}, "format"=>"json", "controller"=>"api/v1/teams", "action"=>"update", "id"=>"1"} permitted: false>あれ?なんか変な形で送られてますね。
クエリの生成方法のオプションがあった
qsが生成したURLパラメータをデコードしてみると…team[name]=hoge&team[member_ids][0]=2&team[member_ids][1]=3ということで、
team[member_ids][0]=2&team[member_ids][1]=3のように指定されています。これをRailsでは"member_ids"=>{"0"=>"2", "1"=>"3"}と解釈してしまうようですね。(確かにHashっぽいです)
では、Railsではどう記述すればArrayと解釈されるのか、ということで、逆にArrayからクエリを生成してみましょう。pry(main)> { name: 'hoge', member_ids: [2, 3] }.to_query('team') => "team%5Bmember_ids%5D%5B%5D=2&team%5Bmember_ids%5D%5B%5D=3&team%5Bname%5D=hoge" # => team[member_ids][]=2&team[member_ids][]=3&team[name]=hoge順番は違いますが、若干指定の仕方が違いますね。Arrayのクエリでは、
[]のように数字を入れずに指定すれば良いようです。
現在はその数字の処理はqs側にまかせています。qsのGitHubをよく読んでみると…You may use the arrayFormat option to specify the format of the output array:
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }) // 'a[0]=b&a[1]=c' qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }) // 'a[]=b&a[]=c' qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }) // 'a=b&a=c' qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' }) // 'a=b,c'とのことです。
なので、フロント側でURLパラメータを生成する際に、const params = { team: { name: 'hoge', member_ids: [2, 3] } } qs.stringify(params, { arrayFormat: 'brackets' }) // => 'team%5Bname%5D=hoge&team%5Bmember_ids%5D%5B%5D=2&team%5Bmember_ids%5D%5B%5D=3'とオプションを追加してみると…
[1] pry(#<Api::V1::TeamsController>)> params => <ActionController::Parameters {"team"=>{"id"=>"1", "name"=>"hoge", "member_ids"=>["2", "3"]}, "format"=>"json", "controller"=>"api/v1/teams", "action"=>"update", "id"=>"1"} permitted: false>出来ました!レコードも正常に作られます。
結論
Railsは、Railsが提供するWayに沿えば非常に強力なフレームワークですが、その分色々な決まりがあるので、こういった細かいところもキチンと調べていきたいですね。
この記事が誰かの参考になれば幸いです。
参考
ありがとうございました!
- 投稿日:2019-05-30T02:12:38+09:00
Node.jsでインフラのテスト、構成管理、オペレーション自動化
とあるプロジェクトで昨年から約1年半、Ansibleでサーバ構築をしていて書きづらいなーと思いながらDocker/Kubenetesとか使いたいけど、それに合わせたシステムの改修をが間に合うわけもなく……
という鬱々とした気持ちを抱えておりました。が、不平不満を言うだけでははじまらないと思い立って、自分の好きなように書けるツールを作成してみました
Submarine - https://gitlab.com/mjusui/submarine
私がAnsibleを書きづらいと感じたのは、別にAnsibleが悪いわけではなく、単にプログラミングパラダイムの問題です
結論から言うと、タイトルどおりNode.jsで書きたかったんです紹介の前にインフラエンジニア以外の人向けにAnsibleがどういうものなのか、簡単に説明しておきます
Ansibleとは
サーバ構築、構成管理や自動化のためのツール。特徴としては以下のようなものがあります
- コードは全部
YAMLで表現されるオブジェクト指向や関数の概念はなく、条件分岐とループのみで処理が進んでいく(ただしコードをinclude/importできるので多少の再利用性はある)- 変数は基本すべてグローバルアクセス可能で、再代入も自由(一部スコープを制限する機能はある)
- サーバ構築でよく使う機能(ファイルコピーなど)は
moduleという単位でAnsibleやサードパーティが用意してくれたものを使うmoduleを自前で作成することもできる- Ansible自体はPythonで開発されており、Pythonの機能や、パッケージを使って
moduleも開発されている- サーバには
sshさえできればよく、専用のエージェントをインストールする必要はないプログラミング経験があまりないインフラエンジニアにも分かりやすいよう、シンプルに設計されているという印象です
Submarineとは
Node.jsで開発するインフラ管理のFrameworkです。以下のような特徴があります
- Ansibleは構成管理がメインのツールですが、Submairneはサーバのテストもできます
- テストの結果に応じて、コマンドを実行する/しないが決定されます
- Classと継承を使って、コードの再利用性を高めています
- Node.jsの機能を使えば、変数の再代入を制限したり、スコープを限定することもできます
- サーバの状態を取得する関数、取得した状態をテストする関数、サーバに変更を加える関数を分離することで、安全で読みやすい(条件分岐の少ない)実装ができます
- HTTPサーバを起動して、上記で実装した関数をエンドポイントとして公開する機能があります
今回はSubmarineの基本的な機能をサンプルコードをまじえて、紹介していこうと思います
サーバの状態を取得する
まずはサーバから情報を取得します
Node.jsconst Submarine = require('Submarine'); const Tutrial = class extends Submarine { query(){ return { hostname: 'hostname -s', ipv4_addrs: String.raw` ip -o -f inet a \ |awk '{print $4}' ` }; } } const tut=new Tutrial({ conn: 'bash' }); tut.current().then((stats)=>{ console.log(stats); });Node.jsが読める人は感覚的にわかるかもしれませんが
Submarineというクラスを継承してTutrialというオリジナルのクラスを定義しています。そして、そのクラスをnewでインスタンス化してtut.current()というところでTutrialクラスに定義したquery関数が実行されますクラスをインスタンス化するときに
{ conn: 'bash' }という引数を与えることでTutrialクラスに定義したquery関数の中のコマンド(hostname -sなど)がlocalhostのbashで実行される、ということを指定していますクラスをインスタンス化するときに例えば
{ conn: 'ssh', host: <ip address> }といった具合に指定すれば、指定したIPアドレスにsshして、コマンドが実行されますサーバの状態をテストする
今度は取得した情報をテストします
Node.jsconst Submarine = require('Submarine'); const Tutrial = class extends Submarine { query(){ return { nonexecutable: String.raw` which none \ 2> /dev/null \ || exit 0 `, executable: String.raw` which node \ 2> /dev/null \ || exit 0 `, }; } test(stats){ return { none_is_not_executable: stats.nonexecutable === '', node_is_executable: stats.executable, }; } } const tut=new Tutrial({ conn: 'ssh', host: '127.0.0.1' }); tut.check().then((done)=>{ console.log(done); });
queryの部分でnoneというコマンドとnodeというコマンドのパスをwhichコマンドで引くことができるか確認しています。noneというコマンドは、普通は存在しませんからtestの中でパスが空''であることを検証しています。一方nodeというコマンドは、このNode.jsのコードが実行できている以上、どこかに存在しますからwhichコマンドの結果に何らかのパスが含まれていることでしょう。test関数の戻り値に含まれている2つの値(none_is_not_executableとnode_is_executable)はtut.check()という部分で評価され論理積(AND)でTrue/Falseが決定されます。結果はdone変数に格納されていますサーバに変更を加える
サーバの状態を取得して、それをテストしました。テストの結果が問題なければ、何もする必要はありませんが、テストで異常が発見された場合は、それを修正しなければなりません。テストがFalseになった場合にだけ実行されるコマンドを以下のように定義します
Node.jsconst Submarine = require('Submarine'); const Tutrial = class extends Submarine { query(){ return { file_content: String.raw` [ -r /tmp/submarine/hogehoge ] && { cat /tmp/submarine/hogehoge } || { echo 'File not readable' >&2 } ` }; } test(stats){ return { file_content_is_hogehoge: stats.file_content === 'hogehoge', }; } command(props){ return String.raw` mkdir -p /tmp/submarine \ && echo ${props.msg} > /tmp/submarine/hogehoge `; } } const tut=new Tutrial({ conn: 'ssh', host: '127.0.0.1' }); tut.correct({ msg: 'fugafuga' }).then((done)=>{ console.log(done); });
/tmp/submarine/hogehogeというファイルの内容が'hogehoge'であるか確認し、そうでない場合はファイルが作成され、内容は'hogehoge'で書き換えられます
doneにはコマンドが実行された場合は、実行したコマンドのreturn codeやstdoutの情報が格納され、実行されなかった場合はtest関数のときの結果が格納されます複数サーバーで実行する
上記の例は1台のサーバに対してコマンドを実行していましたが、複数台のサーバに実行することも可能です
Node.jsconst Submarine = require('Submarine'); const Tutrial = class extends Submarine { query(files){ return { availables: String.raw` df -P \ |awk '{print $4}' \ |grep "^[0-9]*$" ` }; } format(stats){ return { available_max: Array.isArray(stats.availables) ? stats.availables.map( available => available * 1 ).sort((a ,b)=>{ return a < b ? -1 : a == b ? 0 : 1; }).reverse()[0] : stats.availables * 1 }; } } const Tutrials = Submarine.hosts( host => new Tutrial({ conn: 'ssh', host: host }), server1, server2, server3, server4, server5 ); const tut = new Tutrials(); tut.current().then( hosts => hosts.map( host => host.available_max ).reduce((a, b)=>{ return a + b; }) / 1024 / 1024 ).then((available_sum)=>{ console.log(available_sum); });
server1からserver5がそれぞれnew Tutrial...でインスタンス化されtut.current()で全台に対してquery関数が評価されます。結果はホスト1台のときと同じフォーマットの結果が、配列で返されます(コードのhostsに格納されている)このサンプルコードでは、5台のサーバのディスク空き容量を足し合わせています
おわりに
他にも、Ansibleの基本的な機能は置き換えられるようか機能が実装されております
各機能のTutrialを鋭意作成中です
https://gitlab.com/mjusui/submarine/tree/master/doc/en/Tutrial
- 投稿日:2019-05-30T00:07:01+09:00
Vue.jsで昔懐かしのframeタグを実現する方法
前置き
HTML5以前の時代には、frameタグなるものが存在していました。
1つのWEBページの中に複数のWEBページを埋め込んで上下や左右に並べて表示させるタグです。
2000年代前半あたりにはframeタグを活用した個人サイトなどが数多く存在したような気がします。
(index.htmlの中にframesetを書いてフレームの左側にmenu.html、右側にtop.htmlとかやってたり)HTML5が登場してからはframeタグはframesetタグと共に非推奨となり、今ではもうこのタグを使っているサイトはほぼ無いと思います。
ですがもしかするとframeタグが今でも好きな方がいらっしゃるのではと思い、代替案としてVue.jsを用いてframeタグらしきものを再現してみました。
(実際に需要があるかどうかは特に調査していませんので気にしないでください。)デモ
vue-frame
(frameの境目をドラッグしてコンテンツの幅を変えられます)動作イメージ
ざっくり解説
構成
左側
LeftFrame.vue<template> <div class="left-frame" v-bind:style="{width:width + 'px'}"> <div class="left-frame-content">ヒダリー</div> <div class="frame-border" @mousedown="$emit('startResize')"></div> </div> </template> <script> export default { props: { width: Number } }; </script> <style scoped> .left-frame { background-color: rgb(240, 240, 255); display: flex; } .left-frame-content { flex-grow: 1; padding-left: 10px; } .frame-border { width: 3px; background-color: rgb(208, 208, 208); border-left: solid 0.5px rgb(170, 170, 170); border-right: solid 0.5px black; } .frame-border:hover { cursor: col-resize; } </style>
- コンテンツの横幅は可変にするため、変数で保持
- フレームの境界線にはドラッグイベントを持たせるため、borderっぽいdiv要素(frame-border)を配置
右側
jRightFrame.vue<template> <div class="right-frame">ミギー</div> </template> <style scoped> .right-frame { background-color: white; flex-grow: 1; padding-left: 10px; } </style>
- 画面右側に表示させたいコンテンツを配置するだけ
本体
Frame.vue<template> <div class="frame" v-bind:class="{dragged: isDragged}" @mousemove="resizeFrame" @mouseup="endResizeFrame"> <left-frame v-bind:width="leftWidth" @startResize="startResize"></left-frame> <right-frame></right-frame> </div> </template> <script> import LeftFrame from "./LeftFrame.vue"; import RightFrame from "./RightFrame.vue"; const LEFT_FRAME_MIN_WIDTH = 45; const FRAME_ADJUSTED_SETTING = 2; export default { components: { LeftFrame, RightFrame }, data() { return { isDragged: false, leftWidth: 200 }; }, methods: { startResize() { this.isDragged = true; }, resizeFrame(event) { if (event.buttons === 0) { this.endResizeFrame(); return; } if (this.isDragged) { if (event.clientX + FRAME_ADJUSTED_SETTING < LEFT_FRAME_MIN_WIDTH) { this.leftWidth = LEFT_FRAME_MIN_WIDTH; return; } this.leftWidth = event.clientX + FRAME_ADJUSTED_SETTING; } }, endResizeFrame() { this.isDragged = false; } } }; </script> <style scoped> .frame { display: flex; flex-direction: row; height: 100vh; } .dragged * { cursor: col-resize; } </style>
- ドラッグ開始(startResize())はLeftFrameから発火させる
- mousemoveイベントでマウスの位置を監視する
- event.buttonsの値でマウスの押下状態を判断(===0ならマウス押下無し)
- マウスの位置(event.clientX)でleftFrameの幅を変更させる(フレームのstyleに合わせて適当に微調整)
ソースコード全体
参考資料




































