20200520のJavaScriptに関する記事は30件です。

web初心者が気分転換できそうなトリビアボタンを作ってみた(Vue.js + Trivia API)

概要

 普段は組込みソフトエンジニアをしており、設計やデバッグが思うように進まず、集中が続かなかったり脳をリフレッシュしたいことが多々あります。

そこで、脳をリフレッシュするためにブラウザさえ使えれば気分転換できる
トリビアボタン
を作ってみました。

デモ

こちらから利用できますのでお試しあれ~?

※2020/5/20現在、エスケープ文字がおかしな表示になっている不具合があります。

中身はどうなっているか

構成

 ざっくりとですが、次のような構成になっています。
image.png

トリビアのデータは「OPEN TRIVIA DATABASE」のTrivia APIで取得て利用しています。

お気づきの方もいらっしゃるかもしれないですが、ドメインがherokuではないです。これは、「freenom」という無料でドメイン取得ができるサービスを利用しています。

開発環境

 2020/5/20現在の開発環境です。
- Windows10
- VS code 1.45.0
- Node.js v10.15.3

ソースコード解説

inde.html
<html>
<head>
  <title>Refreshing trivia</title>
</head>
<body>
  <p>Refreshing trivia</p>
  <div id="app">
    Question:<br>
    <br>
      {{ message }}<br>
    <br>
    Answer:<br>
     {{ ans }}<br>
    <br>
    <button v-on:click="getData();">次のトリビア</button>
    <br>
    ※ネット環境によりロードに数秒かかる事があります。
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '',
        ans: ''
      },
      methods: {
        getData: async function () {
          const response = await axios.get('/api')
          console.log(response.data);
          this.message = response.data.results[0].question;
          this.ans = response.data.results[0].correct_answer;
        },
      },
      mounted: function () {
        this.getData();
      }
    })
  </script>
</body>
</html>

 index.htmlでは、Vue.jsでボタンを押したときの処理を実装しています。こうすることでシングルページアプリケーション(SPA)を実現しています。

app.js
// expressライブラリを呼び出す
var express = require('express');
var app = express();

// axiosライブラリを呼び出す
const axios = require('axios');

// public というフォルダに入れられた静的ファイル(HTMLファイル・CSSファイル・ブラウザ上のJavaScriptファイル)はそのまま表示
app.use(express.static(__dirname + '/public'));

app.get('/api', async function(req, res) {
  let response;
  try {
    // Trivia APIにリクエストして、データを取得する
    response = await axios.get('https://opentdb.com/api.php?amount=1');
    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
  // 結果をJSONに割り当てる
  res.json(response.data);
});

//app.listen(8080);
app.listen(process.env.PORT || 8080);

 app.jsではTrivia APIにリクエストを出してデータを取得し、index.htmlに渡しています。

問題発覚!

ここで、Trivia APIの癖が発覚しました。Trivia APIから取得したデータをそのまま利用すると、次のようにエスケープ文字が変換されてしまっています。

image.png

こちらの方法でアンエスケープしてやれば修正できるのではないかとトライしています...。

おわりに

 トリビアは英語限定なので、翻訳機能があると嬉しいですね。これでも英語の勉強になる...?からよしとします。

参考

Trivia API
freenom
エスケープ文字をアンエスケープする方法

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

web初心者が、気分転換トリビアボタンを作ってみた(Vue.js + Trivia API)

概要

 普段は組込みソフトエンジニアをしており、設計やデバッグが思うように進まず、集中が続かなかったり脳をリフレッシュしたいことが多々あります。

そこで、脳をリフレッシュするためにブラウザさえ使えれば気分転換できる
トリビアボタン
を作ってみました。

デモ

こちらから利用できますのでお試しあれ~?

※2020/5/20現在、エスケープ文字がおかしな表示になっている不具合があります。

中身はどうなっているか

構成

 ざっくりとですが、次のような構成になっています。
image.png

トリビアのデータは「OPEN TRIVIA DATABASE」のTrivia APIで取得て利用しています。

お気づきの方もいらっしゃるかもしれないですが、ドメインがherokuではないです。これは、「freenom」という無料でドメイン取得ができるサービスを利用しています。

開発環境

 2020/5/20現在の開発環境です。
- Windows10
- VS code 1.45.0
- Node.js v10.15.3

ソースコード解説

inde.html
<html>
<head>
  <title>Refreshing trivia</title>
</head>
<body>
  <p>Refreshing trivia</p>
  <div id="app">
    Question:<br>
    <br>
      {{ message }}<br>
    <br>
    Answer:<br>
     {{ ans }}<br>
    <br>
    <button v-on:click="getData();">次のトリビア</button>
    <br>
    ※ネット環境によりロードに数秒かかる事があります。
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '',
        ans: ''
      },
      methods: {
        getData: async function () {
          const response = await axios.get('/api')
          console.log(response.data);
          this.message = response.data.results[0].question;
          this.ans = response.data.results[0].correct_answer;
        },
      },
      mounted: function () {
        this.getData();
      }
    })
  </script>
</body>
</html>

 index.htmlでは、Vue.jsでボタンを押したときの処理を実装しています。こうすることでシングルページアプリケーション(SPA)を実現しています。

app.js
// expressライブラリを呼び出す
var express = require('express');
var app = express();

// axiosライブラリを呼び出す
const axios = require('axios');

// public というフォルダに入れられた静的ファイル(HTMLファイル・CSSファイル・ブラウザ上のJavaScriptファイル)はそのまま表示
app.use(express.static(__dirname + '/public'));

app.get('/api', async function(req, res) {
  let response;
  try {
    // Trivia APIにリクエストして、データを取得する
    response = await axios.get('https://opentdb.com/api.php?amount=1');
    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
  // 結果をJSONに割り当てる
  res.json(response.data);
});

//app.listen(8080);
app.listen(process.env.PORT || 8080);

 app.jsではTrivia APIにリクエストを出してデータを取得し、index.htmlに渡しています。

問題発覚!

ここで、Trivia APIの癖が発覚しました。Trivia APIから取得したデータをそのまま利用すると、次のようにエスケープ文字が変換されてしまっています。

image.png

こちらの方法でアンエスケープしてやれば修正できるのではないかとトライしています...。

おわりに

 トリビアは英語限定なので、翻訳機能があると嬉しいですね。これでも英語の勉強になる...?からよしとします。

参考

Trivia API
freenom
エスケープ文字をアンエスケープする方法

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

気分転換にトリビアボタンをポチろう(Vue.js + Trivia API)

概要

 どうしても集中が続かないとき、脳をリフレッシュしたいですよね。

ブラウザさえ使えれば気分転換できる
トリビアボタン
を作ってみました。

デモ

こちらから利用できますのでお試しあれ~?

※2020/5/20現在、エスケープ文字がおかしな表示になっている不具合があります。

中身はどうなっているか

構成

 ざっくりとですが、次のような構成になっています。
image.png

トリビアのデータは「OPEN TRIVIA DATABASE」のTrivia APIで取得て利用しています。

お気づきの方もいらっしゃるかもしれないですが、ドメインがherokuではないです。これは、「freenom」という無料でドメイン取得ができるサービスを利用しています。

開発環境

 2020/5/20現在の開発環境です。
- Windows10
- VS code 1.45.0
- Node.js v10.15.3

ソースコード解説

inde.html
<html>
<head>
  <title>Refreshing trivia</title>
</head>
<body>
  <p>Refreshing trivia</p>
  <div id="app">
    Question:<br>
    <br>
      {{ message }}<br>
    <br>
    Answer:<br>
     {{ ans }}<br>
    <br>
    <button v-on:click="getData();">次のトリビア</button>
    <br>
    ※ネット環境によりロードに数秒かかる事があります。
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '',
        ans: ''
      },
      methods: {
        getData: async function () {
          const response = await axios.get('/api')
          console.log(response.data);
          this.message = response.data.results[0].question;
          this.ans = response.data.results[0].correct_answer;
        },
      },
      mounted: function () {
        this.getData();
      }
    })
  </script>
</body>
</html>

 index.htmlでは、Vue.jsでボタンを押したときの処理を実装しています。こうすることでシングルページアプリケーション(SPA)を実現しています。

app.js
// expressライブラリを呼び出す
var express = require('express');
var app = express();

// axiosライブラリを呼び出す
const axios = require('axios');

// public というフォルダに入れられた静的ファイル(HTMLファイル・CSSファイル・ブラウザ上のJavaScriptファイル)はそのまま表示
app.use(express.static(__dirname + '/public'));

