20190530のJavaScriptに関する記事は24件です。

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.js
var 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でページリンクを表示させたいときは注意が必要。
最新のチャットで止まっているときは、リンクとして機能するのですが、チャットが進んでいくと、リンクが機能しなくなります。

スクリーンショット
(↑リンクとして機能している例)

スクリーンショット
(↑チャットが進んで、リンクが機能しなくなる)

でも、全体的にめっちゃ簡単にチャットが作れるので、やっぱり素晴らしいライブラリですよね!!

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

S3にアップロードした画像をAjaxで取得する

やりたいこと

デバッグ等で、S3にアップロードされているかどうかをチェックした後に、
URLを差し替えて画像を表示したいというニッチなニーズに遭遇。

普通に何も考えずに実装すると

has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

という感じに怒られる。
ちゃんとヘッダを設定して、クロスドメインを有効にしましょうという話。

S3のCORS設定

s3_cors_settings.png

<?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'));
        });
      }
    });

かなり急いで作ったので雑ですが、意図した挙動になりました :innocent:

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

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.md

  let 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

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

ゼロから始める生体認証webアプリケーション作成(3)localhost上での画像加工

はじめに

はじめに謝らせていただきます...
前回の記事で使ったopencv4nodejsですが....localホスト上で使う方法がわからなかったのでクビになりました。
代わりに本家opencvさんを使います。

1.node.js+express+jage環境構築

Expressの開発環境構築~デバッグ環境構築 ここを参考にしましょう。
私が書くより絶対わかりやすい。

スクリーンショット 2019-05-30 17.50.10.png

expressの中身はこんな感じになっています。
作ったスクリプトなどはpublicにHTML(拡張子が.jade似合っているけど...)はviewsに入れます。
他もいじれるけど、私みたいな初心者は触らないに限る。

2.スクリプトの修正

受け取った画像を100x100に縮小して、グレイスケール化します

processing_cv.js
et 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.jade
doctype 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に繋ぐとこんな感じに表示されます
スクリーンショット 2019-05-30 18.06.19.png

次に有名なlennaの画像を与えてみます。

スクリーンショット 2019-05-30 18.10.47.png

縮小してグレイスケール化した画像が出力されたことが確認できました。

まとめ

localhost上での画像加工を行うことができた。
次回は、少しjavascriptから離れて、顔認証システムを機械学習を用いて実装します。

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

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();

注意事項

スクレイピングを禁止しているサイトもありますのでご利用の際はお気をつけくださいませ.


  1. 今回, web サーバ上のファイル = CSV ファイル を想定 

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

Javascript 配列の最大値と最小値を求める

何度かいても忘れるやつ
MAX/MINを求めたい配列listがあるとして

min.js
const Min = list.reduce((a,b)=>a<b?a:b)
max.js
const max = list.reduce((a,b)=>a>b?a:b)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsでカレンダーを自作してみた

概要

チームで運用するスケジュール表を自作しようと思い、NuxtとAPIで作成できないか試行錯誤してみた内容を
記録しておく為の記事になります。CSSの説明は省きますが、完成イメージは :arrow_down: こんな感じの物を紹介していきます。
schedule.gif

※今回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.js
export 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:日付』です。そのままですね:sweat:
初期値は当日の「年・月・日」を取得するようにしてあります。

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.js
export 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)">&lt;&lt;</span> {{ schedule.year }}{{ schedule.month }}<span class="arrow" @click="changeMonth(1)">&gt;&gt;</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文内で毎回長々と呼び出し分を書く必要がなくなります。
firstweeklastdayの取得の仕方の詳細は省かせていただきます。

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)">&lt;&lt;</span> {{ schedule.year }}年{{ schedule.month }}月 <span class="arrow" @click="changeMonth(1)">&gt;&gt;</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)">&lt;&lt;</span> {{ schedule.year }}年{{ schedule.month }}月 <span class="arrow" @click="changeMonth(1)">&gt;&gt;</span>
</div>

<span class="arrow" @click="changeMonth(-1)">&lt;&lt;</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>
ここのループは日付・曜日部分のループと全く同じで、中身は空で渡しています。

以上で完成です:smile: !

おわりに