app.get('/api', async function(req, res) {
  let response;
  try {
    // Trivia APIにリクエストして、データを取得する
    response = await axios.get('https://opentdb.com/api.php?amount=1');
    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
  // 結果をJSONに割り当てる
  res.json(response.data);
});

//app.listen(8080);
app.listen(process.env.PORT || 8080);

 app.jsではTrivia APIにリクエストを出してデータを取得し、index.htmlに渡しています。

問題発覚!

ここで、Trivia APIの癖が発覚しました。Trivia APIから取得したデータをそのまま利用すると、次のようにエスケープ文字が変換されてしまっています。

image.png

こちらの方法でアンエスケープしてやれば修正できるのではないかとトライしています...。

おわりに

 トリビアは英語限定なので、翻訳機能があると嬉しいですね。これでも英語の勉強になる...?からよしとします。

参考

Trivia API
freenom
エスケープ文字をアンエスケープする方法

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

jQueryで.on("change が動かない | JavaScript

動かない

$(function(){
  $("#xxx").on("change", function(){
     console.log("動かない");
   });
});

動いた

$(function(){
  $("#xxx").bind("input", function(){
     console.log("動いた");
   });
});

参考

https://gist.github.com/brandonaaskov/1596867

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

3日で忘れるPromiseまとめ

概要

  • Promise、身近な割に挙動詳細をわすれやすいので、いろんなパターンをコードを実際に書いて実行してみて思い出すための呼び水メモ、です

Promiseのポイント

Promiseには3つの「状態」がある

  • pending 初期状態
  • fulfilled 成功状態
  • rejected 失敗状態

Promiseのコンストラクタにはexecutor関数をセットし、Promiseに「値」をセットさせる

  • executor関数とは↓なやつ
 (resolve,reject)=>{} 

つまり↓のようにしてPromiseオブジェクトを作る

new Promise((resolve,reject)=>{});
  • executor関数には 一般的に、処理がおわるまでに待ちが発生するような、非同期に実行したい処理を書く
  • このexecutor関数内で resolve(値) を呼び出すと、Promiseはpending状態からfulfilled状態になる。
  • 逆にreject(値)を呼び出すと、Promiseはpending状態からrejected状態になる。

Promiseは「値」を持つ

  • Promiseはfulfilled,rejectedの状態になるときに「値」ももつ(値はundefinedも可)
  • Promiseの「値」はresolve(値)、reject(値)の呼び出しによってPromiseにセットされる。

(セットされた「値」はthenメソッドのコールバック関数の引数に入って、次の処理に受け渡される。→後述する)

つまり、Promiseは「状態」と「値」を持つ とおぼえる

ということで、
Promiseには 状態(pending→fulfilled or rejected) と 値(非同期処理終了後にexecutor関数でセットされた値) を持つ とおぼえておく。

image.png

Promiseにはthenメソッドがある

  • Promise内(executor関数)の処理が終わると、Promiseは、その状態が「fulfilled」か「rejected」に状態が変化した後、Promiseのthenメソッドに指定した関数が呼ばれる
  • thenには2つの引数をとる。1つでもいい。
  • 2つの引数にはPromiseがfulfilled状態になったとき(成功したとき)と、rejected状態になったとき(失敗したとき)のコールバック関数 を指定する
.then(
//成功したときのコールバック関数
(value)=>{},
//失敗したときのコールバック関数
(value)=>{}
) 
  • Promise内で resolve

  • thenメソッドは必ずPromiseを返却する

  • thenメソッドの引数に指定したコールバック関数内で何をreturnしようと、かならずthenメソッドの返値はPromiseになる。これ重要。

  • thenメソッドは必ずPromiseが返却されるので、then().then() とチェインできる。thenはPromiseのメソッドなので。

実際に動かしておもいだす

1.Promise.resolve()でfulfilled状態のPromiseを扱う

Promise.resolve("value1")を呼び出すと直接 fulfilled状態のPromiseが返値となる

すでに Promise.resolve("value1") はfullfilled状態なので、thenメソッドをつなげれば、すぐにthenメソッドに指定したコールバック関数に処理がうつる

See the Pen Promiseサンプル1 by Tom Misawa (@riversun) on CodePen.

2.Promiseは遅延実行じゃない、作ったらすぐ実行される

Promiseが生成されたら、(executor関数の)中に書いた処理はすぐ実行される

  // Promiseのコンストラクタには引数を2つ(resolve,reject)とる関数をわたす。
  const p2 = new Promise((resolve, reject) => {
    console.log("Promise(p2)のexecutor関数の中で2秒かかる非同期な処理中");
    setTimeout(function () {
      // 非同期処理をして、おわったらresolve
      console.log("非同期な処理終了!");
      //非同期処理が成功ならresolve,失敗ならrejectを呼び出す。
      //引数には、実行結果を入れる。これがthenで返却されるPromiseの値となる
      resolve("value2"); // resolveして成功とする
    }, 2000);
  });

See the Pen Promiseサンプル2 by Tom Misawa (@riversun) on CodePen.

3.PromiseはPromise外の処理をブロックしない

ブロックしちゃったら、Promiseの存在価値なし

  const p3 = new Promise((resolve, reject) => {
    console.log("Promise(p3)の中で非同期処理中。");
    setTimeout(function () {
      console.log("Promise(p3)の非同期処理終了");
      resolve("value3");
    }, 1000);
  });

  console.log("PromiseはPromise外のコード実行をブロックしない");

  p3.then((value) => {
    console.log(`Promise(p3)の実行結果:${value}`);
  });

See the Pen Promiseサンプル3 by Tom Misawa (@riversun) on CodePen.

4.thenメソッドは2つのコールバック関数を指定できる

Promiseがfulfilledかrejectedの状態になったとき、thenメソッドに指定されたコールバック関数が実行される

thenメソッドは、then(成功したときのコールバック関数,失敗したときのコールバック関数)のように記述する。

4-0.fulfilledなPromiseは、thenメソッドに指定した成功したときのコールバック関数が呼ばれる

// p40は値value40を持つfulfilledな状態のPromiseとなる
const p40 = Promise.resolve('value40');
p40.then(
  (value) => {
    console.log(`Promise(p40)はresolveされました 値:${value}`);
  },
  (value) => {
    console.log(`Promise(p40)はrejectされました 値:${value}`);
  });

See the Pen Promiseサンプル40 by Tom Misawa (@riversun) on CodePen.

4-1. rejectedなPromiseは、thenメソッドに指定した第二引数の失敗したときのコールバック関数が呼ばれる

// p41は値value41を持つfulfilledな状態のPromiseとなる
const p41 = Promise.reject('value41');
p41.then(
  (value) => {
    console.log(`Promise(p41)はresolveされました 値:${value}`);
  },
  (value) => {
    console.log(`Promise(p41)はrejectされました 値:${value}`);
  });

See the Pen Promiseサンプル41 by Tom Misawa (@riversun) on CodePen.

4-2. thenメソッドの引数となるコールバック関数を省略すると、thenメソッドは最後に実行されたPromiseの状態を受け継いだPromiseを生成して返す

const p42 = Promise.reject('value42');
p42
  // p42はrejected状態だけど、このthenメソッドにはrejected処理用のコールバック関数の指定が無い
  // rejected処理用のコールバック関数がないので、このthenメソッドはp42と同じ状態のPromiseを返す
  .then((value) => {
    console.log(`Promise(p42)はresolveされました 値:${value}`);
  })
  // ↓チェインされたこちらのthenメソッドに指定されてるrejected用のコールバック関数が呼ばれる
  .then(null, (value) => {
    console.log(`Promise(p42)はrejectされました 値:${value}`);
  });

最初に登場するthenメソッドにはreject用のコールバック関数がない。その場合は、thenメソッドは最後のPromiseの状態を受け継いでいるPromiseを生成する。この例でいえば、thenメソッドは「値として"value42"もち、rejected状態」の新たなPromiseを生成する。
この仕組みにより、ハンドリングされなかったPromiseがチェインされたthenメソッドで後段に受け継がれていくようなコードを書くことができる。

See the Pen Promiseサンプル42 by Tom Misawa (@riversun) on CodePen.

4-3 catchメソッドはthen(null,(value)=>{})の短縮版

上でもみたようにrejected状態のPromiseを処理する関数はthen(null,(value)=>{//rejectを処理する})のようにthenメソッドの第2引数に指定するが、rejectedなPromiseだけを処理したい場合はthen(null,(value)=>{})は冗長なので、catchメソッドをつかう。

const p43 = Promise.reject('value43');
p43
  .then((value) => {
      console.log(`Promise(p43)はresolveされました 値:${value}`);
    }
  )
  .catch((value) => {
    console.log(`Promise(p43)はrejectされました 値:${value}`);
  });

See the Pen Promiseサンプル43 by Tom Misawa (@riversun) on CodePen.

ということで、catchメソッドは引数1つ。引数にはPromiseが失敗したときのコールバック関数、つまりrejectedなPromiseを処理するための関数だけ指定する。

const p43part2 = Promise.reject('value43part2');
p43part2.catch((value) => {
  console.log(`Promise(p43part2)はrejectされました 値:${value}`);
});

See the Pen Promiseサンプル43part2 by Tom Misawa (@riversun) on CodePen.

5.thenメソッドは必ずPromiseを返却する

みてきたとおり、Promiseのthenメソッドには、Promiseが成功した(fulfilled状態)ときのコールバック関数と、失敗(rejected状態)ときのコールバック関数を指定できる。

コールバック関数がどんな値をreturnしようとも、thenメソッド自体は必ず Promise を返す。これを忘れない。

  • 以下は、値"value5"をもつfulfilled状態のPromiseを最初のthenの第1引数に指定したfulfilled用のコールバック関数内で処理している。
  • このコールバック関数で"value5-1"という値を返すと、thenメソッドは値"value5-1"を持ち、fulfilledなPromiseを返す
const p5 = Promise.resolve('value5');
p5
  .then((value) => { // ←このthenメソッドの返値は 値"value5-1"をもつfulfilledなPromiseとなる。つまり、Promise.resolve("value5-1")と同じ。
    console.log(`Promise(p5)はresolveされました 値:${value}`);

    // この「Promise(p5)がresolveされたとき用のコールバック関数」で
    // "value5-1"という値を返す。
    return 'value5-1';
  })
  .then((value) => {
    console.log(`前のPromiseはresolveされました 値:${value}`);
  });

See the Pen Promiseサンプル5 by Tom Misawa (@riversun) on CodePen.

5.1 thenメソッドに指定したコールバック関数が何も返さないと、thenメソッドは"undefined"な値を持つPromiseが返す

const p51 = Promise.resolve('value51');
p51
  //このthenの返値は Promise.resolve() とおなじく、値が"undefined"でfulfilled状態のPromise
  .then((value) => {
    // コールバック関数内でなにもreturnしない場合は
    // 値がundefinedでfulfilled状態のPromiseがこのthenの返値となる

    // なにも返さない
  })
  .then((value) => {
    console.log(`前のPromiseはresolveされました 値:${value}`);
  });

See the Pen Promiseサンプル6 by Tom Misawa (@riversun) on CodePen.

5.2 thenメソッドに指定したコールバック関数でPromiseを返すと、thenはコールバック関数の返値のPromiseの実行結果と同じ値・状態のPromiseを返す

const p52 = Promise.resolve('value52');
p52
  .then((value) => {

    // thenメソッドに指定したコールバック関数でPromiseを返すと、
    // そのthenメソッドの返値は、コールバック関数が返したPromiseと同じ状態、同じ値のものが返る

    //このPromiseは、非同期実行後にrejectedなPromiseをかえす
    return new Promise((resolve, reject) => {
      console.log('非同期処理を実行中。');
      setTimeout(() => {
        reject(value + '-rejected');
      }, 2000);
    });
  })
  .then((value) => {
    console.log(`前のPromiseはresolveされました 値:${value}`);
  }, (value) => {
    console.log(`前のPromiseはrejectされました 値:${value}`);
  })

See the Pen Promiseサンプル52 by Tom Misawa (@riversun) on CodePen.

5.3 thenメソッドやcatchメソッドの返値としてrejectedなPromiseを返したい場合は、引数に指定したコールバック関数内で throw ErrorするかrejectedなPromiseを返すかどちらか

5.3.1 Errorをスローしてrejectedにする

thenメソッドの引数に指定したコールバック関数内でErrorをスローすると、thenメソッドは「値にErrorをもつrejectedなPromise」を返す

const p53 = Promise.resolve('value53');
p53
  .then((value) => {
    // コールバック関数内で
    // Errorをスローすると、thenは値がErrorで、rejectedなPromiseを返す
    throw Error('error53');
  })
  .then((value) => {
      //よばれない
      console.log(`前のPromiseはresolveされました 値:${value}`);
    },
    (value) => {
      console.log(`前のPromiseはrejectされました 型:${Object.prototype.toString.call(value)}`);
      console.log(`前のPromiseはrejectされました 値:${value}`);
    });

See the Pen Promiseサンプル53 part1 by Tom Misawa (@riversun) on CodePen.

5.3.2 Promise.reject()を返してrejectedにする

thenメソッドの引数に指定したコールバック関数でPromise.reject()を返すと、thenメソッドは「rejectの引数に指定した値にをもつrejectedなPromise」を返す

const p53part2 = Promise.resolve('value53part2');
p53part2
  .then((value) => {

    // コールバック関数内で
    // rejectedなPromiseを返すと、thenはrejectedなPromiseを返す
    return Promise.reject('error53part2')

  })
  .then(
    (value) => {
      //よばれない
      console.log(`前のPromiseはresolveされました 値:${value}`);
    },
    (value) => {
      console.log(`前のPromiseはrejectされました 型:${Object.prototype.toString.call(value)}`);
      console.log(`前のPromiseはrejectされました 値:${value}`);
    });

See the Pen Promiseサンプル53part2 by Tom Misawa (@riversun) on CodePen.

5.3.3 Promise内の非同期処理でrejectして、rejectedにする

const p53part3 = Promise.resolve('value53part3');
p53part3
  .then((value) => {

    // thenに指定したコールバック関数内でrejectedなPromiseを返す
    return new Promise((resolve, reject) => {
      console.log('非同期処理を実行中。');
      setTimeout(() => {
        // 何かのエラー発生
        reject("error53part3");// ←この処理によって値"error53part3"をもつrejectedなPromiseとなる
      }, 2000);
    });

  })
  .then(
    (value) => {
      console.log(`前のPromiseはrejectされました 型:${Object.prototype.toString.call(value)}`);
      console.log(`前のPromiseはresolveされました 値:${value}`);
    },
    (value) => {
      console.log(`前のPromiseはrejectされました 型:${Object.prototype.toString.call(value)}`);
      console.log(`前のPromiseはrejectされました 値:${value}`);
    });

See the Pen Promiseサンプル53 part3 by Tom Misawa (@riversun) on CodePen.

6.Promiseをつかった直列実行

  • 3つのAPIを順番に呼び出していく例を考える
  • 呼び出し順序に意味がある前提で、(その気になれば)前のAPIの呼び出し結果を次のAPIでも使えるようにする

実験用に、処理に時間のかかるAPI(スタブ)を3つつくる。Web API呼び出してるつもり。
3つは、それぞれ 足し算、引き算、かけ算 ができる

// 処理に時間がかかる足し算API
function webApiAdd(a, b) {
  return new Promise((resolve, reject) => {
    // 処理に1秒かかる足し算APIをエミュレート
    setTimeout(() => {
      resolve(a + b);
    }, 1000);
  });
};

// 処理に時間がかかる引き算API
function webApiSub(a, b) {
  return new Promise((resolve, reject) => {
    // 処理に1秒かかる引き算APIをエミュレート
    setTimeout(() => {
      resolve(a - b);
    }, 1000);
  });
};

// 処理に時間がかかるかけ算API
function webApiMult(a, b) {
  return new Promise((resolve, reject) => {
    // 処理に2秒かかるかけ算APIをエミュレート
    setTimeout(() => {
      resolve(a * b);
    }, 2000);
  });
};

6.1 Promiseで非同期APIの直列呼び出し

  • 前段の呼び出し結果を後段で活用できるようにする
  • async/awaitはのちほど、ここではPromiseでがんばる
  • ポイントはthenの中で呼び出しをネストさせないこと
    (ネストを深くしてしまうと、せっかくのPromiseがもったいない)
足し算→引き算→掛け算の順番に実行して、すべての結果を返す関数
// 足し算→引き算→掛け算の順番に実行して、すべての結果を詰めるAPI
function webApi(a, b) {

  const container = {};
  return Promise.resolve(container)
    .then(container => {
      console.log('足し算APIを呼び出し中');
      return webApiAdd(a, b)
        .then((addResult) => {
          container.addResult = addResult;
          return container;
        })//rejectが発生しても、ここで .catchせずにreject状態のpromiseをそのまま返す仕様
    })
    .then((container) => {
      console.log('引き算APIを呼び出し中');
      return webApiSub(a, b)
        .then((subResult) => {
          container.subResult = subResult;
          return container;
        })
    })
    .then((container) => {
      console.log('かけ算APIを呼び出し中');
      return webApiMult(a, b)
        .then((multResult) => {
          container.multResult = multResult;
          return container;
        })
    });
}

呼び出しコードは

webApi(2, 3)
  .then((result) => {
    console.log(`すべての処理に成功しました 結果:${JSON.stringify(result)}`);
  })
  .catch((result) => {
    console.log(`途中で処理に失敗しました 結果:${JSON.stringify(result)}`);
  })

See the Pen Promiseサンプル61 by Tom Misawa (@riversun) on CodePen.

APIの順次呼び出し途中でエラーが発生した場合はどうなるか。

以下のように引き算API実行中にエラーが発生しまうことにする

function webApiSub(a, b) {
  return new Promise((resolve, reject) => {
    // 処理に1秒かかる引き算APIをエミュレート
    setTimeout(() => {
      reject("引き算APIでエラー発生");
    }, 1000);
  });
};

この状態でさきほどのコード↓を再度呼び出す

webApi(2, 3)
  .then((result) => {
    console.log(`すべての処理に成功しました 結果:${JSON.stringify(result)}`);
  })
  .catch((result) => {
    console.log(`途中で処理に失敗しました 結果:${JSON.stringify(result)}`);
  })

途中の引き算API呼び出しの失敗でrejectされる。

See the Pen Promiseサンプル61part2 by Tom Misawa (@riversun) on CodePen.

6.2 Promiseで非同期APIの並列呼び出し

Promise.allをつかうと、Promiseの並列処理ができる

先ほどの処理をPromise.allで並列処理に書き換える。
直列にくらべて、シンプルになる。

function webApiConcurrent(a, b) {
  return Promise.all([
    webApiAdd(a, b),
    webApiSub(a, b),
    webApiMult(a, b)])
    .then((results) => {
      const container = {};
      container.addResult = results[0];
      container.subResult = results[1];
      container.multResult = results[2];
      return container;
    })
}

呼び出しコードはさきほど同様以下のとおり

 webApiConcurrent(2, 3)
    .then((result) => {
      console.log(`すべての処理に成功しました 結果:${JSON.stringify(result)}`);
    })
    .catch((result) => {
      console.log(`途中で処理に失敗しました 結果:${JSON.stringify(result)}`);
    });

See the Pen Promiseサンプル62 by Tom Misawa (@riversun) on CodePen.

6.3 async/awaitを使った直列実行

Promiseついでに、async/awaitも書いておく

  • async

    • "async"を関数の冒頭につけた、async functionを宣言すると、その関数は Promise を返すようになる
    • async function内で return "value1";とすると、return Promise.resolve("value1"); をしたのと同じこと
  • await

    • async functionの中で、awaitをつけたPromiseがfulfilledまたはrejectedになるまで実行を止める(待つ)

さきほどの非同期APIリクエストの直列実行をasyncとawaitをつかって書き直すと以下のようになる。同期呼び出しのようにシンプルになった。

async function webApi2(a, b) {
  const container = {};
  console.log('足し算APIを呼び出し中');
  container.addResult = await webApiAdd(a, b);

  console.log('引き算APIを呼び出し中');
  container.subResult = await webApiSub(a, b);

  console.log('かけ算APIを呼び出し中');
  container.multResult = await webApiMult(a, b)

  return container;
}

呼び出しコードは、

 webApi2(2, 3)
    .then((result) => {
      console.log(`すべての処理に成功しました 結果:${JSON.stringify(result)}`);
    })
    .catch((result) => {
      console.log(`途中で処理に失敗しました 結果:${JSON.stringify(result)}`);
    });

See the Pen Promiseサンプル63 by Tom Misawa (@riversun) on CodePen.

6.4 async/awaitを使った並列実行

次は async/awaitを使った並列実行。といっても、並列実行自体はPromise.allにやらせる。Promise.allは実行結果をPromiseで返すので、そこでawaitによる待ちをいれているだけ。

async function webApi2Concurrent(a, b) {
  const results=await Promise.all([
    webApiAdd(a, b),
    webApiSub(a, b),
    webApiMult(a, b)]);
  const container = {};
  container.addResult = results[0];
  container.subResult = results[1];
  container.multResult = results[2];
  return container;
}

呼び出しは、以下のとおり

webApi2Concurrent(2, 3)
  .then((result) => {
    console.log(`すべての処理に成功しました 結果:${JSON.stringify(result)}`);
  })
  .catch((result) => {
    console.log(`途中で処理に失敗しました 結果:${JSON.stringify(result)}`);
  })

See the Pen Promiseサンプル64 by Tom Misawa (@riversun) on CodePen.

おまけ

Promiseつかってるとアロー関数「()=>{}」もよく出てくる。
記述量は少いが、そのぶんは人間の脳内補完(または思い出し)がたより

Promiseでのアロー関数

// 同じ意味。正解はLinter次第。

const p7 = new Promise((resolve, reject) => {
  resolve('value60');
});

const p7 = new Promise((resolve, reject) => resolve('value7'));

const p7 = new Promise(resolve => resolve('value7'));

thenメソッドでのアロー関数

// 以下は同じ意味
p7
  .then((value) => {
    return `${value}_edited`;
  })
  .then((value) => {
    console.log(value);
  });

p7
  .then((value) => `${value}_edited`) // returnは省略できる
  .then((value) => console.log(value)); //{}は省略できる

まとめ

  • Promise、async/await系の忘れやすい挙動をメモしました。
  • 何か忘れたときに、また追加します。

関連記事

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

phpでひとつ前のページにリダイレクトする方法

header('Location: '.$_SERVER['HTTP_REFERER']);
exit;

リダイレクトで処理が中断されるのでexit;を書いておきましょう。

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

PHPでひとつ前のページにリダイレクトする方法

header('Location: '.$_SERVER['HTTP_REFERER']);
exit;

リダイレクトで処理が中断されるのでexit;を書いておきましょう。

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

JQueryの基本操作

使用するための準備

bodyタグ内に下記を設置
ここからJQueryのリンクCDNを取得して、使用できるようにする。

// jqueryを使えるように取得
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

<script>
  $(function() {
    // 実施したい処理
  });
</script>

セレクタの指定方法

基本的な指定方法

// 要素の指定
$('a')
// idの指定
$('#id')
// classの指定
$('.class')

// 子要素の指定
$('#id > .class')
// 配下の全ての指定
$('#id .class')
// and条件
$('#id, .class')
// 隣の要素を指定
$('#id + .class')

フィルタを使用した指定方法

// indexは0からスタート

// index2のli要素を選択
$('#id > li:eq(2)')
// index2より大きいli要素を選択(greater thanの略)
$('#id > li:gt(2)')
// index2より小さいli要素を選択(less thanの略)
$('#id > li:lt(2)')
// indexが偶数のli要素を選択
$('#id > li:even')
// indexが奇数のli要素を選択
$('#id > li:odd')
// テキストに2が含まれているli要素を選択
$('#id > li:contains(2))')
// 最初のli要素を選択
$('#id > li:first')
// 最後のli要素を選択
$('#id > li:last')

メソッドを使用した指定方法

// 親要素を指定
$('#id').parent()
// 子要素を指定
$('#id').children()
// 次の要素を指定
$('#id').next()
// 前の要素を指定
$('#id').prev()
// 兄弟ノード全てを選択(自身は含まない)
$('#id').siblings()

セレクタメソッドを使用した指定方法

// 属性が一致する要素を取得
$('a[href="http://google.com"]')
// 属性が一致しない要素を取得
('a[href!="http://google.com"]')
// 属性に一部含まれている要素を取得
$('a[href*="google"]')
// 属性の冒頭が一致した要素を取得
$('a[href$="https://"]')
// 属性の末尾が一致した要素を取得
$('a[href$=".jp"]')

その他の指定方法

// a要素 且つ 属性が一致する要素を取得
($('a').attr('href', 'http://google.co.jp')

タグ操作

要素の追加/変更/作成

const 変数 =を外すと要素の変更になる

// 要素の作成(div要素の中にdivを追加)
const div = $('<div>').append($('<div>'));

// idを追加する
const div = $('<div>').attr('id', 'id名');

// classを追加する
const div = $('<div>').attr('class', 'class名');
const div = $('<div>').addClass('class名');

// htmlの追加
const div = $('<div>').html('追加したいHTML');

// textの追加
const div = $('<div>').text('追加したいテキスト');

// valueの追加
const div = $('<div>').val('追加したいvalue');

// プロパティの追加
const div = $('<option>').prop('selected', true);
const div = $('<input>').attr('type', 'checkbox').prop('checked', true);

// 下記は変更のみ
// 内容を削除
$('#id').empty();
// 要素自体を削除
$('#id').remove();

要素の追加

// 要素を作成
const div = $('<div>').text('text');

// li要素index2の前に追加
$('#id > li:eq(2)').before(div);
li.insertBefore($('#id > li:eq(2)'));
// li要素index2の後ろに追加
$('#id > li:eq(2)').after(div);
li.insertAfter($('#id > li:eq(2)'));
// 子要素の先頭に追加(複数ある場合は全てに追加される)
$('.class > li:eq(2)').after(div);
// 子要素の末尾に追加(複数ある場合は全てに追加される)
$('.class > li:eq(2)').append(div);

イベント操作

// クリックイベント
$('#id').click(() => alert('hi!'));
// 続けて書くことで複数イベントの設定が可能
// アロー関数を使用する場合
$('#box')
  .mouseover(e => {
    $(e.target).css('background', 'green');
  })
  .mouseout(e => {
    $(e.target).css('background', 'red');
  })
  .mousemove(e => {
    $(e.target).text(e.pageX);
  });

// functionを使用する場合
$('#box')
  .mouseover(function() {
    $(this).css('background', 'green');
  })
  .mouseout(function() {
    $(this).css('background', 'red');
  })
  .mousemove(function(e) {
    $(this).text(e.pageX);
  });

イベントの種類はこちらを参照

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

JQueryのチートシート

使用するための準備

bodyタグ内に下記を設置
ここからJQueryのリンクCDNを取得して、使用できるようにする。

// jqueryを使えるように取得
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

<script>
  $(function() {
    // 実施したい処理
  });
</script>

セレクタの指定方法

基本的な指定方法

// 要素の指定
$('a')
// idの指定
$('#id')
// classの指定
$('.class')

// 子要素の指定
$('#id > .class')
// 配下の全ての指定
$('#id .class')
// and条件
$('#id, .class')
// 隣の要素を指定
$('#id + .class')

フィルタを使用した指定方法

// indexは0からスタート

// index2のli要素を選択
$('#id > li:eq(2)')
// index2より大きいli要素を選択(greater thanの略)
$('#id > li:gt(2)')
// index2より小さいli要素を選択(less thanの略)
$('#id > li:lt(2)')
// indexが偶数のli要素を選択
$('#id > li:even')
// indexが奇数のli要素を選択
$('#id > li:odd')
// テキストに2が含まれているli要素を選択
$('#id > li:contains(2))')
// 最初のli要素を選択
$('#id > li:first')
// 最後のli要素を選択
$('#id > li:last')

メソッドを使用した指定方法

// 親要素を指定
$('#id').parent()
// 子要素を指定
$('#id').children()
// 次の要素を指定
$('#id').next()
// 前の要素を指定
$('#id').prev()
// 兄弟ノード全てを選択(自身は含まない)
$('#id').siblings()

セレクタメソッドを使用した指定方法

// 属性が一致する要素を取得
$('a[href="http://google.com"]')
// 属性が一致しない要素を取得
('a[href!="http://google.com"]')
// 属性に一部含まれている要素を取得
$('a[href*="google"]')
// 属性の冒頭が一致した要素を取得
$('a[href$="https://"]')
// 属性の末尾が一致した要素を取得
$('a[href$=".jp"]')

その他の指定方法

// a要素 且つ 属性が一致する要素を取得
($('a').attr('href', 'http://google.co.jp')

タグ操作

要素の追加/変更/作成

const 変数 =を外すと要素の変更になる

// 要素の作成(div要素の中にdivを追加)
const div = $('<div>').append($('<div>'));

// idを追加する
const div = $('<div>').attr('id', 'id名');

// classを追加する
const div = $('<div>').attr('class', 'class名');
const div = $('<div>').addClass('class名');

// htmlの追加
const div = $('<div>').html('追加したいHTML');

// textの追加
const div = $('<div>').text('追加したいテキスト');

// valueの追加
const div = $('<div>').val('追加したいvalue');

// プロパティの追加
const div = $('<option>').prop('selected', true);
const div = $('<input>').attr('type', 'checkbox').prop('checked', true);

// 下記は変更のみ
// 内容を削除
$('#id').empty();
// 要素自体を削除
$('#id').remove();

要素の追加

// 要素を作成
const div = $('<div>').text('text');

// li要素index2の前に追加
$('#id > li:eq(2)').before(div);
li.insertBefore($('#id > li:eq(2)'));
// li要素index2の後ろに追加
$('#id > li:eq(2)').after(div);
li.insertAfter($('#id > li:eq(2)'));
// 子要素の先頭に追加(複数ある場合は全てに追加される)
$('.class > li:eq(2)').after(div);
// 子要素の末尾に追加(複数ある場合は全てに追加される)
$('.class > li:eq(2)').append(div);

イベント操作

// クリックイベント
$('#id').click(() => alert('hi!'));
// 続けて書くことで複数イベントの設定が可能
// アロー関数を使用する場合
$('#box')
  .mouseover(e => {
    $(e.target).css('background', 'green');
  })
  .mouseout(e => {
    $(e.target).css('background', 'red');
  })
  .mousemove(e => {
    $(e.target).text(e.pageX);
  });

// functionを使用する場合
$('#box')
  .mouseover(function() {
    $(this).css('background', 'green');
  })
  .mouseout(function() {
    $(this).css('background', 'red');
  })
  .mousemove(function(e) {
    $(this).text(e.pageX);
  });

イベントの種類はこちらを参照

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

onclickを用いて複数の関数を呼び出す

Onclickで複数の関数を記述する

初心者プログラマーの備忘録です。

記法1:関数をセミコロン(;)で区切る

input type = "XXX" value = "a" onclick = "YYY;ZZZ"
このように、関数をセミコロンで区切って記述した場合、YYY → ZZZの順に関数を実行。
仮に YYY = true, ZZZ = false であっても、XXXが実行される。
YYY = false, ZZZ = true の場合は、YYYの結果ではじかれるためXXXは実行されない。

記法2:関数をカンマ(,)で区切る

input type = "XXX" value = "a" onclick = "YYY, ZZZ"
基本的にカンマで関数を区切ることはないが、上記のように記述した場合
カンマの右側の関数(ZZZ)の結果が true であれば、YYYの結果に依らずXXXが実行される。

記法3:関数を「&&」で区切る

input type = "XXX" value = "a" onclick = "YYY && ZZZ"
このように記述した場合、関数 YYY および ZZZ の結果がともに true であれば、XXXが実行される。

おまけ

まだ試していないが、仮に
input type = "XXX" value = "a" onclick = "YYY || ZZZ"
とすれば、YYYあるいはZZZのどちらかの結果が true であれば、XXXが実行されると考えられる。

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

乱出するCTAリンクの更新にイライラしているLP更新者の方への提案

LPあるある

1ページ中に同じお問い合わせエリア(CTAボタンやら電話番号やら)が複数回でてくる

例えばこんな感じ

LP/index.html:before
<body>
  <section id="sec1">
    <div><p>内容1</p></div>
  </section>
  <div class="cta">
    <div class="tel">
      <a href="tel:0123456789">0123-45-6789</a>
    </div>
    <div class="link">
      <a href="//google.com"><div class="button">お問い合わせはこちら</div></a>
    </div>
  </div>
  <section id="sec2">
    <div><p>内容2</p></div>
  </section>
  <div class="cta">
    <div class="tel">
      <a href="tel:0123456789">0123-45-6789</a>
    </div>
    <div class="link">
      <a href="//google.com"><div class="button">お問い合わせはこちら</div></a>
    </div>
  </div>
  <section id="sec3">
    <div><p>内容3</p></div>
  </section>
  <div class="cta">
    <div class="tel">
      <a href="tel:0123456789">0123-45-6789</a>
    </div>
    <div class="link">
      <a href="//google.com"><div class="button">お問い合わせはこちら</div></a>
    </div>
  </div>
</body>

「どんだけ、お問い合わせエリアで場所とってんねん:rage:」って気分になる
視認性も良くない

さらに「計測用のクリックイベントを追加して」「電話番号が変わりました」なんて修正依頼もあったり...

いちいち検索して手作業もたいへんだし
エディタで一括置換もできるけど、本当にちゃんと出来てるかも不安だし...

なんとか1箇所の変更で済むようにしたい(一括編集したい)

JSに頼ろう

こんな感じにして解決 (JavaScript で HTML を書き換え)

LP/index.html:after
<body>
  <section id="sec1">
    <div><p>内容1</p></div>
  </section>
  <div class="cta"></div> <!-- ← ココにお問い合わせ表示 -->
  <section id="sec2">
    <div><p>内容2</p></div>
  </section>
  <div class="cta"></div> <!-- ← ココにお問い合わせ表示 -->
  <section id="sec3">
    <div><p>内容3</p></div>
  </section>
  <div class="cta"></div> <!-- ← ココにお問い合わせ表示 -->
</body>

<!-- 表示させるお問い合わせのコード -->
<script id="cta_code" type="text/html">
  // ココから
  <div class="tel">
    <a href="tel:0123456789">0123-45-6789</a>
  </div>
  <div class="link">
    <a href="//google.com"><div class="button">お問い合わせはこちら</div></a>
  </div>
  // ココまで
</script>
<!-- END 表示させるお問い合わせのコード -->

<!-- .cta にコードを入れるスクリプト -->
<script type="text/javascript">
  var code = document.getElementById("cta_code").innerHTML; // 上記の「表示させるお問い合わせのコード」がつまった #cta_code を格納
  var cta = document.querySelectorAll(".cta"); // ソース中から .cta を見つけて格納
  var arr = Array.prototype.slice.call(cta); // forEach が使えるように配列に
  arr.forEach(function (e) {
    e.innerHTML = code; // 入れ替え実施
  });
</script>
<!-- END .cta にコードを入れるスクリプト -->

解説

1)お問い合わせエリアに

お問い合わせを入れたい部分に<div class="cta"></div>を入れる

2)表示させるお問い合わせのコード

// ココから // ココまで の間に書き換えたいHTMLを置く

ポイントは<script id="cta_code" type="text/html">の部分
type="text/html"で、表には表示させないように。

<div id="cta_code" style="display:none">でも動くと思うけど、どちらでも)

3).cta にコードを入れるスクリプト

  • Array.forEach()を使いたい
  • IE11でも動くようにしたい

ということからArray.prototype.slice.call()を使うことに

参考サイト:IEで NodeList を forEach するとエラーになる問題の対処方

解決 :relaxed:

これで、今後に更新があっても、1箇所だけの修正で対応できます

image.png

↑ レンダリングされたあとは冒頭のLP/index.html:beforeと同じソースになってる

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

Vue.jsでAPIを返してみる

概要

LINEで写真を撮るBotがGyazoに写真を溜めていくので、お友達ではない人にも見せられるような簡易なWebサービスをVue.jsで作成してVercelとfreenomで独自ドメインのサイトを作るというものです。

できたもの

環境

macOS Catalina 
Visual Studio Code 1.45.1
Node.js: 12.8.1

構成

app.js
public
 - index.html
 - style.css

コード

    <div id="app" class="waku">
    <h1>3yakaさん家のにゃんこはなにしてる?</h1>
    <p>猫カメラが撮った写真の最新10件がランダムに表示されるよ</p>
    <button id="testbutton" v-on:click="getData()"></button>
       <p class="mes"> {{ message }}
        <img class="imgsiz" v-bind:src="src" /><!-- データバインディングの場合はカッコをくくらなくて呼び出せます -->
      </p>
      </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue!',
                src:'https://i.gyazo.com/9360a06096a20ab93a79a4793f7670dd.jpg' // 画像のメッセージ初期値
            },

            methods: {
               getData: async function(){
                  let e;
                    const response = await axios.get('/api');
                    for (var i = 0 ; i < 9 ; i++){
                        e = Math.floor(Math.random () * 10);
                    }
                    const date = new Date(response.data[e].created_at);
                    let datef = date.toLocaleString();

                    this.message = `この写真を撮った時間は${datef}だよ`;
                    this.src = response.data[e].url; // 取得した画像差し替え
                    console.log(response.data[e].url);
                },
            },
            mounted: function(){
                this.getData();
            }
        })
    </script>