かなり長々となってしまいましたが、最後まで読んでくださった皆様ありがとうございました!
中々複雑な構造になってしまいましたが、もっとスマートなStoreの呼び出し方や、
v-forの回数を減らせる記述方法などがあれば教えていただきたいです。

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

Catapultのトランザクションを解剖してみよう!

今回は送信に成功したトランザクションの中身を解剖していきます。

まずは、Catapult Transaction Viewer を作成したのでそちらをご覧ください。
http://xembook.net/xembook-sdk/snapshot/20190526/examples/270_transaction.html?hash=047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFD

meta

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
    36867

deadline:トランザクションの有効期限です。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)
    0

id:mosaicを一意に識別するidです。uint64型
toHex(id):10進数で表記するととても長くなるので、16進数のHEX値で表記します。
amount:送信するmosaic量です。uint64型
compact(amount):10進数表記です。

大体こんな内容になりました。
次回からはいろいろなトランザクションを発行していきたいと思います!

関連記事

  1. Catapultアカウントを作ろう!
  2. Catapultのトラザクションを送信しよう!
  3. Catapultのトランザクションを解剖してみよう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/047DE1D1BD9B67FE8C5910CA75457723FAD7DB1B565D854F49FCE5513E9A0DFD

meta

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
    36867

deadline:トランザクションの有効期限です。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)
    0

id:mosaicを一意に識別するidです。uint64型
toHex(id):10進数で表記するととても長くなるので、16進数のHEX値で表記します。
amount:送信するmosaic量です。uint64型
compact(amount):10進数表記です。

大体こんな内容になりました。
次回からはいろいろなトランザクションを発行していきたいと思います!

関連記事

  1. Catapultアカウントを作ろう!
  2. Catapultのトラザクションを送信しよう!
  3. Catapultのトランザクションを解剖しよう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラジオボタンの選択状態に合わせてテキスト表示を変更させる(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.最後に

うまくコードの説明ができていないところがあるかもしれないので、後で見直そうと思います。
もし、分かりにくいところがありましたら、ご指摘いただきますと助かります。

拙文に最後までお目通しいただき、ありがとうございました!

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

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

当然ですがコンソールが開いている間だけ自動コンパイルしてくれます。

以上です。

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

超最低限の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

当然ですがコンソールが開いている間だけ自動コンパイルしてくれます。

以上です。

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

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.js
export default {
    apiUrl : 'https://dev-example.com'
}
prd.js
export default {
    apiUrl : 'https://example.com'
}

このような感じでステージごとの環境変数を設定していきます。

package.json の scripts 設定

package.json に npm run <ステージ>の実行コマンドを定義します。
ステージごとに環境変数定義ファイルを動的に読み込むために NODE_ENV=stg を設定します。
NODE_ENVwebpack.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.js
const 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.js
import 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-envdotenvなどがありますがそれらを利用する場合の違いやメリット・デメリットがよくわかってないのでわかる人いたら教えてくださいm(_ _)m

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

素の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でも解説されていました。さすが西畑先生です。

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

[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),
            ], //★↑ここ
          },
        ],
      }
    },
  },
}

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

はじめての 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 dev

マシン環境にもよるが、まあまあ時間はかかる
スクリーンショット 2019-05-27 13.13.34.png

2 回目以降はnode_modules/.cacheの下にbabel-loaderのキャッシュを作ってる影響からか、少し速くなっている
スクリーンショット 2019-05-27 13.13.47.png

でもホットリローディングをサポートしているので、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.ts
import 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.ts
interface 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-prettiereslint-config-prettierが依存パッケージとしてインストールされており、.eslintrc.jsの extends に prettier が設定してあるので、ESLint と Prettier の競合は気にしなくて良さそう

TS 用にLinterを使う場合の修正箇所は以下