node.js
var express = require('express');
var app = express();

const Gyazo = require('gyazo-api');
const gyazoclient = new Gyazo('***');

// public というフォルダに入れられた静的ファイル(HTMLファイル・CSSファイル・ブラウザ上のJavaScriptファイル)はそのまま表示
app.use(express.static(__dirname + '/public'));

app.get('https://po3-8-vue-d49zw7rgl.now.sh/api', async function(req, res) {
  let response;
  try {
    response = await gyazoclient.list();
  } catch (error) {
    console.error(error);
  }
  //結果をJSONに割り当てる
  res.json(response.data);
});

app.listen(8080);
console.log('server start! (po-03-08-web-vue)');
//app.listen(process.env.PORT || 8080);

上記でローカルのサーバーだと動くけどVercellにDeployするとapiが読めないと返ってくる。
vercelから出るIntended Nameserversの番号が4日くらい前にProtoOut Studioでやったときとだいぶ違う感じになってた。

a.zeit-world.co.uk
c.zeit-world.org
e.zeit-world.com
f.zeit-world.net

これが、こんな感じに

ns1.vercel-dns.com
ns2.vercel-dns.com

きっと色々変更中なんだろうな、、、now.shからvercelってだいぶ違う感じだしな。。。
きっとこれが理由ではないけど、Deployしたらダメになるって辛い。

参考サイト

爆速!Vercelとfreenomで独自ドメインのサイトを無料で作成する - Qiita

感想

久々にコンソールログが真っ赤なサイトを見た。

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

Contact Form 7とJavaScriptで特殊なバリデーションをする方法

やりたいこと

contact-form-7で、特殊なバリデーションをして欲しいと言われました。

例えば、言われたこととしては ↓

  • 半角英数字、大文字でかつ10文字の規制をお願いします。
  • これは極端な話、aと入力したら自動でAとなる。と思っていいですよね。

これを実現したい場合、下記のように実装すると出来るようになります。

contents-form7

フォームでは、idを用いて操作します。

[text* coupon-code-2-1 class:secure-key id:Name01]

functions.phpに追加

固定ページで、jQueryを使うために、カスタムJSを使えるようにします。functions.phpの最後にこれを追加してください。

//Custom JS Widget
add_action('admin_menu', 'custom_js_hooks');
add_action('save_post', 'save_custom_js');
add_action('wp_head','insert_custom_js');
function custom_js_hooks() {
    add_meta_box('custom_js', 'Custom JS', 'custom_js_input', 'post', 'normal', 'high');
    add_meta_box('custom_js', 'Custom JS', 'custom_js_input', 'page', 'normal', 'high');
}
function custom_js_input() {
    global $post;
    echo '<input type="hidden" name="custom_js_noncename" id="custom_js_noncename" value="'.wp_create_nonce('custom-js').'" />';
    echo '<textarea name="custom_js" id="custom_js" rows="5" cols="30" style="width:100%;">'.get_post_meta($post->ID,'_custom_js',true).'</textarea>';
}
function save_custom_js($post_id) {
    if (!wp_verify_nonce($_POST['custom_js_noncename'], 'custom-js')) return $post_id;
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return $post_id;
    $custom_js = $_POST['custom_js'];
    update_post_meta($post_id, '_custom_js', $custom_js);
}
function insert_custom_js() {
    if (is_page() || is_single()) {
        if (have_posts()) : while (have_posts()) : the_post();
            echo '<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script><script type="text/javascript">'.get_post_meta(get_the_ID(), '_custom_js', true).'</script>';
        endwhile; endif;
        rewind_posts();
    }
}

カスタムjs