yarn add -D @typescript-eslint/eslint-plugin
.eslintrc.js
parserOptions: {
-    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.js
rules: {
  'no-unused-vars': 'off',
  '@typescript-eslint/no-unused-vars': 'error'
}

Airbnb の ESLint ルールを入れてみる

yarn add -D eslint-config-airbnb-base
.eslintrc.js
extends: [
  '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.js
settings: {
  '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 から作る派なのですが、たまに自動で環境作ってくれる系ツールを使うとそのお手本のような構成に勉強になることがあります
また、今後フロントエンドの環境を作る上で活かせることもあると思うので、試しに使ってみるのもありかなと思いました

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

Javascriptで電卓を作ろう 第4回 小数点を正しく入力しよう

目次

第1回 HTMLとCSSでボタンを横並びで配置しよう

  • HTMLのマークアップ
  • CSS

第2回 四則演算をやってみよう

  • Javascriptで四則演算
  • 6)打った数字をコンソールに出力させよう。
  • 7)打った数字と記号を変数に格納しよう。
  • 8)計算結果を出力させよう。
  • 8-1)「=」を押した時の処理
  • 8-2) 計算結果と計算途中を、計算結果の画面に表示させよう。

第3回不具合の改善1

  • 最初に「+ ÷ - × =」が押せないようにする。
  • 二回連続で計算できるようにしたい。
  • 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;
    }
  })

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

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を使う方が良いかもしれません…。

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

Vuetifyのv-iconに自作SVGアイコンを(楽に)使いたい!!

TL;DR

SVGアイコンをWebfontにしてstyle.cssをインポートすると
<v-icon>{名前}</v-icon>で使えるようになるよ!

Vuetifyのリファレンスがかなりアレ

Vuetifyのv-iconってFontAwesomeとかのアイコンを利用できるじゃないですか。
でも自作のSVGアイコンも使いたいですよね。

一応リファレンスにやり方が載っています。

Installing Icons — Vuetify.js

簡単に訳すと、

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で試してみます。

サイトにアクセス

スクリーンショット 2019-05-30 9.33.52.png

Icon Font & SVG Icon Sets ❍ IcoMoon

アクセスしたら右上のIcoMoon Appをクリックしましょう

スクリーンショット 2019-05-30 9.34.00.png

でこんな感じになったら自作SVGアイコンをドラッグアンドドロップ

スクリーンショット 2019-05-30 9.41.45.png

アップロードしたアイコンが表示されたら、Webfontを生成したいアイコンをクリック(複数可能です)

クリックしたら右下のGenerate Fontをクリックして先に進みましょう

スクリーンショット 2019-05-30 9.42.10.png

この画面では アイコンの名前とコードを決められます。
コードは一意に定まる様にし、名前はわかりやすい名前をつけます。ここでの名前は実際に使用する際に使われます

設定が終わったら右下のDownloadボタンをクリックするとicomoon.zipファイルのダウンロードが始まります

Vueのプロジェクトに追加

解凍したicomoonフォルダをsrc/assetsに配置しましょう。
だいたいこんな感じになると思います。

スクリーンショット 2019-05-30 11.18.30.png

早速使ってみましょう

今回はVueのプロジェクトにvuetifyを入れたあと、HelloWorld.vueを編集し動作の確認を行います。

style.cssをインポートしての中に icon-{名前} を入れて、実行してみましょう。

スクリーンショット 2019-05-30 11.21.16.png

僕は先程 svgという名前とemoiという名前をアイコンに付けているので使用する際は上記の様になります。

ここで実行してみましょう

スクリーンショット 2019-05-30 11.20.57.png

うまくいってますね。 これでアイコンを表示させることが出来ました。

追加の設定

インポートしたstyle.cssを見ると、colorの設定があります。

v-icon側でcolorを設定したい場合、style.cssにあるcolorの設定を削除する必要があります。

実際の運用

親コンポーネントでimportするのはアレなのでv-iconをラッピングしたoreore-v-iconみたいな単一コンポーネントを作ってそこでインポートするのが運用としては正しいと思います。

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

初めてのフロントエンド。

はじめに

私は今までバックエンドとしてサーバー周りだったり、apiがどうのこうのを主にGolangで作業をしてたのですが、この度フロントエンドをやってみることに。

表側もかなり大変ですね ~

javascriptとは

Javaとは別物の言語でオブジェクト指向型スクリプト言語だが、クラスの概念もあると。

なんだか面白そうだと思いました。

今回は引数でmethodやendopoint,パラメーター,必要なcolumn情報(DB)を渡してレスポンスを得る関数を作成しただけですので、クラス等はまた今度。

XMLHttpRequest

さて、早速調べていくのですが、早速いいもの見つけました。
XMLHttpRequestというのがあるじゃないですか。