で、最後に、カスタムjsに下記をコピペしてください。

/**
 * 半角英数字かチェック
 * @return true:半角英数字である(もしくは対象文字列がない), false:半角英数字でない
 */
function isHalfWidthAlphanumeric(value) {
  if ( value == null )
    return;
  if( value.match( /[^A-Za-z0-9\s.-]+/ ) ) {
    alert("半角英文字で入力してください");
    return false;
   }
   return true;
}

$(function(){$("#Name01").blur(function(){ if ( !isHalfWidthAlphanumeric($(this).val()) ) { $(this).focus(); } });});
$(function(){$("#Name01").blur(function(){document.getElementById("Name01").value=$(this).val().toUpperCase();});});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PC版ではロゴと検索窓が出て、スマートフォン版では切り替え表示したいなと思ったら・・・

タイトルの通りですが、PC版では検索窓とサイトのロゴを出したいけど、スマホ版ではどちらも出す余裕はありませんが、ハンバーガーデザインに分けることもしたくなかったので作ってみました

 コードとデモ

デモ

https://jsfiddle.net/mqgc207L/

コード

CSS部

    @media screen and (max-width: 768px) {
      .SearchWindow__view {
          display: none;
      }
    }

サンプルではBootstrap4を使っていることもあり、今回は768pxを区分けしましたが、検索窓に張り付けるクラスをとりあえずスマホとPC版を切り替えたいブレークポイントで非表示と表示に切り替えるようにします。

JS部

const SMP_SEARCH  = document.getElementById("SearchWindow");
  const SMP_LOGO    = document.getElementById("LogoWindow");
  const SMP_LINK    = document.getElementById("header__btm--icon");
  const CLS_SEARCH  = 'fas fa-search';
  const CLS_CROSS   = 'fas fa-times';
  const WINDOWS_MD  = 768;
  let flgChenge     = true;

  window.addEventListener('resize', function(e) {
    //現在のサイズを取得
    let nowsize = window.innerWidth;
    if( (nowsize >= WINDOWS_MD)  ){
      //基準以上ならリセット
      display_reset();
      SMP_SEARCH.style.display ="";
    }
  });

  function clickBtn1(){
    if(flgChenge){
      // 検索窓表示
      SMP_LINK.className = CLS_CROSS;
      $(SMP_LOGO).hide();
      $(SMP_SEARCH).show();
      $(SMP_SEARCH).removeClass('SearchWindow__view');
      flgChenge = false;
    }else{
      // 検索窓非表示
      display_reset();
    }
  } 

  function display_reset(){
    SMP_LINK.className = CLS_SEARCH;
    $(SMP_LOGO).show();
    $(SMP_SEARCH).hide();
    $(SMP_SEARCH).addClass('SearchWindow__view');
    flgChenge = true;
  }

起動時にフラグ用引数を用意し、ボタンを操作する度にclickBtn1関数を呼び出して表示・商事を繰り返しますが、PC版ではリセットする必要があるので、 window.addEventListener('resize', function(e) {});でリサイズ時のサイズを算定。所定以上ならリセットを行います。なお、display: block;が残っていると拡大→縮小を繰り返すと最初は隠しておくべき検索窓が表示されるので、SMP_SEARCH.style.display ="";で消しておきます。

<header id="header">
  <div class="row" id="header__top">
    <div id="LogoWindow" class="col-md-4 col-12">
      <h1><a href="#">LOGO</a></h1>
    </div>
    <div id = "SearchWindow" class="col-md-8 col-12 SearchWindow__view">
      <form id="header__search" action="/" method="get">
        <input class="validate" name="search" type="text" autocomplete="off" placeholder="キーワードを入力" >
        <button type="submit">検索</button>
      </form>
    </div>
  </div>

  <!--ボタン-->
  <div id="header__btm">
    <div class="d-block d-md-none">
      <a href="#" class="text-grey darken-4" onclick="clickBtn1()">
      <i id="header__btm--icon" class="fas fa-search" style="font-size:2.0rem;"></i></a>
    </div>
  </div>

HTMLの本文です。画像サイズによって表示するかどうかを区分けするのは難しく車輪の差発明になるので、ここではBootstrapの表示ユーティリティを多用しています。グリッドシステムのみのCSSが入ったBootsrap Gridにもこのユーティリティは附属しているので活用して頂ければと思います。

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

P5.js 日本語リファレンス(storeItem)

このページでは「P5.js 日本語リファレンス」 の storeItem関数を説明します。

storeItem()

説明文

キー名でローカルストレージに値を格納します。キーは変数の名前にすることができますが、そうである必要はありません。保存されたアイテムを取得するには getItem() を使用してください。

ローカルストレージとは、ブラウザに保存され、ブラウジングセッションとページの再読み込みの間も保持されているものです。

パスワードや個人情報などの機密データはローカルストレージに保存しないでください。

構文

storeItem(key, value)

パラメタ

  • key

    String:キー名

  • value

    String | Number | Object | Boolean | p5.Color | p5.Vector:保存する値

// キャンバスの中心にキー入力した文字を表示します
// ページをリロードすると最後に入力した文字を再表示します
// (最後に入力した文字はローカルストレージに保存してあるため)
let myText;

function setup()  {
  createCanvas(100, 100);
  myText = getItem('val');

  // val にはまだ一度も保存していないので NULL になっている
  if (myText === null) {
    myText = '';
  }
 }

function draw()  {
   textSize(40);
   background(255);
   text(myText, width / 2, height / 2);
 }

function keyPressed()  {
   myText = key; // 入力したキー(文字)の内容を myText に代入
   storeItem('val', myText); //  myText の値をローカルストレージに保存
 }

実行結果

https://editor.p5js.org/bit0101/sketches/OZEG-8F3x

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(removeItem)

このページでは「P5.js 日本語リファレンス」 の removeItem関数を説明します。

removeItem()

説明文

storeItem() で保存したローカルストレージアイテムを削除します。

構文

removeItem(key)

パラメタ

  • key
    String:キー名

function setup() {
  let myNum = 10;
  let myBool = false;
  storeItem('key1', myNum); // key1 に 10 を保存します  
  storeItem('key2', myBool); // key2 に false を保存します
  print(getItem('key1'));
  print(getItem('key2'));
  removeItem('key1') ;    // key1 を削除します
  removeItem('key2') ;    // key2 を削除します
  print(getItem('key1')); // 再度、key1 を取得します -> NULL を表示
  print(getItem('key2')); // 再度、key2 を取得します -> NULL を表示
}

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(getItem)

このページでは「P5.js 日本語リファレンス」 の getItem 関数を説明します。

getItem()

説明文

storeItem() を使用してローカルストレージに保存したアイテムの値を取得します。

ローカルストレージとは、ブラウザに保存され、ブラウジングセッションとページの再読み込みの間も保持されているものです。

構文

getItem(key)

パラメタ

  • key
    String:ローカルストレージに保存するために使用した名前

戻り値

Number | Object | String | Boolean | p5.Color | p5.Vector:保存されたアイテムの値

例1

// マウスをクリックすると背景色が変わります
// 色を変更した後、ページをリロードしても色は変わりません
// (変更した背景色はローカルストレージに保存してあるため)
let myColor;

function setup()  {
   createCanvas(100, 100);

   // key "myColor" にはまだ一度も保存していないので NULL を取得
   myColor = getItem('myColor'); 
 }

function draw()  {
   if (myColor !== null) {
     background(myColor);
   }
 }

// マウスをクリックすると呼ばれる
function mousePressed()  {
   myColor = color(random(255), random(255), random(255));

  //  random() で求めた color を key "myColor" に保存
   storeItem('myColor', myColor);
 }

実行結果

https://editor.p5js.org/bit0101/sketches/ksjXkEkkn

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

P5.js 日本語リファレンス(clearStorage)

このページでは「P5.js 日本語リファレンス」 の clearStorage関数を説明します。

clearStorage()

説明文

storeItem() で保存したすべてのローカルストレージアイテムをクリアします。

構文

clearStorage()

function setup() {
  let myNum = 10;
  let myBool = false;
  storeItem('key1', myNum); // key1 に 10 を保存します  
  storeItem('key2', myBool); // key2 に false を保存します
  print(getItem('key1'));
  print(getItem('key2'));
  clearStorage() ;        // クリアします 
  print(getItem('key1')); // 再度、key1 を取得します -> NULL を表示
  print(getItem('key2')); // 再度、key2 を取得します -> NULL を表示
}

著作権

p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.

ライセンス

Creative Commons(CC BY-NC-SA 4.0) に従います。

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

Deno 上の JS で OAuth 認証して Twitter API を使用する

Deno が面白そうだったので、試しに Twitter API を使ってみました。

本当は TypeScript を実行できますが、ここでは JavaScript で記述しています.

1. コード

以前自分が書いたコードを少し改良して、さらに Deno 向けに変更を加えました。

参考「ブラウザ上のピュア JavaScript で OAuth 認証して Twitter API を使う

main.js
import Twitter from './twitter.js';

// 
const twitter = new Twitter({
    'api_key'       : '...',
    'api_secret_key': '...',
    'access_token'       : '...',
    'access_token_secret': '...'
});

// 
const json = await twitter.get('friends/list', {
    'screen_name': 'TwitterJP'
});

console.log(json);
twitter.js
import hmacSha1 from './hmac-sha1.js';
import * as base64 from "https://denopkg.com/chiefbiiko/base64/mod.ts";
import * as hex from "https://deno.land/std/encoding/hex.ts";

export default class Twitter {

    #options

    constructor(options) {
        this.#options = options;
    }

    get(path, paramsObj) {
        return this.#request('GET', path, paramsObj);
    }

    async #request(method, path, paramsObj) {

        const url = this.#getRestUrl(path);
        const params = this.#objToArray(paramsObj);

        // 認証情報
        const authHeader = await this.#getAuthHeader(method, url, params);

        const headers = {'Authorization': authHeader};

        // 通信
        const query = this.#percentEncodeParams(params).map(pair => pair.key + '=' + pair.value).join('&');

        const response = await fetch((! params || method === 'POST' ? url : url + '?' + query), {method, headers});

        return response.json();

    }

    async #getAuthHeader(method, url, params) {

        // パラメータ準備
        const oauthParamsObj = {
            'oauth_consumer_key'    : this.#options['api_key'],
            'oauth_nonce'           : this.#getNonce(),
            'oauth_signature_method': 'HMAC-SHA1',
            'oauth_timestamp'       : this.#getTimestamp(),
            'oauth_token'           : this.#options['access_token'],
            'oauth_version'         : '1.0'
        };

        const oauthParams = this.#objToArray(oauthParamsObj);

        const allParams = this.#percentEncodeParams([...oauthParams, ...params]);

        this.#ksort(allParams);

        // シグネチャ作成
        const signature = await this.#getSignature(method, url, allParams);

        // 認証情報
        return 'OAuth ' + this.#percentEncodeParams([...oauthParams, {key: 'oauth_signature', value: signature}]).map(pair => pair.key + '="' + pair.value + '"').join(', ');

    }

    #getSignature(method, url, allParams) {

        const allQuery = allParams.map(pair => pair.key + '=' + pair.value).join('&');

        // シグネチャベース・キー文字列
        const signatureBaseString = [
            method.toUpperCase(),
            this.#percentEncode(url),
            this.#percentEncode(allQuery)
        ].join('&');

        const signatureKeyString = [
            this.#options['api_secret_key'],
            this.#options['access_token_secret']
        ].map(secret => this.#percentEncode(secret)).join('&');

        // シグネチャ計算
        const signatureUint8Array = hmacSha1(signatureBaseString, signatureKeyString);

        return base64.fromUint8Array(signatureUint8Array);

    }

    #getRestUrl(path) {
        return 'https://api.twitter.com/1.1/' + path + '.json';
    }

    /**
     * RFC3986 仕様の encodeURIComponent
     */
    #percentEncode(str) {
        return encodeURIComponent(str).replace(/[!'()*]/g, char => '%' + char.charCodeAt().toString(16));
    }

    #percentEncodeParams(params) {

        return params.map(pair => {
            const key   = this.#percentEncode(pair.key);
            const value = this.#percentEncode(pair.value);
            return {key, value};
        });

    }

    #ksort(params) {

        return params.sort((a, b) => {
            const keyA = a.key.toUpperCase();
            const keyB = b.key.toUpperCase();
            if ( keyA < keyB ) return -1;
            if ( keyA > keyB ) return 1;
            return 0;
        });

    }

    #getNonce() {
        const array = new Uint8Array(32);
        window.crypto.getRandomValues(array);
        return hex.encodeToString(array);
    }

    #getTimestamp() {
        return Math.floor(Date.now() / 1000);
    }

    #objToArray(object) {
        return Object.entries(object).map(([key, value]) => ({key, value}));
    }

}
hmac-sha1.js
import hmac from 'https://raw.githubusercontent.com/denolibs/hmac/master/lib/mod.ts';
import { Hash, encode } from 'https://deno.land/x/checksum@1.2.0/mod.ts';