XMLHttpRequestとは。

Ajax(非同期通信)で使われる組み込みオブジェクトのことで、サーバーへ通信リクエストが送れるようになる。とのことです。

コーディング開始

もうこれ使うしかないなと思って、早速調べながらコーディングを始めました。
で、一番最初に書いたコードがこちら ...

loginApiRequest.js
function 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.js
a = request.onload = function () {
      ...
}

return a

血迷っちゃったんでしょうか。
.onloadで取得した結果をそのままreturnしようとしたんです ...
反省してます。

いくつもの試行錯誤を重ね、最終的にたどり着いたコードが以下です。

loginApiRequest.js
function 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
こちらの記事は特に大変参考になりましたありがとうございます。

https://ja.wikipedia.org/wiki/JavaScript

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

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&parametersという形式

仕様上、ズーム指定できません。

Direction(道順)

指定した場所間の道順を表示するリクエスト

https://www.google.com/maps/dir/?api=1&parametersという形式

Display a map(地図を表示)

SearchやDirectionと異なり、ピンや道順を表示せずに、単に地図を表示するリクエスト

https://www.google.com/maps/@?api=1&map_action=map&parametersという形式

Display a Street View panorama(ストリートビュー表示)

ストリートビューのパノラマ画像表示のリクエスト

https://www.google.com/maps/@?api=1&map_action=pano&parametersという形式

試してみる

東京駅をターゲットにしてみます。GoogleMapで検索すると、緯度経度は35.6812362,139.7649361となっているので、それを使いましょう。

検証にはEdgeを使っています。

Search

パラメータは、queryまたはquery_place_idです。どちらかが必須です。query_place_idの取得方法は調べ切れませんでした。

https://www.google.com/maps/search/?api=1&query=東京駅

image.png

ウィンドウ小さくした影響か、ピンは隠れていますが出てきます。日本語でそのまま検索しても、一応問題なく使えます。検証は必要かと思います。

次に緯度経度です。

https://www.google.com/maps/search/?api=1&query=35.6812362,139.7649361

image.png

こちらも問題ないですね。

東京駅周りの書店、なんて指定もできます。キーワードを+で区切ります。

https://www.google.com/maps/search/?api=1&query=東京駅+書店

image.png

Direction

必須パラメーターは、

パラメーター名 意味
origin スタート地点
destination 目的地

です。Searchと同じく、place_idも使えるようです。

東京駅から横浜駅に行ってみます。

https://www.google.com/maps/dir/?api=1&origin=東京駅&destination=横浜駅

image.png

おお…これはすごい。ちゃんと出る。

以下、オプションのパラメーターを見ていきます。

travelmode

初期に表示される移動方法を指定できるようです。

  • 車(driving)

https://www.google.com/maps/dir/?api=1&origin=東京駅&destination=横浜駅&travelmode=driving

image.png

おお…!

他にも指定できます。指定しない場合は、ルートやユーザー設定に基づいて選択されるそうです。

dir_action=navigate

経路案内してくれるパラメーターのようです。現在位置や区間に基づいて行われるようで、詳細な検証ができませんでした…。
経路案内できない場合は、このパラメーター無視するよ、と書いてあります。

waypoints

経由地を加味したルート検索を行ってくれます。waypointsに指定するのは、origindestinationと同じく、建物名や緯度経度等になります。複数指定する場合は|で区切ります。

経由地計算も、行えない場合は無視されるそうです。

Display a map

https://www.google.com/maps/@?api=1&map_action=map

と、パラメータ無しで開けます。この場合は現在地等に基づいた表示になるのだろうと思います。

以下、オプションのパラメーターを見ていきます。

center

中心点を指定します。緯度経度での指定になります。

https://www.google.com/maps/@?api=1&map_action=map&center=35.6812362,139.7649361

image.png

東京駅が中心になりました。

zoom

地図の倍率を指定します。0~21だそうです。最大にしてみましょう。

https://www.google.com/maps/@?api=1&map_action=map&center=35.6812362,139.7649361&zoom=21

image.png

最大は大きすぎますね。

basemap

地図タイプ指定です。roadmap(通常、未指定の場合これ)、satellite(衛星)、terrain(地形図)の三種類です。

  • satellite