const hashSha1 = new Hash('sha1');
const hash = bytes => hashSha1.digest(bytes).data;

const hmacSha1 = (data, key) => hmac(encode(data), encode(key), hash, 64, 20);

export default hmacSha1;

2. Deno の準備

自分は Windows Subsystem for Linux を使用しているので、Linux の方法でインストールしました。

ターミナル
curl -fsSL https://deno.land/x/install/install.sh | sh

環境変数 PATH に deno のインストールディレクトリを追加します。

~/.bash_profile 等に追加
export DENO_INSTALL="/home/<ユーザー名>/.deno"
export PATH="$DENO_INSTALL/bin:$PATH"

参考「Installation - The Deno Manual

3. Deno での実行

Deno で通信をしたい場合には、パーミッションのオプション --allow-net を使用します。

ターミナル
deno run --allow-net main.js

よりセキュアにするために、ホスト名で制限をかけることも可能です。

参考「Permissions - The Deno Manual

4. コードの説明

4.1. Deno での HMAC-SHA1 の計算

Deno では Crypto は実装されていますが SubtleCrypto はありませんので、他の方法で計算します。

Deno 向けの HMAC を計算するライブラリと SHA1 を計算するライブラリがそれぞれあるので、組み合わせたらできました。

参考「GitHub - denolibs/hmac: A faster HMAC module for Deno using TypeScript and WebAssembly
参考「GitHub - manyuanrong/deno-checksum: Sha1、MD5 algorithms for Deno

SHA1 のブロック長は 512 ビット ( = 64 バイト) 、出力長は 160 ビット ( = 20 バイト) です。

参考「SHAシリーズの比較 - SHA-1 - Wikipedia
参考「US Secure Hash Algorithm 1 (SHA1)

4.2. OAuth 認証とシグネチャの計算

できれば分かりやすく説明したいのですが、ここでは Twitter のドキュメントのリンクを張っておきます。

参考「Authorizing a request — Twitter Developers
参考「Creating a signature — Twitter Developers

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

Vue Composition API×vue-konva+自作サービスで解説するcanvas操作

初めに

 本記事では、canvas上でテキストを操作するサービスをリリースするにあたり得られた、以下の知見を紹介します

  • canvasを操作するライブラリ、konva(vue-konva)について
  • Vue Composition APIについて

 Vue composition APIとは、次期メジャーバージョンのVueにて追加予定のAPI群です。
 このAPI群を使うことで、これまでのVueの書き方ががらっと変わります(従来の書き方もできます)。
 この機能は現在(2020年5月時点)でも、Vueにパッケージを追加することで試用できます。本記事では、このAPIのポイントについても紹介します。

本記事にて取り上げる内容

  • 自作サービスの紹介
  • Vue(Vue2.0+Vue Composition API)
  • vue-konva

前提条件

 本記事は、以下の読者を想定しています。

  • canvasでの自在な描画技術や、vueに興味がある人
  • vueの入門程度の知識があるとなおよい

自作サービスの紹介

 今回、Netlify+Nuxt+Veautifyという構成でWebサービスを作りました。

 筆の海 https://seaofbrush.netlify.app/

 どんなサービスかは、下の動画を見たほうが早いです

intro_lossy.gif

 こんなふうに、テキストボックスに入力した文章を筆として扱い、キャンバス上に文を描く(?)ことができます。
「とにかく字をぐりぐり描きたい」というのが一番の動機のため、あまり凝った機能は設けていませんが、フォントや文字サイズを変更したり、アンドゥやダウンロードをしたりといった字描き(?)のための最低限の機能は備えています。

konva、vue-konvaとは

 html5のcanvas上で図形を便利に操作するため、konvaというライブラリがあります。
 vue-konvaはそれをvueのコンポーネントとして扱えるようにしたものです。
 導入方法については、既に素晴らしい記事がありましたのでそちらを参照してください。

konvaによる図形の描画

 konvaの基本的な使い方ですが、まずkonvaにはstage・layer・その他オブジェクト(基本図形やテキストなど)という三種類のオブジェクトがあり、

  1. stageにlayerを登録
  2. layerにその他オブジェクトを登録
  3. layerを描画

……という3ステップで図形を描画するようになっています。

実行例は以下の通り
https://konvajs.org/docs/overview.html を元に少し改変)

円を描画するサンプルコード
// ステージを作成
let stage = new Konva.Stage({
  container: 'container',   // 描画するdivのidを指定
  width: 500,
  height: 500
});

// レイヤーを新規作成
let layer = new Konva.Layer();

// 図形(ここでは円)を作成
let circle = new Konva.Circle({
  x: stage.width() / 2,
  y: stage.height() / 2,
  radius: 70,
  fill: 'red',
  stroke: 'black',
  strokeWidth: 4
});

// レイヤーに図形を追加
layer.add(circle);

// ステージにレイヤーを追加
stage.add(layer);

// レイヤーを描画
layer.draw();

vue-konvaによる図形の描画

 続いてvue-konvaによる図形の描画方法です
 上記のkonvaの例をベースとしつつ、Vue Composition APIの解説も兼ねるため、vueのコードとしてやや無理がある例になっていますがご了承ください。

単一ファイル(.vue)本体サンプルコード
<template>
  <div>
    <client-only placeholder="Loading...">
      <v-stage
        ref="stage"
        :config="myState.stageConfig"
        @mouseenter.native="onMouseEnter"
        @mouseleave.native="onMouseLeave"
      >
        <v-layer ref="layer">
          <v-circle
            ref="circle"
            :config="myState.circleConfig"
          />
        </v-layer>
      </v-stage>
    </client-only>
    今の円のサイズは{{myState.text}}です
  </div>
</template>

<script>
  // composition-apiで使用する一連のapiをインポート
  import {
    ref,
    reactive,
    computed,
    watch,
  } from "@vue/composition-api";

  export default {
    name: "test",
    setup(_, context) {
      //ref
      const stage = ref(null);
      const layer = ref(null);
      const circle = ref(null);
      const size = ref(250)
      //reactive
      const myState = reactive({
        stageConfig: computed(() => {
          return {
            width: size.value,
            height: size.value,
          }
        }),
        circleConfig: computed(() => {
          return {
            x: size.value / 2,
            y: size.value / 2,
            radius: size.value / 2,
            fill: 'red',
            stroke: 'black',
            strokeWidth: 4
          }
        }),
        text: "",
      })

      //function
      function onMouseEnter(event) {
        size.value = 500;
      };

      function onMouseLeave() {
        size.value = 250;
      };

      watch(() => size.value, (val, prevVal) => {
        myState.text = String(val);
      })

      return {
        //const
        stage,
        layer,
        circle,
        size,
        myState,
        //func
        onMouseEnter,
        onMouseLeave,
      };
    }
  }
</script>

 上記のコードを使いwebページを作成すると、以下のようにマウスカーソルを当てると巨大化する円が描画されます。

sample_circle_lossy.gif

 上記の例を元に、ポイントをいくつか紹介します。

vue-konvaにおけるポイント

  • new Konva.hogehogeオブジェクトが<v-hogehoge>というコンポーネントに置き換わっている
  • Konvaオブジェクトの初期値は:configディレクティブで渡す
  • refを指定する
     これはどこのドキュメントやサイトにも載っていないのですが、vue-konvaのコンポーネントはとりあえずref="hogehoge"という形で、何かしらのコンポーネント名を指定しておくと良いです。

※例

        <v-layer ref="layer"></v-layer>

 理由については後述します。

Vue composition APIのポイント

ref()とreactive()

 Vueの特徴といえばリアクティブ、すなわち変数の変更が他の変数と動的に連動する点にあります。
 従来のAPIではdata()内に記述していたリアクティブな変数は、ref()かreactive()関数を使うように変更されました。

//従来の書き方
data() {
  return {
    size: 250
  }
}

//Composition APIの書き方
const size=ref(250);
return {
  size
}
//または
const state = reactive({
  size:250
  })
return {
  state
}

 ref()とreactive()、どちらの関数で書いても、もう一方の書き方で表現しなおすことができます。
 どちらの表現も一長一短があり、特性やユースケースで使い分ける必要があるかと思います。

  • ref()のメリット
    • 宣言が楽
  • ref()のデメリット
    • 値を参照するときや代入するとき、関数名そのままではなく「関数名.value」でアクセスする必要がある

※例

const size=ref(500)
console.log(size.value)//=>"500"

 この、使うときに.valueを付けなければならないという特性は何かと忘れがちで、バグの原因にもなりがちです。

  • reactiveのメリット
    • 関連する変数を一つのstate関数名にまとめられ、コードが分かりやすくなる
       上記vue-konvaのコードでは、一連のconfig設定をmyStateという関数名でまとめている例がそれにあたります。
       アクセスする際は「ステート名.変数名」という形式でアクセスします。こちらは.valueを付ける必要はありません。

※例

const myState = reactive({
  size:500
  })
console.log(myState.size)//=>"500"
  • reactiveのデメリット
    • 宣言が若干手間

function

 従来methodで指定していた各メソッドは、functionという形でそのまま記述するようになりました。

//従来の書き方
methods: {
  onMouseEnter(event) {
    //(中略)
  };
}
//Composition APIの書き方
function onMouseEnter(event) {
  //(中略)
};

watch

 watchも若干書き方が変わりました。

//従来の書き方
watch: {
  size(value) {
    //(中略)
  }
},

//Composition APIの書き方
//(1)引数に値を指定する
watch(() => size.value, (val, prevVal) => {
  //(中略)
})
//または
//(2)特に値を指定しない
watch(() => {
  if(size.value==500){
    //(中略)
  }
})

 なお、新しい書き方の場合、さらに(1)引数に値を指定する書き方と(2)しない書き方があるようです。
 指定する書き方のほうが、ウォッチする内容が少ないぶん速いはず(要確認)ですし、コードが分かりやすくなるため、引数に値を指定したほうが良いのではないかと思います。

return

 コンポーネント内で使用する変数や関数は、return内で指定しないと使えませんのでお気をつけください。

return{
  //constを指定
  size,
  myState,
  //functionを指定
  onMouseEnter,
}

ref()についてのtips

1. コンポーネントにアクセスする

 コンポーネント内のrefディレクティブと同名のref関数を用意することで、コンポーネントがマウントされた際にそのコンポーネントオブジェクトが自動的に代入されます。
 従来の$refに相当する使い方ですね。

<template>
   (中略)
   <v-layer ref="myLayer"/>
</template>

<script>
(中略)
const myLayer=ref(null);//←同じ名前にしておくと、<v-layer>の実体が後で勝手に入る
</script>

2. konvaのnodeオブジェクトを取得して図形を操作

 一度画面に表示した図形にアニメーションを付ける場合など、後から図形に何か操作する場合、操作対象をnodeオブジェクトとして指定する必要があります。
 nodeオブジェクトは、上記1.で取得したコンポーネントからgetNode()メソッドで取得します。

※例:円が移動するvue-konvaアニメーション(Tween)の場合

<template>
(中略)
        <v-circle ref="myCircle"/>
</template>

<script>
(中略)
const myCircle=ref(null);
(中略)
if(myCircle.value){
  //コンポーネントからnodeオブジェクトを取得
  const nodeObj=myCircle.value.getNode();
  //nodeオブジェクトを引数に、Konva.Tween()でアニメーションを設定
  let tweenObj = new Konva.Tween({
    node: nodeObj,
    duration: 1.0,
    x: myState.X
    y: myState.Y
    easing: Konva.Easings.BackEaseOut,
  });
  //アニメーションを実行
  tweenObj.play();
}
</script>

 「vue-konvaのコンポーネントはとりあえずrefを指定しておけ」と前述した理由はここにあります。
 konvaを使う動機として、単に図形を表示するだけでなく、何かしらの凝った操作やアニメーションを付けたいというケースが大半だと思います。そのため、refしてコンポーネント取得してgetNodeしてアニメーションを設定、というケースが非常に多いです。