https://www.google.com/maps/@?api=1&map_action=map&center=35.6812362,139.7649361&&basemap=satellite

image.png

  • terrain

https://www.google.com/maps/@?api=1&map_action=map&center=35.6812362,139.7649361&&basemap=terrain

image.png

東京で地形図を出しても、高低差が無いのであまり意味が無いですね…

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&parameters&viewpoint=35.6812362,139.7649361

image.png

思いっきり、駅構内みたいですね。

追加パラメーターも見ていきます。

heading

デフォルトを北として、そこから何度向き直すか、という指定です。

https://www.google.com/maps/@?api=1&map_action=pano&parameters&viewpoint=35.6812362,139.7649361&heading=-90

image.png

-90だと、左を向くようです。つまりプラスだと(自分が)時計回りするイメージみたいです。

pitch

上を向くか下を向くか、という感じです。-90~90の範囲で指定します。

https://www.google.com/maps/@?api=1&map_action=pano&parameters&viewpoint=35.6812362,139.7649361&heading=-90&pitch=45

image.png

headingと同じ位置で45度上を向いてみました。なるほど。

fov

ズーム倍率、のようです。値が小さいほどズーム倍率が大きくなる(近くなる)ようです。

https://www.google.com/maps/@?api=1&map_action=pano&parameters&viewpoint=35.6812362,139.7649361&heading=-90&fov=40

image.png

余談

これらの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.js
const 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を用意します。すると、

image.png

image.png

image.png

ユーザーの許可を得て現在位置を取得した上で、Directionを指定してGoogleMapを呼び出す、なんてこともできますね。

使い方次第でいろいろ便利かもしれない、と思った次第です。

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

ReactやVueなどのJavascriptフレームワークからRailsの多対多モデルに対して、関連付けも含めて一気に作ってみた

こんにちは!@hairgaiです。
突然ですが、とあるTeamを作成するときにMemberを一緒に所属させたい!と思うことって一日に一回くらいありませんか?
今回は、それを実装するときにJavascriptからRailsへのパラメータで若干悩んだので、それを共有しようかなと思います。

前提

よくある多対多です。

  • Teamモデル
    • has_many :team_members
    • has_many :members, through: :team_members
  • TeamMemberモデル
    • belongs_to :team
    • belongs_to :member
  • Memberモデル
    • has_many :team_members: :destroy
    • has_many :teams, through: :team_members

React 16.8.6、Rails 5.2.2

実装

Memberが数人いて何かTeamを作るときに、一度作成して後から所属させるのは面倒なので、当然ながら「Teamを作るときにMemberを所属させつつ作れたらいいのに」と思うと思います。
なので、フロントでTeamを作成するときに、既に登録済みのMemberを一覧で表示して、それを選択させることでパラメータを同時に送らせることにします。

image.png

こんな感じですね。

また、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に沿えば非常に強力なフレームワークですが、その分色々な決まりがあるので、こういった細かいところもキチンと調べていきたいですね。

この記事が誰かの参考になれば幸いです。

参考

ありがとうございました!

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

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.js
const 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 など)が localhostbash で実行される、ということを指定しています

クラスをインスタンス化するときに例えば { conn: 'ssh', host: <ip address> } といった具合に指定すれば、指定したIPアドレスにsshして、コマンドが実行されます

サーバの状態をテストする

今度は取得した情報をテストします

Node.js
const 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_executablenode_is_executable)は tut.check() という部分で評価され論理積(AND)でTrue/Falseが決定されます。結果は done 変数に格納されています

サーバに変更を加える

サーバの状態を取得して、それをテストしました。テストの結果が問題なければ、何もする必要はありませんが、テストで異常が発見された場合は、それを修正しなければなりません。テストがFalseになった場合にだけ実行されるコマンドを以下のように定義します

Node.js
const 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.js
const 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

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

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の境目をドラッグしてコンテンツの幅を変えられます)

動作イメージ

vue-frame.gif

ざっくり解説

構成

キャプチャ.JPG

左側

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に合わせて適当に微調整)

ソースコード全体

vue-frame(GitHub)

参考資料

<frame> - HTML: HyperText Markup Language | MDN

jQueryを使った擬似フレーム(高機能版)

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