3. ref()で配列を扱う

 ref()は配列も扱うことができます。配列内の各要素にアクセスするときは「関数名.value[index]」という形式です。

const refArray=ref([]);
refArray.value.push("A")
refArray.value.push("B")
refArray.value.push("C")
console.log(refArray.value[1])//=>"B"

(1~3の応用)複数の図形を自在に操る

 vueの場合、v-forディレクティブで複数のコンポーネントを一括して管理できるわけですが、
これと上記1~3を組み合わせると、複数の図形の一括したアニメーションが非常に容易になります。
 例を以下に示します。前述した円のアニメーションのコードを踏襲していますが、今度の例ではリアクティブにする必要がなくなったconfig設定は単なる定数や関数になっている点にのみご注意ください。

単一ファイル(.vue)本体
<template>
  <div>
    <client-only placeholder="Loading...">
      <v-stage
        ref="stage"
        :config="stageConfig"
        @mouseenter.native="onMouseEnter"
      >
        <v-layer ref="layer">
          <v-circle v-for="index in numberArray"
                    :config="circleConfig(index)"
                    ref="circle"/>
        </v-layer>
      </v-stage>

    </client-only>
  </div>
</template>

<script>
  import {
    ref,
    reactive,
    computed,
    watch,
  } from "@vue/composition-api";

  export default {
    name: "test",
    //data
    setup(_, context) {
      //ref
      const stage = ref(null);
      const layer = ref(null);
      const circle = ref(null);
      const stageConfig={
        width: 1000,
        height: 1000,
      }

      //0~99の連番を作成
      const numberArray = [...Array(100).keys()];

      function circleConfig(index) {
        return {
          x: 500,
          y: 500,
          radius: index + 25,
          fill: 'red',
          stroke: 'black',
          strokeWidth: 1
        }
      };

      function onMouseEnter(event) {
        for (let index of numberArray) {
          if(circle.value[index]){
            const nodeObj = circle.value[index].getNode();
            let tweenObj = new Konva.Tween({
              node: nodeObj,
              duration: 1.0,
              x: Math.random() * 1000,
              y: Math.random() * 1000,
              easing: Konva.Easings.BackEaseOut,
            });
            tweenObj.play();
          }
        }
        ;
      }
      return {
        //const
        stage,
        layer,
        circle,
        stageConfig,
        numberArray,
        //func
        circleConfig,
        onMouseEnter,
      };
    }
  }
</script>

 上記の例を実行すると……

sample_circle2_lossy.gif

 さまざまな円が、マウス操作に従って動き出すアニメーションができました!
 このアニメーション、描画を担っているコードは実質、<template>内の<v-circle>コンポーネントと<script>内のcircleConfig()とonMouseEnter()。合計でたったの二十行程度となります。
 "vue-konvaは使える"ということが分かってもらえましたでしょうか。

おわりに

 自作サービスの紹介から始まり、vue-konvaの紹介からVue composition APIのtipsまで、やや散漫な内容となってしまいました。
 しかしこれを機にvueやvue-konvaの魅力が伝わり、新たなサービス開発の一助となれば幸いです。

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

JS5 switch文 条件分岐に使う方法

switch文
これはifと同様に条件分岐を行う時に用いられる。

var item = ("手裏剣");

switch(item) {
case "手裏剣":
console.log("忍者が投げて使う");
break;
case "おにぎり":
console.log("忍者が食います);
break;
default:
console.log("アイテムはありません");
break;
}

breka; はswitch文を抜ける意味がある。

例えば、itemが"手裏剣"だった場合、consoleには"忍者が投げて使う"が表示される。
そして、brekaによってswitch文を抜ける。これがないと、switch文を抜けられない。

defaultはどれにも当てはまらない場合。

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

【nuxt】【style-resources】「Semicolons aren't allowed in the indented syntax. というエラーがでて、buildできない

やりたかった事

scssの変数や、mixin等を一括で各コンポーネントに読み込みたい。そしてそれをbuildしたい。

手順

下記を参考に、scssの変数や、mixin等を一括で各コンポーネントに読み込んだ。
https://github.com/nuxt-community/style-resources-module

npm i @nuxtjs/style-resources

もしくは

yarn add -D @nuxtjs/style-resources

その後、 nuxt.config.jsにて追記

export default {
  buildModules: [
    '@nuxtjs/style-resources',
  ],

  styleResources: {
   // それぞれの環境に合わせて設定を記述
   sass: [],
   scss: [],
   less: [],
   stylus: []
  }
}

僕の場合は、scssファイルを使用していたので以下に変更

export default {
  buildModules: ['@nuxtjs/style-resources'],
  styleResources: {
    scss: [ // scssの変数や、mixin等のファイル名を記述
       '~assets/scss/variables.scss',
       '~assets/scss/mixin.scss',
       '~assets/scss/style.scss',
      ]
  }
}

build時に、vuetify-moduleのエラーが発生

これで開発環境では特に問題起こらず一括読み込みができるようになった。
しかし、なぜがbuildができない。。。
スクリーンショット 2020-05-20 16.53.48.png

Semicolons aren't allowed in the indented syntax

v-〇〇でのエラーなので、vuetify関連でエラーが発生してるぽい。

解決策

全く同じ現象に遭遇した人がいたので参考にしてみた。
https://github.com/nuxt-community/vuetify-module/issues/82

話を追っていくと、 一旦、node_modulesを削除、再インストールしてみると良いとのこと。

node_modeluesの削除&再インストール

試しに
node_modeluesの削除
npm i
を行ってみると、無事解決できた。詰まったらこれ大事ですね。

スクリーンショット 2020-05-20 17.42.11.png

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

リンクの中にリンクを入れたい時は?

リンクの中にリンクを入れたいレイアウト

aタグの中にaタグ

こんなレイアウ見たことありますよね?

業務でこんなレイアウトで実装しなきゃいけなくて、どうやるか色々と調べたので、まとめようと思います。

aタグの中にaタグを設置して見る

sample.html
<a>
<p>
   <a>リンク</a>
</p>
</a>

にしてみると
aタグの中にaタグ

とレイアウトが、崩れてしまいました。

対策1:Objectタグで囲む

sample.html
<a>
<p>
   <object><a>リンク</a></object>
</p>
</a>

objectタグとは?

HTML の 要素は、画像、内部の閲覧コンテキスト、プラグインによって扱われるリソースなどのように扱われる外部リソースを表現します。
リファレンス

属性は、HTML5で廃止されたものも多いので注意です。
主要なブラウザには対応しているので、使えそうですね、、、。

対策2:JavaScriptのclickイベントを使う

sample.html
<a>
<p>
   <span data-url="/link" class="js-click">リンク</span>
</p>
</a>
$('.js-click').on('click', function(event){
  //伝播をストップ
  e.stopPropagation();
  e.preventDefault();

  //リンクを取得して飛ばす
  location.href = $(this).attr('data-url');
})

引用
ここの実装は、実装環境にもよるので、引用させてもらいました。
location.href を使うって感じです。

SEOは大丈夫?

googleサイトをみる限り、jsを読んでくれますが、イベントで生成されるリンクまでを読んでくれるかは不確かです。
調べてみると
- https://www.suzukikenichi.com/blog/content-and-link-generated-by-javascript-are-treated-just-same-as-static-content-and-link/
- https://www.suzukikenichi.com/blog/google-can-read-javascript-and-pass-pagerank-and-anchor-text/
- https://webtan.impress.co.jp/e/2019/10/07/34043#moz16
という記事もあり、レンダリングされるようなリンクであれば読み取ってくれそうですが、、、。
「local.href」はわからないです。

aタグの中にaタグを書くのはNG×

リファレンスを読むと許可されている親要素に

記述コンテンツを受け入れるすべての要素、フローコンテンツ を受け入れるすべての要素。ただし 要素を除く。

と書いてあります。
aタグ内でaタグを書くのはよくなさそうですね、、、

結局リンクの中にリンクを入れたい時は?

  • 最初にレイアウトの変更を考える

のがいいかなーって思います。

リンクの中にリンクをいれると、ユーザが間違ってクリックしてしまう、間違ってタップしてしまう可能性も高いですしね。

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

KARTEで特定の値をユーザー情報に送信する方法について

不動産SHOPナカジツでマーケティングを担当している浜田です。
KARTEを利用することで、非エンジニアでも簡単なjavascriptを学習することで今回の施策を実装できますのでぜひ挑戦してください。

今回はページ内にある特定の値をユーザー情報に送信する方法について解説します。

ユーザー情報に送信したい値を決める

ユーザーが閲覧したカテゴリー情報をKARTEのユーザー情報に送信します。今回の場合、中古MS(中古マンション)の情報を送信します。

スクリーンショット 2020-05-20 14.42.46.png

KARTEでScriptの接客を作成

//種別を判別
var syubetu = document.getElementsByClassName('icon_info')[0].getElementsByTagName('img')[0].getAttribute('alt');
var eventData = {
    物件種別: syubetu
};
eventData.json = JSON.stringify(eventData);
tracker.track('identify', eventData);

DOMを利用して値を取得していきます。
今回の場合、getElementsByClassNameで取得する範囲を指定
getElementsByTagNameで取得するタグを指定
getAttribute('alt')でaltタグの値を取得します。
取得した値は変数syubetuに代入

eventDataの変数では、表示名とその値を指定
ここでは物件種別に取得した値を挿入

取得した情報をJSON形式でidentifyに送信して完了です。
ちなみにidentifyはユーザ情報になります。

最後に実際に値が送信されているか、来訪者のストーリー画面からユーザーデータを確認してみましょう。

スクリーンショット 2020-05-20 15.45.01.png

データの受信を確認できたら完了です。

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

クリップボードのデータを可及的速やかにbase64エンコードするツールを作った

WindowsではWin + Shift + Sでスクリーンショットを撮影できますね。macではCommand + Shift + 4とかです。これらのショートカットキーにより、スクリーンショットを簡単にクリップボードに保存しておくことができます。

こちらをbase64として出力したい。しかしながら一旦ファイルとして保存するのも面倒くさい。

この手のツールは大体検索すれば出回ってると思ったんですがね。こことかファイルをアップロードすることには対応していてもクリップボードからのペーストには対応していません。そこで簡易的なツールを作成しました。同じことを考えたことがある方は是非使ってやってください。

https://chelproc.github.io/paste-to-base64/

雑に作ったのでソースは載せません。まあ秘匿してもいないですが。スタイリング?そんなものは知らぬ。

とはいえここはQiitaですので、はまりポイントだけ簡単に書いておきます。

dropイベントでe.preventDefaultしてるのにページ遷移する

dragoverイベントでも同じ操作が必要だったようです。これって常識?

element.addEventListener('dragover', e => {
  e.preventDefault();
});

「クリックしてコピー」を実現するのに未だにexecCommandを使わせようとする記事がたくさんヒットする

MDNに「廃止されました」って書いてあるやん!勘弁してくだはれ。

今はAsync Clipboard APIを使うのが普通です。騙されないようにしましょう。

ただ「Secure context」でのみ有効だそうです。まあ今時httpなサイトなんて存在しないので問題ないとは思いますが。

あとは

バイナリデータをbase64に変換する手法は巷に記事があふれているのであえて言及しません。強いて挙げるならばFileReaderクラスのAPIが未だにPromiseに対応していなくて使いづらいといった点でしょうか。早く改善してほしいですね。

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

表をドラッグ & ドロップで並び替えたい

はじめに

表をドラッグ & ドロップで並び替える必要があったのですが、なにか良いライブラリがないか探すとSortableJSというのがあり使ってみると便利だったので紹介したいと思います。

インストール

npm

npm install sortablejs

CDNもあります

<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>

使い方

並び替えたいhtml要素を用意します。
※わかりやすくするためにbootstrapでcssを適用しています

html
<table class="table table-bordered w-25">
    <tbody id="sort-table">
    <tr><td>1行目</td></tr>
    <tr><td>2行目</td></tr>
    <tr><td>3行目</td></tr>
    </tbody>
</table>

用意したhtml要素をjsで取得して、SortableJSに設定します。

js
// 以下のいずれかでSortableJSを読み込みます
// CDNの場合は不要です
import Sortable from 'sortablejs';
const Sortable = require('sortablejs/Sortable');

// html要素を取得をしてSortableJSに設定します
const sortElement = document.getElementById('sort-table');
Sortable.create(sortElement);

たったこれだけで並び替えができます。
base.gif

html要素はtableタグ以外でも利用できます。

ul、liタグ

html
<ul id="sort-table">
    <li>1行目</li>
    <li>2行目</li>
    <li>3行目</li>
</ul>

divタグ

html
<div id="sort-table">
    <div>1行目</div>
    <div>2行目</div>
    <div>3行目</div>
</div>

こんな使い方はしないと思いますが、以下のように子要素が異なるタグでも適用できます。

html
<div id="sort-table">
    <p>1行目</p>
    <div>2行目</div>
    <section>3行目</section>
</div>

オプション

SortableJSにはオプションがあり、これらを利用することで簡単に機能の拡張ができます。

js
const sortElement = document.getElementById('sort-table');
Sortable.create(sortElement, {
    handle: '.handle', // ドラッグのトリガーをセレクタで指定します
    chosenClass: 'chosen', // ドラッグで選択中の要素に付与するクラス名
    animation: 200, // ドラッグして並び替える時のアニメーションの速さを指定します
});
css
<style>
    .chosen {
        background-color: #e0ffff;
    }
</style>
html
<table class="table table-bordered w-25">
    <tbody id="sort-table">
    <tr><td><button class="handle">:</button>1行目</td></tr>
    <tr><td><button class="handle">:</button>2行目</td></tr>
    <tr><td><button class="handle">:</button>3行目</td></tr>
    </tbody>
</table>

demo2.gif

まとめ

SortableJSを使うことで、ドラッグ & ドロップでの表の並び替えが簡単にできました。
公式のデモページには他にもたくさんのサンプルがあったので、まだまだ色々できそうです。

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

再帰関数を理解するためには再帰関数を理解しなければならない

「再帰関数を理解するためには、再帰関数を理解しなければならない。そのためにはまず再帰関数を理解する必要があり、その過程でさらに再帰関数を理解しなければならない。それならば、まず第一に再帰関数を理解しなければならず、かといって再帰関数をおざなりにしても再帰関数を理解することはできない。」

自分で書いた再帰関数が理解できなかった

数週間前に作ったnpmモジュールで再帰関数を何度か使ったのだが、実際は他サイトからコピペしたコードに少し手を加えただけであって、本質的には全く理解していなかった。昨日、ついに一念発起してある程度理解するに至ったので考えをまとめるために書き残しておく。

ソースコード

function com(n, k) {
    if (k === 0) return [[]];
    const result = [];
    for (let i = 0; i < n; i++) {
        if (n - i - 1 < k - 1) continue;
        com(n - i - 1, k - 1).forEach((js) => {
            result.push([i, ...js.map((j) => j + i + 1)]);
        });
    }
    return result;
}

お分かり頂けただろうか。
com関数の中でfor文を書き、その中で再びcom関数を呼び出している。

再帰関数である

ちなみにこの関数は0から第一引数nまでの数を第二引数k個の要素数で組み合わせを列挙するという関数である。なんともニッチだが、順列&組み合わせは思わぬところで必要になるので是非、僕のnpmモジュールを使って欲しい。

大まかなフローチャート

このフローチャートは素人が30分くらいで仕上げたものです。間違い等あればご指摘いただけると幸いです。

Untitled Diagram.png

1行目

「お前まさか1行ずつやってくんじゃないだろうな。。。」
「。。。」
「。。。。。。」

1行目

if (k === 0) return [[]];

kが0だった場合、つまり列挙する配列の要素数が0個の場合の処理。そもそも要素が0個なので何も返せない。ただ、ここで何も返さずにreturn;としてしまうとcom(n - i - 1, k - 1)がundefinedになるため要注意。空の配列を返さなければならない。

2行目

const result = [];

再帰的にcom関数が呼び出されるたびにresultは新しく定義されなければならないためresultの定義は関数の中でしておく。なお、再代入はしないのでconstで構わない。

3行目

for (let i = 0; i < n; i++){

n回ループ処理を行う。このiは列挙した際の一つ目の要素([0])の数値になる。

4行目

if (n - i - 1 < k - 1) continue;

(n-i-1)が(k-1)より小さかった場合、for文内の残りの処理は行わず、次のループに入る。これは少し分かりにくいので例を上げて説明する。

指定された配列の長さ(n)を5とすると、実際の配列は[0,1,2,3,4]なので、配列から選択された値(i)と配列の最後の数値(n)-1の差が列挙の要素数(k)よりも少なければ、これ以上列挙する組み合わせはない。

例えばcom(5,3)を実行し、現在i=0だった場合、iと配列の最後の数値の差は(4-0)で4になる。これは列挙する要素数(3-1)の2よりも大きいため、まだ列挙する配列が残されているということになる。

しかし、i=4の場合、iと配列の最後の数値の差は(4-4)で0になる。この場合、1行目でkが0の場合は処理が終了しているため確実にk-1は0以上になる。よって列挙する配列は残されておらず、次の処理に移る。

5行目

com(n - i - 1, k - 1).forEach((js) => {

ここがメインの再帰部分になる。ここでは、i番目の数値について、残りk-1個をi+1以降から選ぶ。n-i-1とk-1を引数にしてcom関数を再び行い、kが0になる、もしくはn-i-1<k-1が真になるまでこの呼び出しを繰り返す。

6行目

result.push([i, ...js.map((j) => j + i + 1)]);

resultにiと再帰呼び出しで帰ってきた数値にi+1をした値を配列としてpushする。i+1をすることでiより後ろの要素は必然的にiより大きい数となる。

9行目

return result;

resultを返す。これがcom関数内で呼び出されたcom関数が返す値で、この関数の最終的な出力結果でもある。

まとめ

今回は作ったことないフローチャートを作り、未だ「完全に理解した」レベルであるトピックを説明したので相当分かりにくかったと思います。この記事を読んで、全く理解できなかったという方は是非以下の記事でもっと分かりやすい説明を見ていただけると理解が深まるかと思います。
https://qiita.com/jumpyoshim/items/20e6b5e70efa466699b4
https://geeks-world.github.io/articles/J337030/index.html
https://itsakura.com/js-recursive-function

参考になれば幸いです。

筆者情報

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

IOST キャッシュ機構を用いたgasの削減

目的

storageAPIはgasの消費量が多いため(300gas)減らしたい

着想

最も削減できる手段はコントラクトの最適化を行う事だが,ある程度の規模を超えてくるとコーディングが大変になる.そこで,storage API のラッパーを実装してgasの削減を目指した.

概要

storage情報にアクセスしたらその情報をキャッシュに保存.以降のアクセスはstorageではなくキャッシュに行う.
トランザクション処理の最後にキャッシュの内容をstorageに反映する.

実装

/**
 * storage API のラッパー
 */
const props = (function () {
  const _cache = {};
  /**
   * キャッシュから値を取得
   * キャッシュに値がなければstorageから取得
   * JSON.parseをデフォルトで行う為json形式のデータを返す
   * @param {string} key 
   * @param {string} field 
   * @returns {json}
   */
  function get(key, field) {
    if (typeof _cache[key] === "undefined")
      _cache[key] = {};
    if (typeof _cache[key][field] === "undefined")
      _cache[key][field] = storage.mapGet(key, field);
    return JSON.parse(_cache[key][field])
  }
  /**
   * キャッシュに値があるかどうか確認
   * なければ storage して結果を返す
   * @param {string} key 
   * @param {string} field 
   * @returns {boolean}
   */
  function exists(key, field) {
    return get(key, field) !== null
  }
  /**
   * キャッシュに値があるかどうかのチェックを行う
   * @param {boolean} bool 
   * @param {string} key 
   * @param {string} field 
   */
  function require_exists(bool, key, field) {
    if (bool !== exists(key, field))
      throw `property_exists_${!bool}: ${key}, ${field}`
  }
  /**
   * キャッシュに値を新規追加
   * 元データがあるとエラー
   * @param {string} key 
   * @param {string} field 
   * @param {json} value 
   */
  function init(key, field, value) {
    require_exists(false, key, field);
    _cache[key][field] = JSON.stringify(value)
  }
  /**
   * キャッシュのデータを編集
   * 元データが無いとエラー
   * @param {string} key 
   * @param {string} field 
   * @param {json} value 
   */
  function update(key, field, value) {
    require_exists(true, key, field);
    _cache[key][field] = JSON.stringify(value)
  }
  /**
   * キャッシュデータを削除
   * 元データが無いとエラー
   * @param {string} key 
   * @param {string} field 
   */
  function remove(key, field) {
    require_exists(true, key, field);
    _cache[key][field] = null
  }
  /**
   * キャッシュ情報を storage へ反映
   */
  function reflect() {
    for (const key in _cache)
      for (const field in _cache[key])
        if (typeof _cache[key][field] === "string")
          storage.mapPut(key, field, _cache[key][field]);
        else
          storage.mapDel(key, field);
  }
  return {
    get, exists,
    require_exists,
    init, update, remove,
    reflect
  }
}());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IOSTコントラクト キャッシュ機構を用いたgasの削減

目的

storageAPIはgasの消費量が多いため(300gas)減らしたい

着想

最も削減できる手段はコントラクトの最適化を行う事だが,ある程度の規模を超えてくるとコーディングが大変になる.そこで,storage API のラッパーを実装してgasの削減を目指した.

概要

storage情報にアクセスしたらその情報をキャッシュに保存.以降のアクセスはstorageではなくキャッシュに行う.
トランザクション処理の最後にキャッシュの内容をstorageに反映する.

実装

/**
 * storage API のラッパー
 */
const props = (function () {
  const _cache = {};
  /**
   * キャッシュから値を取得
   * キャッシュに値がなければstorageから取得
   * JSON.parseをデフォルトで行う為json形式のデータを返す
   * @param {string} key 
   * @param {string} field 
   * @returns {json}
   */
  function get(key, field) {
    if (typeof _cache[key] === "undefined")
      _cache[key] = {};
    if (typeof _cache[key][field] === "undefined")
      _cache[key][field] = storage.mapGet(key, field);
    return JSON.parse(_cache[key][field])
  }
  /**
   * キャッシュに値があるかどうかgetを使って確認
   * @param {string} key 
   * @param {string} field 
   * @returns {boolean}
   */
  function exists(key, field) {
    return get(key, field) !== null
  }
  /**
   * キャッシュに値があるかどうかのチェックを行う
   * @param {boolean} bool 
   * @param {string} key 
   * @param {string} field 
   */
  function require_exists(bool, key, field) {
    if (bool !== exists(key, field))
      throw `property_exists_${!bool}: ${key}, ${field}`
  }
  /**
   * キャッシュに値を新規追加
   * 元データがあるとエラー
   * @param {string} key 
   * @param {string} field 
   * @param {json} value 
   */
  function init(key, field, value) {
    require_exists(false, key, field);
    _cache[key][field] = JSON.stringify(value)
  }
  /**
   * キャッシュのデータを編集
   * 元データが無いとエラー
   * @param {string} key 
   * @param {string} field 
   * @param {json} value 
   */
  function update(key, field, value) {
    require_exists(true, key, field);
    _cache[key][field] = JSON.stringify(value)
  }
  /**
   * キャッシュデータを削除
   * 元データが無いとエラー
   * @param {string} key 
   * @param {string} field 
   */
  function remove(key, field) {
    require_exists(true, key, field);
    _cache[key][field] = null
  }
  /**
   * キャッシュ情報を storage へ反映
   */
  function reflect() {
    for (const key in _cache)
      for (const field in _cache[key])
        if (typeof _cache[key][field] === "string")
          storage.mapPut(key, field, _cache[key][field]);
        else
          storage.mapDel(key, field);
  }
  return {
    get, exists,
    require_exists,
    init, update, remove,
    reflect
  }
}());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript: オブジェクトからundefinedを取り除く関数

JavaScriptのオブジェクトから、値がundefinedなフィールドを取り除く関数です。

オブジェクトからundefinedを取り除く関数

function removeUndefinedFromObject(object) {
  return Object.fromEntries(
    Object.entries(object).filter(([k, v]) => v !== undefined)
  )
}

実行例

console.log(
  removeUndefinedFromObject({
    a: undefined,
    b: 0,
    c: "",
    d: null,
    e: { e1: undefined, e2: 1 },
  })
)
//=> { b: 0, c: '', d: null, e: { e1: undefined, e2: 1 } }

注意事項

再帰的にはチェックしません

上の実行例で分かるように、ネストしたオブジェクトのプロパティはチェックされません。

対応バージョン

ES2017とES2019に未対応の環境では下記関数が動作しません:

  • Object.fromEntries - ES2019
  • Object.entries - ES2017

必要に応じてpolyfillを当てて下さい。

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