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

Vue.js のディレクティブの記述を膨らませないために

概要

本記事では、Vue.jsのディレクティブって便利だけどなんかかさばって可読性が・・という方に
改善する方法をv-bindを例にして紹介していきます。

元のコード

aタグに対して2つのv-bindが設定されています。
2つくらいなら・・と思うかもしれませんがこれが膨張していくと恐ろしいことになります。

index.html
<div id="app">
  <a :href="url" :id="number">リンク</a>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    url: 'https://sample.com',
    number: 23
  }
})

改善

ひとつのオブジェクトにまとめることができます。
画面上の表示はなにも変わりません。

index.html
<div id="app">
  <a v-bind="{href: url, id: number}">リンク</a>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    url: 'https://sample.com',
    number: 23,
  }
})

さらに改善

dataの中にオブジェクトとして持たせておいて、html上ではbindObjectとバインドさせているだけになりました。こちらも画面上の表示はなにも変わりません。
こちらの方がhtmlに記載する量が少ないのでVue.jsを書く上での理想の形です。そして管理もしやすくなりますね。

index.html
<div id="app">
  <a v-bind="bindObject">リンク</a>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    bindObject: {
      url: 'https://sample.com',
      number: 23,
    }
  }
})

最後に

可読性を下げず、チームでも個人でも管理しやすい形でコーディングできるように自分ももっと気をつけていこうと思います。

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

Vue.jsでイベントオブジェクトを取得する

概要

本記事では、Vueのディレクティブである v-on を使ってイベントオブジェクトを取得する方法を紹介します。

イベントオブジェクトとは

イベントオブジェクトは、イベントハンドラーおよびイベントリスナーにおいて実行される関数の引数として受け取ることのできるオブジェクトです。 そのイベントオブジェクトから、発生したイベントに関わる様々な情報(プロパティ)を知ることができ、またそのイベントを制御するメソッドを活用することができます。
詳しくは下記記事を参考にしてください。
https://phpjavascriptroom.com/?t=js&p=event_object

実際のコード

sampleTextというidを持たせたp要素の上にマウスを乗せた時、そのマウスの位置を取得し
その下のresultというidを持たせたp要素に表示させるという流れ。

index.html
<div id="app">
  <p id="sampleText" v-on:mousemove="mousePosition">ここにマウスを載せると下のX、Yの値が変わるよ</p>
  <p id="result">X:{{x}}, Y:{{y}}</p>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    x: 0,
    y: 0
  },
  methods: {
    mousePosition: function(event) {
      this.x = event.clientX;
      this.y = event.clientY;
      // eventの中を見てみると、全てのイベントオブジェクトが入っている
      console.log(event);
    }
  }
})

補足

今回はclientX(Y)を取得したが、eventの中をconsole.logで見ると全ての情報が入っている。

補足2

引数に渡しているeventは好きなものに変えてもちゃんと動く。

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

[Node.js]モジュール定義について整理(exports-require / export-import)

はじめに

あるファイルに定義した関数等を別のファイルで使いたいときにどうするか。

Node.jsでは二つのやり方がある。

  1. exportsで公開してrequireで読み込む(CommonJS)
  2. exportで公開してimportで読み込む(ES2015)

常に使えるのは1の方法。Babelを使うなら2の方法でも可能。
どちらを選んでもメリット・デメリットがあるわけではない(と思う)ので、お好きなほうで。

個人的には、常に使える1の方法がいいような気がしている。

使用例

exportsで公開してrequireで読み込む(CommonJS)

一つの関数だけをエクスポート

module.js
// 関数を定義
const f = () => {
    console.log('Hello!')
}

// 関数を公開
module.exports = f
client.js
const f = require('./module')
f()  // Hello!

エクスポートするのは関数に限らず、文字列や数値でも良い。

複数の関数をエクスポート

module.js
// 関数を定義
const f1 = () => console.log('Hello!')
const f2 = () => console.log('World!')

// 別名をつけて、二つの関数を公開
module.exports = {
    sayHello: f1,
    sayWorld: f2
}
client.js
const mod = require('./module')
mod.sayHello()  // Hello!
mod.sayWorld() // World!

クラスをエクスポート

module.js
// クラスを定義
class Person {
    constructor(name) {
        this.name = name
    }

    greet(params) {
        console.log(`Hello, ${this.name}!`)
    }
}

// 公開
module.exports = Person
client.js
const Person = require('./module')
p = new Person('aki')
p.greet() // Hello, aki!

おまけ:選択してインポート

module.js
const f1 = () => console.log('Hello!')
const f2 = () => console.log('World!')

module.exports = {
    sayHello: f1,
    sayWorld: f2
}
client.js
// インポートする対象を選択する
const {sayHello} = require('./module')
sayHello() // Hello!

exportで公開してimportで読み込む(ES2015)

下記の参考資料を参照。いつか書く。

参考資料

1. exports - require(CommonJS)

2. export - import((ES2015))

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

JavaScriptの多次元ハッシュ(連想配列)を一括でURIエンコードする

一括処理したいこと

HTTP通信において、次のような多次元のハッシュ(連想配列)を送信する場合、

sampleHash.js
let hash = { 
  text: "こんにちは",
  user: {
    name: "山田太郎",
    email: "taro@yamada.com"
  }
};

サーバへのリクエストは、次のようにURIエンコードして送信することになります。

URIエンコード(UTF-8)
text=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF&user[name]=%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E&user[email]=taro%40yamada.com

この処理を、関数を使って一括でURIエンコードすることがこの記事の目的です。

一括変換する関数

一括変換する関数は、次のとおりです。

sample.js
// テスト用のハッシュ(連想配列)
let hash = { 
  text: "こんにちは",
  user: { name: "山田太郎", email: "taro@yamada.com" }
};

// 一括変換する関数
function encodeHashToURI(data, stockKey="") {
  if (typeof(data) == "object") {
    let resultStr = "";  // エンコードした文字列を格納する変数
    let bracketOpen = stockKey == "" ? "&" : "[";  // 開き角括弧(一番上の階層は"&")
    let bracketClose = stockKey == "" ? "" : "]";  // 閉じ角括弧(一番上の階層は空文字列)
    Object.keys(data).forEach(function(key) {
      let newKey = stockKey + bracketOpen + encodeURIComponent(key) + bracketClose;
      let newVal = data[key];
      if (typeof(newVal) == "object") {
        resultStr += encodeHashToURI(newVal, newKey);  // 値がオブジェクトであれば再帰処理
      } else {
        resultStr += newKey + "=" + encodeURIComponent(newVal);  // 値が文字列や数値の場合は、処理を確定して要素として追加
      }
    });
    if (stockKey == "" && resultStr[0] == "&") {
      resultStr = resultStr.replace("&", "");  //1文字目の"&"を削除
    }
    return resultStr;
  } else {
    return data;
  }
}

// 処理結果の表示
console.log(encodeHashToURI(hash));
// 出力結果: text=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF&user[name]=%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E&user[email]=taro%40yamada.com

これでHTTP送信可能なデータが生成できます。
難しいことはしてないので、コードとコメントを読んでいただければ、大体わかると思います。

階層が深い場合の例

なお、関数を再帰的に利用しているので、階層の深さに関わらず使用できます。
例えば、次のような入り組んだハッシュ(連想配列)でも変換できます。

sampleHash2.js
let hash = { 
  post: {
    text: "こんにちは",
    id: 348,
    other: {
      url: "https://www.yahoo.co.jp/",
      style: {
        layer1: "階層1",
        layer2: "階層2"
      }
    }
  },
  user: {
    name: "山田太郎",
    email: "taro@yamada.com",
  }
};

出力結果は、次のようになります。

sample.js
let ans = encodeHashToURI(hash);  // 関数で変換

console.log(ans);  // 変換結果を表示
// 出力結果: post[text]=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF&post[id]=348&post[other][url]=https%3A%2F%2Fwww.yahoo.co.jp%2F&post[other][style][layer1]=%E9%9A%8E%E5%B1%A4%EF%BC%91&post[other][style][layer2]=%E9%9A%8E%E5%B1%A4%EF%BC%92&user[name]=%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E&user[email]=taro%40yamada.com

console.log(decodeURIComponent(ans));  // 変換結果をデコード
// 出力結果: post[text]=こんにちは&post[id]=348&post[other][url]=https://www.yahoo.co.jp/&post[other][style][layer1]=階層1&post[other][style][layer2]=階層2&user[name]=山田太郎&user[email]=taro@yamada.com

データ加工のイメージ

多次元配列をURIエンコードで送信する場合のルールは次のような感じです。

  • ネストしている要素は1つずつ独立させる
  • 下の階層のキーは、[ ]で括って繋げて1つのキーとする
イメージ
text: こんにちは
user[name]: 山田太郎
user[email]: taro@yamada.com

そして、URIの形式なので、次の加工も必要です。

  • キーと値は = で関連付ける
  • 要素と要素は & で接続する
イメージ
text=こんにちは&user[name]=山田太郎&user[email]=taro@yamada.com

最後に、URIエンコード(UTF-8)で変換します(再掲)。

text=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF&user[name]=%E5%B1%B1%E7%94%B0%E5%A4%AA%E9%83%8E&user[email]=taro%40yamada.com

最後に

挙動を確認しているのは、文字列型、数値型、Boolean型までです。
何らかの参考となれば幸いです。

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

javascript入門

javascriptを勉強した内容をまとめます

ブラウザで使う場合

htmlのscriptタグ内に書く

index.html
<!DOCTYPE html>
<html lang="jp">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script>
      const a = 50
      const b = 17
      const c = a * b
      console.log(c)
    </script>
  </body>
</html>

jsファイルに書いておいてscriptタグでインポートする

以下はindex.htmlと同じディレクトリにtest.jsを置いた場合です

index.html
<!DOCTYPE html>
<html lang="jp">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script type="text/javascript" src="test.js"></script>
  </body>
</html>
test.js
const a = 50;
const b = 17;
const c = a * b;
console.log(c);

変数と定数

動的型付言語なので型を明示的に宣言しなくても動きます

定数
const a = 'hoge';
const b = 300;
const c = 2.9;
console.log(a);
console.log(b);
console.log(c);
変数
var d = 'hoge';
var e = 300;
var f = 2.9;
console.log(d);
console.log(e);
console.log(f);

d = 'fuga';
e = 123;
f = 1.8;
console.log(d);
console.log(e);
console.log(f);

配列

javascript
var arr = [1, 2, 3];
console.log(arr);
console.log(arr[1]);

arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(arr);
console.log(arr[1][1]);

論理演算

等号が「==」、NOTが「!」、ANDが「&&」、ORが「||」になります。

javascript
const a = true && true;
const b = true && false;
console.log(a);
console.log(b);

const c = true || false;
const d = false || false;
console.log(c);
console.log(d);

console.log(!c);
console.log(!d);

if文

javascript
const flag = true
if(flag){
  console.log('input value:' + flag + ' is True');
}else{
  console.log('input value:' + flag + ' is False');
}

const value = 300
if(value < 200){
  console.log('input value:' + value + ' is less than 200');
}else{
  console.log('input value:' + value + ' is more than 200');
}

for文

javascript
for(i=0; i<10; i++){
  console.log(i);
}

switch文

javascript
const place = '東京'
switch (place)
{
    case "東京":
        console.log('Tokyo');
        break;
    case "大阪":
        console.log('Osaka');
        break;
    default:
        console.log("不明");
        break;
}

関数の書き方

3つ書き方があるみたいです。
python勢的には1つに統一してほしいですが、いろんな書き方が出来ることを是とするカルチャーなんでしょうか。

返り値のない関数
function normalFunc1(x) {
    console.log(x);
}

let normalFunc2 = function(x){
    console.log(x);
}

let arrowFunc = (x) => {
    console.log(x);
}

normalFunc1('ふつうの関数1');
normalFunc2('ふつうの関数2');
arrowFunc('アロー関数式');
返り値のある関数
function normalFunc3(x) {
    console.log(x);
    return x * 2;
}

let normalFunc4 = function(x){
    console.log(x);
    return x * 2;
}

let arrowFunc2 = (x) => {
    console.log(x);
    return x * 2;
}

console.log(normalFunc3(3));
console.log(normalFunc4(3));
console.log(arrowFunc2(3));

sleep動作

javascriptはsleep関数が用意されていないようです。これはそもそもjavascriptではsleep()をするべきでないという事みたいなんですが、待機後に実行するsetTimeout()を使えばできます。第2引数ミリ秒待機してから第1引数の内容を実行しますので、例えば2秒待ってから「2秒待機しました」と表示する場合は以下で出来ます。

javascript
setTimeout(() => {
    console.log('2秒待機しました');
}, 2000);

async/awaitによる非同期処理

以下のような仕組みで非同期処理を実施します
1. Promiseをreturnする関数をasync function内で呼ぶと非同期処理を開始する
2. PromiseをawaitするとPromiseを発行したfunctionが終了するまで待機する

複数の関数を非同期で開始しておいて、最後にまとめてPromiseをawaitするような使い方になると思います。

こちらのコードをお借りしました。
https://webbibouroku.com/Blog/Article/js-async-await

実行するとf2()が先に終了して、f1()の結果が後から返ってきます。

javascript
// 2秒後に値を返す
function getNum(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num);
        }, 2000);
    });
}

// 6秒後に出力
async function f1(value) {
    const n1 = await getNum(value*1);
    const n2 = await getNum(value*2);
    const n3 = await getNum(value*3);
    console.log(n1, n2, n3);
}

// 2秒後に出力
async function f2(value) {
    const p1 = getNum(value*1);
    const p2 = getNum(value*2);
    const p3 = getNum(value*3);
    const n1 = await p1;
    const n2 = await p2;
    const n3 = await p3;
    console.log(n1, n2, n3);
}

f1(1);
f2(2);

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

トラがエシレバターになるWebComponents

026_mini.gif

WebComponentsとは

Web Developer Roadmap でも今年からリスト入りした
コンポーネントを作るための ブラウザ標準の技術 の総称。

HTML, JS, CSSが一体となった非常に再利用性の高いコンポーネントを作ることができます。

https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_124296_6e550479-02b5-6cc0-8af0-2aa269233033.png

オリジナルのHTMLタグを作れるだけじゃない

トラがエシレバターになるコンポーネントは、
いつもみんなが使っている <img> タグに is="my-img" を指定しただけです。

スクリーンショット 2020-01-23 21.54.05.png

これがWebComponentsのすごいところで、ただオリジナルのHTMLタグを作るだけじゃなく
ネイティブの機能を活かして 独自に拡張(e.g. クリックすると回転してエシレバター)することができます。

customElements.define('my-img', MyImg, { extends: 'img' });

実装時に重要なのはこの一行だけで、
継承したいネイティブのタグ { extends: 'img' } を指定して
customElements.define にぶちこむだけです。

また、 is属性 を設定しなければ通常の <img> タグを利用することができます。

ソースコード

my-img.js
import butter from '../assets/butter.png';

/**
 * <img>タグの拡張 - HTMLImageElementを継承
 */
class MyImg extends HTMLImageElement {
  constructor() {
    super();
    this.times = 0;
    this.type = '';
    this.innerHTML = `
      <style>
        :host { display: block; cursor: pointer; }
        .rotate1 { animation: rotate 0.75s linear infinite; }
        .rotate2 { animation-duration: 0.5s; }
        .rotate3 { animation-duration: 0.25s; }
        .rotate4 { animation-duration: 0.125s; }
        .rotate5 { animation-name: none; }
        @keyframes rotate {
          0% { transform: rotate(0deg) translate(-50px) rotate(0deg); }
          100% { transform: rotate(-360deg) translate(-50px)  rotate(360deg); }
        }
      </style>
    `;
    this.addEventListener('click', this.rotate);
  }

  rotate() {
    this.times++;
    this.classList.add(`rotate${this.times}`);

    // バターになる
    if (this.times === 5) {
      this.src = butter;
    }
  }
}

customElements.define('my-img', MyImg, { extends: 'img' });

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>WebComponents Sample</title>
  </head>
  <body>
    <h1>トラがエシレバターになるWebComponents</h1>

    <div style="text-align: center; margin: 50px auto;">
      <img src="./assets/tiger.png" alt="トラ" is="my-img" width="240" />
    </div>

    <script src="./my-img.js"></script>
  </body>
</html>

トラがエシレバターになるPR:
https://github.com/cc822jp/saturday-hackathon/pull/1/files

その他こだわった点

普通に回転させるとバター感が薄れてしまうため
半円周上に動くように調整しました。

demo2.gif

実際の案件に耐えうるのか?

MS Edgeのブラウザエンジンがchromiumに置き換わることで、
全てのメジャーブラウザでpolyfill無しで利用することができます。

※Googleが推進するPolymer Projectでも歓喜してました
※polyfillを使うことで旧バージョンのEdgeでも利用することはできます。

まとめ

動画でも解説しているので良ければ見てみてくださいm(__)m

https://youtu.be/RkBrmHKIgik

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

[JavaScript] Submit without submit button

submit ボタン無しで javascript から submit するYO!

<style>
  textarea {
    vertical-align: top;
  }
</style>

<form id='inputs'>
  <p>
    <label for='name'>Name</label>
    <input id='name'>
  </p>

  <p>
    <label for='content'>Content</label>
    <textarea id='content'></textarea>
  </p>
</form>

<script>
  // Meta + Enter で submit しますねん
  inputs.onkeydown = e => {
    // Meta + Enter で処理する
    if (e.key != 'Enter' || !e.metaKey) return;

    // フォーム (HTMLFormElement) を得る
    const form = e.currentTarget;

    // 空の Event を作る
    const form_event = document.createEvent('HTMLEvents')

    // Event 初期化 (submit 機能。イベントチェーンを浮上。キャンセル可)
    form_event.initEvent('submit', true, true);

    // イベントを dispatch できたら……
    if (form.dispatchEvent(form_event)) {
      // submit() メソッドを使える
      form.submit();
    }
  }
</script>

createEvent()HTMLEvents ってのがよく分からない。
取り得るイベント型は "UIEvents", "MouseEvents", "MutationEvents", "HTMLEvents" のいずれかです」で submit に関するコトだから、 HTMLEvents なのかなあ、と

あと dispatchEvent() も、どうしてこれでうまくいくのか説明できないorz

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

LaravelでUncaught domexception: failed to execute '***' on 'element': ',' is not a valid attribute name.を吐き出したときに確認したこと

Laravelにてアプリを作成していたところコンソールに下記のようなエラーが発生

Uncaught domexception: failed to execute 'setattribute' on 'element': ',' is not a valid attribute name.

簡単に訳すと「属性名に','(カンマ)は使えないから'setattribute'は実行できないよ!」
と怒られてしまってます。

考えたこと

コンソールのエラーだからJavaScriptのファイルに不備があるのか?
スクリーンショット 2020-01-23 午後5.36.59.png

右側のapp.js:41954から該当エラー個所をみてみるが何もおかしいところはなさそう。
そもそも","(カンマ)がその行にない...

「属性名」に異常?つまりviewファイル側におかしいところがあるのか?

index.blade.php
      <li>
        <a href="{{ action('WordsController@show', $word )}}">{{ $word->en }}</a>
        <a href="">{{ $word->ja }}</a>
        <a href="#", data-id="{{ $word->id }}">[×]</a>
        <form method="post", action=" {{ url('/words', $word->id) }} " id="form_{{ $word->id }}" >
        {{ csrf_field() }}
        {{ method_field('delete') }}
        </form>
      </li>

よーくみると

4行目のhref="#"の後ろのカンマ...
5行目のmethod="post"の後ろのカンマ...

いりません!!!

スクリーンショット 2020-01-23 午後5.36.17.png

これを消したらエラーも消えました。

おわりに

認証機能を追加するべく、Laravel6のuiパッケージを入れ込んでからこのエラーがで始めたので、色々と考え込んでしまったけれど、結局は小さなミスでした。
直前までUIも問題なく動いていたのも時間を食ってしまいました。
しかしながら探しても日本語の記事が全然ない...
同じようなことが起こった方と自分への忘備録として残しておきます。

参考リンク

https://github.com/OnsenUI/OnsenUI/issues/169

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

moment.jsのsubtract(減算)で時間の分(m)がうまく行かなかった件

問題定義

moment.jsで30分減算した時間を出したかったが subtract(int, 'm') でブラウザがフリーズした

import moment from 'moment'
const time = moment('22:00', 'HH:mm');
// これを実装するとブラウザが固まる
const reducedTime = time.subtract(30, 'm');

解決

subtract(int, 'm')subtract(int, 'minutes')に変えると解決した

import moment from 'moment'
const time = moment('22:00', 'HH:mm');
// これを実装するとブラウザが固まる
const reducedTime = time.subtract(30, 'minutes');

コメント

なぜかはわからないので原因がわかる方はコメントで教えてくださると嬉しいです。

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

jsPDFでHTML上の日本語表記のテーブルをサクッとPDF化

概要

  • クライアントサイドで手軽にPDFを作成したい
    • HTML上の特定テーブル(日本語表記)をそのままPDF化

jsPDFとは?

  • JavaScriptを用いてPDFを作成するライブラリ

jsPDF-AutoTableとは?

  • jsPDFのテーブル作成プラグイン

How to

  • 1. 使用したい日本語フォント(.ttf)をダウンロード
    • ここは個人の好きなフォントでOK
      • 今回は Koruri という軽量日本語フォントを利用
  • 2. ここからフォントをJSファイルに変換する
    • fontNameに適当な名前を入力
    • fontStyleはダウンロードしたフォントのスタイルを指定
    • ファイルにダウンロードしたフォントファイルttfを指定
  • 3. 各種JSをHTMLにて読み込み、jsPDFを用いてPDF化処理を記述
    • jspdf.min.js
    • jspdf.plugin.autotable.js
    • Koruri-Regular-normal.js ← 2でフォントファイルから変換したJS
<html>
  <head>
    <script type="text/javascript" src="assets/js/jspdf.min.js"></script>
    <script type="text/javascript" src="assets/js/jspdf.plugin.autotable.js"></script>
    <script type="text/javascript" src="assets/js/Koruri-Regular-normal.js"></script>
    <script>
      $(function() {
        // 縦表示が'p'、横表示が'l'
        // 用紙サイズはA4
        let doc = new jsPDF('l', 'pt', 'a4', false);
        // ここでセットしたフォントはjsPDFのtext()で指定したテキストにのみ反映
        doc.setFont('Koruri-Regular', 'normal');
        doc.text(40, 30, 'HTMLテーブルをPDF化');
        doc.autoTable({
          theme: 'grid',
          html: '#table',
          // ここでフォントの指定をしないとテーブル内部の文字が化ける
          styles: {font: 'Koruri-Regular', fontStyle: 'normal', fontSize: 12}
        });
        doc.save('table.pdf');
      });
    </script>
  </head>
  <body>
    <table id="table">
      <tr>
        <th>名前</th>
        <th>年齢</th>
      </tr>
      <tr>
        <td>ほげ</td>
        <td>20</td>
      </tr>
      <tr>
        <td>ふが</td>
        <td>30</td>
      </tr>
    </table>
  </body>
</html>

まとめ

  • クライアントサイドでPDF化する方法はいくつかあるが、いまいち日本語化対応になっていなかったり、日本語対応するやり方が複雑だったりで情報も少ない。今回のやり方が一番サクッとできそうな方法かなーと。

参考

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

【Nuxt.js】TypeScript基礎編:Vue.extendでシンプルなコードを書こう

前置き

今回はNuxt.jsでTypeScript??
大まかに3つに分けて書いています✍️
・TSのメリット
・TSの書き方3つ
・Vue.extendコード例

TSのメリット

すごく簡単に言うと、
安全な開発がしやすくなります??‍♀️
ここが参考になります!
https://qiita.com/SoraKumo/items/43fba2ad2d10336a665

TSの書き方

TSを入れた場合の書き方は3パターン
・Vue.extend
・vue-class-component
・vue-property-decorator

シンプルで書きやすいのがVue.extendです?

Vue.extendのメリット

簡単⭕️
1番とっつきやすい書き方?

何故なら!
通常と書き方がほとんど変わらないから!
Vue、Nuxtらしさを保ったままTSが使えます。

TSなしの場合と変わるのは3点のみ!!!
・script langをtsに変更
・Vueモジュールをimport/extend
・コンポーネント、ページ自体にクラス名を付与

他は通常と変わらない
これがVue.extendの良さ?

他の書き方は?

@Componentとかになるあれ。
書き方が結構変わりますよね〜!
・vue-class-component
・vue-property-decorator

nuxtの場合はこちら
・nuxt-class-component
https://github.com/nuxt-community/nuxt-class-component
・nuxt-property-decorator
https://github.com/nuxt-community/nuxt-property-decorator

詳しい書き方はこちらが参考になります!
https://qiita.com/potato4d/items/c9c0c8e674f20c85948a

Vue.extend

【基礎構文】
通常と比較しても
シンプルで非常に分かりやすいですね!?

公式はこちら
https://typescript.nuxtjs.org/ja/
https://jp.vuejs.org/v2/guide/typescript.html#基本的な使い方

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 // コンポーネント・ページ自体にクラスを付与
 name: 'Component',
 components: {
 },
 props: {
 },
 data() {
  return {
  }
 },
 computed: {
 },
 mounted () {
 },
 methods: {
 },
 created () {
   console.log('CLICK!!!')// eslint-disable-line
 },
})
</script>

【通常】

index.vue
<script>
export default {
 components: {
 },
 props: {
 },
 data() {
  return {
  }
 },
 computed: {
 },
 mounted () {
 },
 methods: {
 },
 created () {
   console.log('CLICK!!!')// eslint-disable-line
 },
}
</script>

data

Stringの場合は
" "(double quote)ではなく
' ' (single quote)のみ⭕️

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 name: 'Component',
 data () {
   return {
     userName: '',
   }
 },
})
</script>

props

TSなしの場合と変わりません。
・type
・required
・validator
全てそのまま書けます✍️

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 name: 'Component',
 props: {
   status: {
     type: String,
     required: false,
     validator (value) {
       return [
         'default',
       ].includes(value)
     },
   },
 },
})
</script>

methods

こちらも同様
$emitなども通常通り記載可能です。

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 name: 'Component',
 methods: {
   alert () {
     this.$emit('componentAlert')
   },
 },
})
</script>

console.log()

methodsやライフサイクルで
console.log()を使用する場合ESlintにひっかかります。
その場合は// eslint-disable-lineを追記すれば⭕️
https://eslint.org/docs/rules/no-console

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 name: 'Component',
 created () {
   console.log('created!!!')// eslint-disable-line
 },
})
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript][Node.js]メモ:アロー関数の文法

アロー関数(ES2015)の書き方。

f = () => console.log('Hello!')
f()  // Hello!
評価した値を返す
// 式(Expressions)の評価結果が戻り値になる
sum = (a, b) => a + b
r = sum(1, 2)
console.log(r) // 3
returnで値を返す
// {}で文(Statements)を作り、returnで値を返す
sum = (a, b) => { 
  return a + b 
}
r = sum(1, 2)
console.log(r) // 3
引数のカッコを省略
// 引数が一つの時のみカッコを省略可能(0個もしくは2つ以上のときは省略不可)
say = word => console.log(word + '!')
say('Yeah')  // Yeah!
即時関数
(() => console.log('Hello!'))() 
引数としてのアロー関数
arr = [1,2,3,4,5]

// かっこよくない
console.log(arr.map(function(e) { return e + 1 }))  // [ 2, 3, 4, 5, 6 ]

// かっこいい
console.log(arr.map(e => e + 1))  // [ 2, 3, 4, 5, 6 ]

リンク(mozilla)

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

[JavaScript][Node.js]アロー関数のサンプル集

アロー関数(ES2015)ってかっこいいですよね。

ふつうに
f = () => (console.log('Hello!'))
f()  // Hello!
カッコは省略可能
f = () => console.log('Hello!')
f()  // Hello!
returnの省略
// 一行の時だけ
sum = (a, b) => a + b
r = sum(1, 2)
console.log(r) // 3
returnの省略(NGパターン)
// {}で括ると省略できない。()ならOK。
sum = (a, b) => { a + b }
r = sum(1, 2)
console.log(r) // undefined
複数行
// returnは必須
sum = (a, b) => { 
  return a + b 
}
r = sum(1, 2)
console.log(r) // 3
引数のカッコを省略
// 引数が一つの時のみカッコを省略可能(0個もしくは2つ以上のときは省略不可)
say = word => console.log(word + '!')
say('Yeah')  // Yeah!
即時関数
(() => console.log('Hello!'))() 
引数としてのアロー関数
arr = [1,2,3,4,5]

// かっこよくない
console.log(arr.map(function(e) { return e + 1 }))  // [ 2, 3, 4, 5, 6 ]

// かっこいい
console.log(arr.map(e => e + 1))  // [ 2, 3, 4, 5, 6 ]

リンク(mozilla)

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

[JavaScript][Node.js]アロー関数のサンプル集

アロー関数(ES2015)ってかっこいいですよね。

ふつうに
f = () => (console.log('Hello!'))
f()  // Hello!
カッコは省略可能
f = () => console.log('Hello!')
f()  // Hello!
returnの省略
// 一行の時だけ
sum = (a, b) => a + b
r = sum(1, 2)
console.log(r) // 3
returnの省略(NGパターン)
// {}で括ると省略できない。()ならOK。
sum = (a, b) => { a + b }
r = sum(1, 2)
console.log(r) // undefined
複数行
// returnは必須
sum = (a, b) => { 
  return a + b 
}
r = sum(1, 2)
console.log(r) // 3
引数のカッコを省略
// 引数が一つの時のみカッコを省略可能(0個もしくは2つ以上のときは省略不可)
say = word => console.log(word + '!')
say('Yeah')  // Yeah!
即時関数
(() => console.log('Hello!'))() 
引数としてのアロー関数
arr = [1,2,3,4,5]

// かっこよくない
console.log(arr.map(function(e) { return e + 1 }))  // [ 2, 3, 4, 5, 6 ]

// かっこいい
console.log(arr.map(e => e + 1))  // [ 2, 3, 4, 5, 6 ]

リンク(mozilla)

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

TypeScriptで FileReaderのonloadした後、resultに対してエラーが出る時の暫定対処

FileReader

https://developer.mozilla.org/ja/docs/Web/API/FileReader/result

なんかおかしいらしい。

スクリーンショット 2020-01-23 15.48.46.png

TS2339: Property 'result' does not exist on type 'EventTarget'

なぜか怒られる

https://github.com/Microsoft/TypeScript/issues/4163
なんかおかしいらしい。
※翻訳してまで読んでないので興味がある方は調べてください。

const reader = new FileReader();
      reader.onload = (e) => {
        //(e.target as any)でany型に変換する
        console.log((e.target as any).result);
      };
      reader.readAsDataURL(file);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npmとは yarnとは

この記事の目的

yarnとは何か、npmとは何かという概念を理解することを目的としています。


yarnとは npmとは・・・

Node.jsで動作するパッケージマネージャー

Node.jsとは

Node.jsというのは、JavaScriptでサーバーサイドの処理を可能にさせるプログラム

パッケージマネーシャーとは

「パッケージマネージャとは、コンピュータに何のソフトウェアがインストールされたかを記録し、新しいソフトウェアのインストール・新しいバージョンへのソフトウェアの更新・以前インストールしたソフトウェアの削除を容易に行えるようにするプログラム」
【Debian公式ページ】パッケージマネージャとは


つまり

yarnとnpmはJavaScriptでサーバーサイドの処理を行うために使うプログラム(Node.js)上で使うパッケージマネージャーということです。
下記で一つずつ確認していきましょう。


JavaScriptはもともとフロントエンド言語

  • ブラウザでの画面表示部分、つまりフロントエンドで使用されることを前提に作られたプログラム言語。
  • データベースへのアクセスなどサーバーサイドの処理はできなかった。

Node.jsの誕生でJavaScriptでサーバーサイドの処理が可能に

  • Node.jsが開発されたことで、JavaScriptでサーバーサイドの処理ができるようになった。
  • それに伴い、JavaScriptのフレームワークが開発された。(React、Express、)

フレームワークを使うためにはインストールを簡単にする必要がある

  • 1000個のプログラムで出来ているフレームワークをインストールするとして、そのフレームワークが動くために必要なプログラムを全て手作業でサーバーの適切な場所にコピーし、特定の設定ファイルを書き換え、正常にインストールできたかを確認し、バージョン管理をするのはかなり困難。

パッケージマネージャーがインストールや管理を簡単にする

  • 簡単なコマンドの入力でインストールを完了することができる。

Node.jsで動作するパッケージマネージャー、それがyarnとnpm

:)

npmは

  • Node.jsをインストールすれば一緒にインストールされる。
  • 2009年にNode.jsがリリースされた翌年にnpmがリリースされた。

yarnは

  • 2016年にリリース。npmと互換性があり、npmで使用していたプロジェクト設定ファイル(package.json)がそのまま使える。
  • npmと比べてインストールが速い、セキュリティが高いという特徴がある。
    • セキュリティが高いというのは、インストール時にパッケージが不正に変更されていないかなどをチェックサムを用いて検証することができ、安全なパッケージのインストールが可能であるということ。
  • バージョン管理についても優れていて、yarnではプログラムのインストール後に、yarn.lockというファイルが作成され、それにはインストールしたプログラムが使用している別のプログラム(依存プログラム/パッケージ)のバージョンが明確に書き込まれている。
    • 依存プログラム/パッケージをその後再度インストールしてもバージョンの整合性が保たれるので、バージョン不一致でプロジェクトが動かなくなる危険性が無くなる。
  • npm、homebrew、MacPortsからインストールできる。

【公式ページ】Yarnのインストール

npmとyarnの違い

依存プログラム/パッケージのケア

npm:バージョン違いの依存プログラム/パッケージをインストールしてしまう可能性あり
yarn:yarn.lockファイルにより、バージョン違いの依存プログラム/パッケージのインストールは起こらない

インストール速度

npm:遅い
yarn:早い

参考

【Samurai Blog】JavaScriptのパッケージマネージャーnpmとYarnについて解説します!
【Samurai Blog】【Node.js】初心者も理解できる言語の特徴を体系的に解説!

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

Nuxt.jsのbeforeDestroyed()でイベントリスナーを削除できなかった時の対処法。

発端

偉い人「このページは横画面でしか表示したくない」
蝦「はい」

環境

OS: windows10 64bit
node.js v10.16.3
Nuxt.js v2.10.2

イベントリスナーを追加

蝦「画面の向きが変わるたびにVueファイル内のdata()の値を変えてやればおk」
とりあえずMDNのサンプルをそのままつかう。
Window: orientationchange イベント - Web API | MDN

ebi.js
<script>
//中略
beforeMount() {
    console.log("beforeMount");
    window.addEventListener("orientationchange", this.checkRotate, false);
  }

</script>

しかし

イベントリスナーが消えない。

「後はbeforeDestroy()でイベントリスナーを削除するように書けばおk」

kani.js
<script>
//中略
  beforeDestroy() {
    console.log("beforeDestroy");
    window.removeEventListener("orientationchange", this.checkRotate, false);
  },

</script>

消えない。
コメント 2020-01-23 145126.png
追加はされるが削除はされないので、該当ページに遷移するたびにイベントリスナーが増える。やばい

原因: beforeDestroy()もDestroy()も実行されてない

プリントデバッグしてみると、ページ離脱時にbeforeDestroy()もDestroy()も実行されてない。
再起動してもキャッシュを消しても実行されないという不思議。

解決策: ナビゲーションガードを使う。

どうにもならなくなったらbeforeRouteLeave(to, from, next)で削除すればおk。
ナビゲーションガード | Vue Router
Vue-Routerのナビゲーションガードを使ってみる - Qiita

uni.js
<script>
//中略
 beforeRouteLeave(to, from, next) {
    if (to.name) {
      next();
      window.removeEventListener("orientationchange", this.checkRotate, false);
      return;
    }
    return;
  },

</script>

消えました。
コメント 2020-01-23 151735.png

別のプロジェクトでは再現しなかったので、多分もう使うことはほぼないだろうけど備忘録代わりに書いておく。
原因がわかる人がいたらだれか教えてください。

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

ReactNative(Expo)でオススメしたいライブラリ

ReactNativeを開発していて、しばらくしてからにこんなライブラリあったんだ!!
ということがしばしばあったので、ReactNativeの開発を始めようと思っている方向けに、オススメのライブラリを紹介していこうと思います。

採用したライブラリ

native-base

当初、react-native-elementを導入していたが、
styleを書く手間を省けない。
アクションシートやツールチップなど、OS間の差分を吸収してくれるUIがなかっためにnative-baseに移行した。
githubのスターではreact-native-elementが上回っているが、
工数削減を目的とした場合は、native-baseの方が良いと思う。

react-native-swiper

intro系のライブラリと比較してもswiperで事足りそうなので実装が手軽な点が魅 a了。

そのほかにも紹介予定のライブラリー・・・

lottie-react-native

react-navigation-redux-helpers

sentry-expo

expo-analytics-amplitude

expo-analytics

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

ReactNative(Expo)でイベントトラッキング 〜expo-analyticsの使い方をざっくりまとめる〜

ReactNativeでイベントトラッキングする時に候補に上がるのは、

  1. firebase
  2. amplitude
  3. expo-analytics

の3つだと思います。
今回は、そのうちの1つexpo-analyticsの紹介です。

expo-analyticsはWeb版GoogleAnalyticsのイベントトラッキングをReactNativeで簡単に利用できるようにしてくれているライブラリです。
正直、WebサイトでGAのイベントトラッキングをしたことがある人は、
使い方がほぼ同じなのでこの記事を見る必要はないです笑

リポジトリURL
https://github.com/ryanvanderpol/expo-analytics

Events [イベント]

GoogleAnalyticsでの説明
https://developers.google.com/analytics/devguides/collection/analyticsjs/events?hl=ja

analytics.event(new Event('eventCategory', 'eventAction', 'eventLabel', eventValue))

例)
analytics.event(new Event('Video', 'Play', 'The Big Lebowski', 123))
 .then(() => console.log("success"))
 .catch(e => console.log(e.message));

引数の対応表

引数 フィールド名 値の型 必須 説明
第1引数 eventCategory テキスト はい 通常はインタラクションに使用されたオブジェクト(例: 'Video')
第2引数 eventAction テキスト はい インタラクションの種類(例: 'play')
第3引数 eventLabel テキスト いいえ イベントを分類する際に使用(例: 'Fall Campaign')
第4引数 eventValue 整数 いいえ イベントに関連する数値(例: 42)

Custom Dimensions [カスタムディメンション]

GoogleAnalyticsでの説明
https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cd_

ログイン状態や有料ユーザーなど独自で分析軸を定義したい時に使用する。
※最大20個までなのでトラッキングクラスでインデックスを管理する必要がある
Google analytics版の解説
https://webtan.impress.co.jp/e/2017/09/21/26869

GooleAnalytics管理画面の設定手順

GA管理画面で管理→プロパティ→カスタム定義にて、カスタムディメンションを作成。
「ユーザー」「セッション」「ヒット」「商品」のどれに紐づくかを設定。

実装

analytics.addCustomDimension(1(作成したディメンションのインデックス番号), 'Comedy’(ディメンション名))

使いところわからないけど、削除も可能。
analytics.removeCustomDimension(1);

Custom Metrics [カスタム指標]

GoogleAnalyticsでの説明
https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cm_

数値の独自定義をしたい時に使用する、
こちらも最大20個までなのでトラッキングクラスでインデックスを管理する必要がある

想定イメージ

  • 電話のコール数
  • アプリの起動回数
  • 表示時間

フォーマット

  • 整数
  • 通過
  • 時間

実装例

analytics.addCustomMetric(1, 15);
analytics.removeCustomMetric(1);

デバックモード

デバックモードをtrueにするとconsole.logに表示される。

const analytics = new Analytics('UA-XXXXXX-Y', null, { debug: true })
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1時間でChrome拡張作ってみた!

なぜか急にChrome拡張というものを作ってみたくなったので、作ってみました!:sunglasses:

作ったもの

Instagramってログインしてないと、全部の写真見えないんです...。

instagram-movie-01-before.gif

アカウント作ってログインすればいいって話なんですけど、見るためだけにアカウント作るのがなんかあれだったので、ログインを促すポップアップが表示されないようにしました!

ソースコードはGithubリポジトリを参照してください ↓
https://github.com/Michin0suke/instagram-extension-pack

1. マニフェストファイルをつくる!

所要時間:15分

Chrome拡張にはマニフェストファイル(manifest.json)というのが必須らしいです。この記事を参考にさせていただきました!

{
  "manifest_version": 2,
  "name": "Instagram without login",
  "version": "1.0.0",
  "description": "Chrome extension to remove login prompts on Instagram sites",
  "icons": {
    "16": "icon16.png",
    "48": "icon48.png",
    "128": "icon128.png"
    },
  "content_scripts": [
    {
        "matches": [ "https://www.instagram.com/*" ],
        "js": [ "script.js" ]
    }
],
}

こんな感じで完成!

descriptionは英語書けないので、Google翻訳にやってもらいました笑

無理に英語を使わなくても、日本語でも大丈夫です。

Google翻訳

2. アイコンを用意!

所要時間:15分

Illustratorでテキトーに作りました笑

icon128.png

なんかアンチインスタグラムみたいなアイコンになっちゃったけど配信はしないのでOK!

マニフェストに指定した通り、16px*16px、19px*19px、48px*48px、128px*128pxを用意しました。

3. スクリプト書くよ!

所要時間:20分

script.jsに以下を書き込みます。

document.querySelector('.ctQZg').remove()
const interval = setInterval(() => {
  if (document.querySelector('.RnEpo.Yx5HN')) {
    document.querySelector('.RnEpo.Yx5HN').remove();
    setTimeout(() => document.body.style.overflow = 'visible', 50);
    clearInterval(interval);
  }
}, 100);

今回のスクリプトは数行なので簡単!

JavaScriptの簡単な説明をすると、0.1秒毎にログインポップアップが表示されているか確認して、表示されていれば消して、bodyに付与されるスクロール禁止も外すというものです。ポップアップを消したあとは、clearInterval()で繰り返し実行を止めてます。

追加で、ログインボタンとか下に表示されるバナーも非表示にしました!
before
スクリーンショット 2020-01-23 14.06.33.png
after
スクリーンショット 2020-01-23 14.06.49.png

4. Chromeで実際に使ってみる!

所要時間:10分

『パッケージ化されていない拡張機能を読み込む』でもいいんですが、毎回警告が表示されるらしいので、chrome://extensions/から『拡張機能をパッケージ化』してみます。

下の画像だとダウンロード直下にソースファイルを含むディレクトリを置いていますが、この記事の最後にあるファイル構成のように2重のディレクトリ構成にしておいたほうが、.pem.crxが管理しやすくて良さそうです。

拡張機能をパッケージ化

ポチポチして鍵を作って、生成された.crxファイルをChromeにドラッグ&ドロップ!

Before

instagram-movie-01-before.gif

After

instagram-movie-01-after.gif

ポップアップは一瞬表示されるけど、すぐ無くなって快適!:tada::tada:

これでインスタのアカウントなくてもインスタグラムを楽しめます!

(なぜそこまでしてアカウントを作ろうとしないのか)

ファイル構成

instagram-extension-pack
├── instagram-extension
│   ├── icon128.png
│   ├── icon16.png
│   ├── icon19.png
│   ├── icon48.png
│   ├── manifest.json
│   └── script.js
├── instagram-extension.crx
└── instagram-extension.pem

感想

思ったより簡単に作れた!:grinning:

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

Javascript アロー関数を簡単にまとめて学ぶ

はじめに

アロー関数?書けるよ。あまり理解してないけどって人向けに具体的な使用例の一部をお伝えする記事です。
アロー関数とは、

アロー関数式は、より短く記述できる、通常の function 式の代替構文です。また、this, arguments, super, new.target を束縛しません。アロー関数式は、メソッドでない関数に最適で、コンストラクタとして使うことはできません。

つまり従来より簡潔に書けるし明瞭な構文で書けるってこと。
ちなみに関数とメソッドの違いは、

  • 単独で 呼び出せる のが関数
myfunc(); <- こんな形の奴
  • 単独で 呼び出せない のが関数
var str = 'user1,user2,user3';

var result = str.split(','); <- メソッド

従来の関数の書き方との書き換え方

引数の値を2倍にして返り値で返す関数で例えると、

  • 従来の書き方
index.js
function doubleUp(num){
   return num * 2;
} 
  • アロー関数で書き換えた書き方 その1
index.js
const doubleUp = (num) => {
   return num * 2;
} 
  • アロー関数で書き換えた書き方 その2.0 (引数が一つの場合に限る)
index.js
const doubleUp = num => {
   return num * 2;
} 
  • アロー関数で書き換えた書き方 その2.1 (引数が一つ以上の場合)
index.js
const doubleUp = (a, b) => a * b;

  • アロー関数で書き換えた書き方 その3.0 (関数の処理が returnで一行の場合に限る)
index.js
const doubleUp = num => num * 2;

  • アロー関数で書き換えた書き方 その3.1 (関数の処理が returnで一行以上の場合)
index.js
const doubleUp = num => {
   alert('hoge');
   return num * 2;
}

アロー関数と無名関数の挙動って一緒なの?

結論から言うと一緒じゃない。
関数内で this を使う場合は挙動が変わってくるんじゃい。
以下の関数を見るんじゃい。

index.js
let person = {
   age: "30",
   callName: function(){
      console.log(this.age); //"30" が出力される

       window.setTimeout(function() {
          console.log(this.age) // undefined が出力される
      }, 1000);
   }
};

console.log(this)で両方とも出力してもらえればわかると思うんじゃい。
window に関してはそもそも callName というオブジェクトがないんじゃい。
なので参照しようとすると undefined になるんじゃい。

しかし上記のようなオブジェクトにおいてageプロパティをwindow.setTimeout...内で使用したいというケースはよくあるケース。

ではどうするかというと、こうする。

  • やり方 その1
index.js
let person = {
   age: "30",
   callName: function(){
      console.log(this.age); //  "30" が出力される


       window.setTimeout(function() {
          console.log(this.age) //  "30" が出力される
      }.bind(this), 1000);
   }
};

bind()は引数とされている this と callName直下のスコープ内にある this を同じものとする機能があるんじゃい。
- やり方 その2

index.js
let person = {
   age: "30",
   callName: function(){
      console.log(this.age); //"30" が出力される
      let hoge = this;

       window.setTimeout(function() {
          console.log(hoge.age) //"30" が出力される
      }, 1000);
   }
};

hoge変数にpersonオブジェクトのスコープを代入してあげるんじゃい。

  • やり方 その3
index.js
let person = {
   age: "30",
   callName: function(){
      console.log(this.age); //"30" が出力される

       window.setTimeout(function() {
          console.log(this.age) //"30" が出力される
      }.call(this), 1000);
   }
};

callメソッドを使うんじゃい。

  • やり方 その4
index.js
let person = {
   age: "30",
   callName: function(){
      console.log(this.age); //"30" が出力される

       window.setTimeout(() => {
          console.log(this.age) //"30" が出力される
      }, 1000);
   }
};

アロー関数を使うんじゃい。
アロー関数内を使うと、アロー関数内でthisが定義されないんじゃい。
javascriptの性質上, アロー関数内でthisを見つけられない場合スコープチェーンを辿って
上の階層に同じ変数名のものを探すんじゃい。

最終的にグローバル変数まで探して見つかれなければ undefined を返すんじゃい。
こういう使い方があるんじゃい。

以上じゃい。

ps:
こういうやり方もあるよ〜」とかあれば教えて欲しいんじゃい。
勉強になるんじゃい。

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

VisualForce で rerender すると4バイト文字が化ける

問題点

SalesForce の VisualForce で rerender すると
4バイト文字が文字化けする

サンプル

単純にボタン押したら再描画するだけの画面を作成

Test.vfp
<apex:page standardController="User" extensions="TestController" title="Test">
    <div>rerender範囲外:?野家</div>
    <apex:form id="frm">
        <div>rerender範囲内:?野家</div>
        <apex:commandButton value="rerender" action="{!act}" status="reloadStatus" rerender="frm" />
    </apex:form>
</apex:page>

TestController.apxc
public class TestController {
    public TestController(ApexPages.StandardController controller) {

    }

    public void act(){
        return;
    }
}

これで rerender ボタンを押下すると

before.png
 ↓
after.png

rerender 範囲内の「?野家」が「ஷ野家」になってしまう
※範囲外に関しては変更なし

調査

文字コード

とりあえず「?」と「」のコードポイント調べた

Test.js
console.log(''.codePointAt().toString(16));    // 0x0bb7
console.log('?'.codePointAt().toString(16));    // 0x20bb7

絶対これや
サロゲートペア文字(4バイト文字)がおかしくなる模様

欠損箇所

rerender の範囲外は正常なことから apex:commandButton が怪しい
ボタン押下時は Ajax で Apex にアクセスしてるようなのでレスポンス内容を見てみる

Response
<?xml version="1.0"?>
<html lang="en_US" xmlns="http://www.w3.org/1999/xhtml"><head>
    <!--省略-->
</head>
<body>
    <form id="j_id0:frm" name="j_id0:frm" method="post" action="https://hogehoge.visual.force.com/apex/Test?core.apexpages.request.devconsole=1" enctype="application/x-www-form-urlencoded">
        <input type="hidden" name="j_id0:frm" value="j_id0:frm" /> 
        <div>rerender範囲内:ஷ野家</div>
    <!--省略-->
</body>
</html>

もう化けとる
このへん見る限り XML ではサロゲートペア使えないっぽい

数値文字参照 &#x20bb7; にすればいけるかな~と思ったけど
状況変わらず Ajax のレスポンス時点で文字化けしてた。

対策

苦肉の策としてエスケープを挟むことにした
画面側でデコードできればURLエンコードとか他のでもいいと思う

Test.vfp
<apex:page standardController="User" extensions="TestController" title="Test">
    <div>rerender範囲外:?野家</div>
    <apex:form id="frm">
        <div id="yoshi" style="visibility:hidden;">%uD842%uDFB7</div>
        <apex:commandButton value="rerender" action="{!act}" status="reloadStatus" rerender="frm" />
        <script>
            var ysdiv = document.getElementById('yoshi')
            ysdiv.innerHTML = unescape(ysdiv.innerHTML);
            ysdiv.style.visibility = "visible";
        </script>
    </apex:form>
</apex:page>

エスケープ後の文字列を表示してJSで無理矢理元に戻す感じ
<script>rerender 内に置かないと動かないので注意

もうちょいきれいな対策したい

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

あっさり読むrails④(非同期通信)

はじめに

JSを使った非同期通信を簡単に書いてみます。

前提

使用するのは、
Ruby on rails
Haml
jQuery
です。
CSSは特に指定しません。

実行

次のファイルを用意します。

sample.haml.html
= form_with model:@sample, local: true do |f|
  = f.text_area :name, placeholder: "サンプル", class: "sample-form"

.add-text

これがビューデータとなります。

sample.js
$(function(){
 $(".sample-form").on("change" functon(){
   var sampletext = $(this).val;
   $(".add-text").text(sampletext):
 })
});

これが非同期処理の中身になります。

これで、テキストエリアに文章を入力すれば、同じ文章が.add-textの部分に表示されます。

※随時更新します

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

あっさり読むrails④(非同期処理)

はじめに

JSを使った非同期処理を簡単に書いてみます。

前提

使用するのは、
Ruby on rails
Haml
jQuery
です。
CSSは特に指定しません。

実行

次のファイルを用意します。

Gemfile
gem 'jquery-rails

これを記述して、bundle installします。
(ディレクトリが、アプリのディレクトリになっている事を確認。pwdというコマンドで確認可能)

application.js
//= require jquery

この記述を忘れるとエラーになります($とは何ですか?という感じのエラー)

sample.haml.html
= form_with model:@sample, local: true do |f|
  = f.text_area :name, placeholder: "サンプル", class: "sample-form"

.add-text

これがビューデータとなります。

sample.js
$(function(){
 $(".sample-form").on("change" functon(){
   var sampletext = $(this).val;
   $(".add-text").text(sampletext):
 })
});

これが非同期処理の中身になります。

これで、テキストエリアに文章を入力すれば、同じ文章が.add-textの部分に表示されます。

※随時更新します

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

Node.js、Web Speech APIを使って音声認識を出力

概要

WebSpeechAPIを使用して聞き取った音声の文字おこしをブラウザ上に表示させます。

作成方法

1.WebSpeechAPIを含むhtmlフォルダの作成

新規フォルダを作成し、その中にindex.htmlを作成。

index.html
<!-- index_voice.html -->

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>音声認識サンプル</title>
</head>
<body>
    <h2>音声認識サンプル</h2>
    <button id="btn">start</button>
    <div id="content"></div>

<script>
//ここに音声認識の処理を書いていく

</script>
<body>
<html>

音声認識をするための処理をscriptタグ内に書いていきます。

index.html
//ここに音声認識の処理を書いていく
const speech = new webkitSpeechRecognition();
speech.lang = 'ja-JP';
</script>

音声認識を実行する準備はこの2行で完了です。
webkitSpeechRecognition()を定義し、langをja-JPにすることで日本語に対応したWeb Speech APIが使えるようになります。
そしてこれをイベントで実行出来るようにしていきます。

index.html
//ここに音声認識の処理を書いていく
const speech = new webkitSpeechRecognition();
speech.lang = 'ja-JP';

//---------------追記---------------//
const btn = document.getElementById('btn');
const content = document.getElementById('content');

btn.addEventListener('click' , function() {
    // 音声認識をスタート
    speech.start();
});

speech.addEventListener('result' , function(e) {
   // 音声認識で取得した情報を、コンソール画面に表示
   console.log(e);
//---------------追記---------------//
    // 音声認識で取得した情報を、HTMLに表示
    const text = e.results[0][0].transcript;
    content.innerText = text;
    //--------------------------------//
});
//--------------------------------//

</script>

2.サーバー立ち上げ
node.js Expressを使ってローカルサーバーの立ち上げを行います。
Web speech Api-sampleというフォルダー作成します。

初期設定します。

npm init -y

必要なライブラリをインストールします。

npm i body-parser express

index.jsを作成し、こちらのコードをコピペします。

var express = require('express');
var app = express();

// public というフォルダに入れられた静的ファイルはそのまま表示
app.use(express.static(__dirname + '/public'));

// bodyParser
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.post('/post', function(req, res) {
  for (key in req.body) {
    console.log(key, '=', req.body[key]);
  }
  res.end();
});
app.listen(process.env.PORT || 8080);

console.log("server start! (heroku)");

こちらをindex.jsで保存します。
index.js と同じ階層に public フォルダを作りその中に 先ほど作成したindex.html を格納します。

node index.js

実行します。

http://localhost:8080/ でアクセス。
スタートボタンをおして話し、終わりにまたボタンを押すと。
音声認識.PNG

これでひとまず完了です。次に使用してHerokuにデプロイしていきます。(続く)

考察

まずは音声認識APIをつかってアウトプットすることができました。音声の聞き取りの精度もパソコンを目の前にあるような状況、会議とかだったら問題なく拾える制度でした。
次は、Vue.jsでデザインを整えていきます。
また、いまのままではただの文字の羅列になるので、これをどう編集させていくか、プログラムで機能を追加できていければと思います。

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

【Rails】 DataTables のテーブルに関連付けされたモデルのデータを表示する方法

はじめに

DataTableへデータを渡すときに json 形式に変換する必要があります。
関連付けされたモデルのデータを簡単に json 形式に変換することができます。

関連リンク

関連リンクを下記に載せておくので、必要であれば参考にしてください。。

関連付けされたモデルのデータを表示する方法

as_json の include をすることで user に関連付けされた post のデータを一緒に json のデータとして作成してくれます。

app/datatables/users_datatable.rb
class UsersDatatable
  # *** 省略 ***

  def as_json(options = {})
    {
        recordsTotal: User.count, # 取得件数
        recordsFiltered: users.total_count, # フィルター前の全件数

        # user モデルに関連付けされた post モデルを include して json ファイルを作成する。
        data: users.as_json(include: :post), # 表データ
    }
  end

  # *** 省略 ***
end

まとめ

こちらは突き詰めると、 DataTable 特有の使い方ではなく、 json ファイルの作り方の記事になってしまっています。
使い方が全くわからないという方には少しは参考になるかなーと思い投稿させていただきました。

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

ブログカードを支える技術

ブログカードとは以下のようにリンクをちょっとリッチに表示してくれる機能のことです。以前はこれってどうやって実現しているんだろうと不思議に思っていました。


 
上記のようなブログカードは「はてなブログ」や「WordPress」等のブログサービスでよく見かけますが、基本的にこれらのブログカードはリンク先のURLを指定するだけで自動的に生成されています。本記事では上記のようなブログカードを支える技術について解説します。

(本記事は自分のブログからの転載記事です。)

はじめに

本記事では、ブログカード1の表示に使われる一般的な技術の解説およびJavaScriptによる実装を行います。普段何気なく見たり使ったりしているブログカードの技術に興味がある人におすすめします。

ブログカードの要素技術

まずはブログカードを実現するための要素技術について解説します。

ブログカードの構成

ブログカードは主に「タイトル」、「説明」、「画像」から成ります。オプションで「favicon」、「サイト名」、「ソーシャルカウント」を表示する場合もあります。

ブログカードの主な情報源

ブログカードの主な情報源は以下の3つになりますが、メインの「タイトル」、「説明」、「画像」といった情報を提供しているのはOpen Graph Protocolになります。

  • Open Graph Protocol(OGP)
  • favicon
  • ソーシャルカウント

Open Graph Protocol

Open Graph Protocolの説明の前提知識として、まずはソーシャルグラフについて説明します。ソーシャルグラフとはFacebookやTwitter等のSNS(ソーシャル・ネットワーク・サービス)において、人と人の繋がりである「ソーシャル・ネットワーク」を点と線で可視化したものです。「Graph」の由来は、点と点の結びつきに関する数学理論であるグラフ理論から来ています。


 
上記のようなソーシャルグラフの構築には点と点をつないで線にする仕組みが必要です。そのために考案されたのがOpen Graph Protocol(OGP)です。ソーシャルグラフにおける「点」は「人」を表していますが、Webの世界ではWebページを「人」とみなしてソーシャルグラフを構築します。WebページはHTMLで記述されHTTPプロトコルを用いてやり取りされますが、基本的にはOGPもその仕組みの上に成り立っています。具体的には以下の図のように単純な仕組みでメタデータのフォーマットのみがOGPで規定されていて、それ以外は既存のHTTPやHTMLの仕組みをそのまま利用しています。メタデータに関してはHTMLのヘッダに記述されているのでHTTPのHEADメソッドで取得でき、Webページ全体を取得しなくても済むようになっています。

OGPで必須とされているメタデータはog:titleog:typeog:imageog:urlの4つですが、og:site_nameog:descriptionもよく利用されます。具体的なHTMLのヘッダに埋め込まれたメタデータの例は以下のようになります。

<meta property="og:type" content="article">
<meta property="og:title" content="とにかくかっこいいブログの作り方">
<meta property="og:url" content="https://tonikaku-kakkoii-blog.com/articles/how_to_create_kakkoii_blog/index.html">
<meta property="og:site_name" content="tonikaku-kakkoii-blog.com">
<meta property="og:description" content="とにかくかっこいいブログサイトを作るにはどうすればいいのかお悩みの方も多くいると思います。本記事ではデザイン、技術、記事内容の3つの視点で解説します。">
<meta property="og:image" content="https://https://tonikaku-kakkoii-blog.com/ore_kakkoii.png">

favicon

「favicon」はWebサイトのシンボルとして表示される画像のことでブラウザのタブやブックマークで表示されます。もともとInternet Explorer 5で「お気に入り」に画像を表示するための技術で、「favicon」の由来は「favarite icon」だとされています。


 
faviconの画像形式には特に決まりがなくブラウザ依存ですが、伝統的にはICO形式です。ICO形式はWindowsのアイコン形式で、以下のように正方形の任意の画像サイズを複数格納できるようになっています2
 

 
Webサイトにfaviconを設定する伝統的な手法はWebサイトのルートディレクトリにfavicon.icoというファイル名でICO形式のファイルを配置することです。しかし近年のブラウザではそれ以外にも以下のようにHTMLのヘッダでfaviconを指定することもできます。

<link rel="icon" href="tonikaku-kakkoii-blog.com/favicon.ico" />

MIMEタイプを指定することによってgifやpngといった画像形式にも対応できます。

<link rel="icon" type="image/png" href="tonikaku-kakkoii-blog.com/favicon.png" />

favicon取得用API

faviconはこれまで説明してきたとおり画像形式も配置場所もばらばらなので、単純にブログカードに表示させることはできません。クライアント側のJavaScriptでもある程度はできるかもしれませんがICO形式等のマルチ画像のフォーマットがあると厳しいです。そこでブログカード用にfavicon取得用のAPIをサーバサイドで実装するのが一般的です。favicon取得用APIではfaviconを取得してブログカードの表示に適切は画像フォーマットとサイズに変換してクライアントに返却します。このようなAPIは自作することも公開されているサービスを利用することもできます。以下にfavicon取得用APIの例を掲載します3

ソーシャルカウント

ソーシャルカウントは一般的にSNSにおける「人気」を表す指標のことで、例えばはてなブックマークの数とかFacebookのいいねの数になります。ソーシャルカウントを取得する方法は、サービスによってそれぞれ異なります。具体例として以下にはてなブックマーク数を取得するAPIを掲載します。

ブログカードの実装

今回は以下のようなブログカードをサーバサイドJavaScript(Node.js)で実装してみたいと思います。

JavaScriptでHTMLを出力するイメージです。今回はgetTag関数を実装し、戻り値はPromiseとします。利用方法は以下のとおりです

getTag({url: "https://hinastory.github.io/cats-cats-cats/2019/12/29/visualize_ruby_development_by_file/"}).then(e => console.log(e))

HTMLの骨組み

まずはHTMLを出力するにあたって骨組みを考えます。最初は出力するイメージを再現できる素直な入れ子構造を考えます。ポイントはブログカードをクリックしたらリンク先に飛びたいのでリンクを示すaタグでなるべく広く囲むことです。次にスタイル(CSS)を当てることを考えて不足しているレイヤーがあれば調整します。最終的にできた骨組みは以下になりました。aタグと書かれた箇所以外は全てdivタグで、class属性の骨格を示しています。

  • hbc-blog-card
    • hbc-link-wrap
    • hbc-link(aタグ)
      • hbc-card
      • hbc-info
        • hbc-favicon
        • hbc-site-name
      • hbc-contents
        • hbc-thumbnail
        • hbc-text
        • hbc-title
        • hbc-url
        • hbc-description

JavaScriptにおける実装

実装は以下のとおりです。基本的には上記の骨格どうりにHTMLタグを組み立てているだけです。今回はソーシャルリンクの実装は行っていませんが、実装はそれほど難しくはないはずです。

blog_card.js
const util = require('hexo-util');
const ogs = require('open-graph-scraper'); // Open Graph Protocol解析用
const escapeHTML = require('escape-html');
const url = require('url');
const descriptionLength = 140;
const className = 'blog-card';
const faviconAPI = 'http://favicon.hatena.ne.jp/?url=$URL';

function getTag(options){
  return ogs(options)
    .then(function (result) {
      const ogp = result.data;

      const info = getInfo(options, ogp);
      const contents = getContents(options, ogp);

      const card = util.htmlTag('div', { class: 'hbc-card' }, info + contents, false);
      const link = util.htmlTag('a', { class: 'hbc-link', href: options.url, target: options.target, rel: options.rel }, card, false);
      const linkWrap = util.htmlTag('div', { class: 'hbc-link-wrap' }, link, false);
      const tag = util.htmlTag('div', { class: className }, linkWrap, false);
      return tag;
    })
    .catch(function (error) {
      console.log('error:', error);
      return '';
  });
}

function getInfo(options, ogp) {
  let name = '';
  const urlParsed = url.parse(options.url);

  // ogSiteNameがなかった場合にホスト名を表示
  if (ogp.hasOwnProperty('ogSiteName')) {
    name = ogp.ogSiteName;
  } else {
    name = urlParsed.hostname;
  }

  const siteName = util.htmlTag('div', { class: 'hbc-site-name' }, name);

  let api = faviconAPI.replace('$DOMAIN', encodeURIComponent(urlParsed.hostname));
  api = api.replace('$URL', options.url);
  const favicon = util.htmlTag('img', { class: 'hbc-favicon', src: api } , '');
  return util.htmlTag('div', { class: 'hbc-info' }, favicon + siteName, false);
}

function getContents(options, ogp) {
  let contents = '';
  let text = '';

  if (ogp.hasOwnProperty('ogImage')) {
    const image = util.htmlTag('img', { src: ogp.ogImage.url } , '');
    contents = util.htmlTag('div', { class: 'hbc-thumbnail' }, image, false);
  }

  text += util.htmlTag('div', { class: 'hbc-title' }, escapeHTML(ogp.ogTitle), false);
  text += util.htmlTag('div', { class: 'hbc-url' }, options.url, false);

  if (ogp.hasOwnProperty('ogDescription')) {
    const description = adjustLength(ogp.ogDescription);
    text += util.htmlTag('div', { class: 'hbc-description' }, description);
  }
  contents += util.htmlTag('div', { class: 'hbc-text' }, text, false);

  return util.htmlTag('div', { class: 'hbc-contents' },  contents, false);
}

// 内容が長い場合に切り詰める
function adjustLength(description) {
  if (description && description.length > descriptionLength) {
    description = description.slice(0, descriptionLength) + '';
  }
  return description;
}

// 実行したい場合は以下のようにする
// getTag({url: "https://hinastory.github.io/cats-cats-cats/2019/12/29/visualize_ruby_development_by_file/"}).then(e => console.log(e))

実行結果

上記のプログラムの実行にはnode.jsとnpmによるライブラリ(hexo-utilopen-graph-scraper)のインストールが必要ですが、興味がある方は実行してみてください。実行結果(HTML)は以下のとおりです。適当にスタイルを当てています。

See the Pen blog_card by hinastory (@hinastory) on CodePen.

まとめ

本記事ではブログカードで使われている技術として以下の3つの技術について解説を行い、JavaScriptによるブログカードの実装例を紹介しました。

  • Open Graph Protocol
  • favicon
  • ソーシャルカウント

本記事がブログカードを支える技術の理解の一助となれば幸いです。

参考文献


  1. リンクカードと呼ばれることもあります。 

  2. ここではICO形式の一般的な特徴のみ説明しており、フォーマットの詳細については割愛させて頂きます。 

  3. ここではfavicon取得用APIの説明用の参考例として掲載しています。おそらく非公式なのでご利用にはお気を付けください。 

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

Windows 10のChromeで文字認識(OCR)

Shape Detection APIのTextDetectorText Detection not implemented. というエラーが出たのでメモ。

【重要】Windows 10にEnglishの言語機能(光学式文字認識)をインストール

日本語のWindowsには、Englishの言語機能は入ってないので注意が必要。

設定を開く

スクリーンショット (6).png

時刻と言語を選択

スクリーンショット (7).png

言語タブを選択し、優先する言語を追加する

スクリーンショット (9).png

United Statesを選択し、次へ

スクリーンショット (12).png

光学式文字認識をインストール

言語パックとか音声認識とか手書き入力は必要ないです。
スクリーンショット (13).png

★再☆起★動☆

Englishの言語が追加されたことを確認して、Windowsを再起動。
スクリーンショット (16).png

これで、英語版のWindows.Media.Ocrが使えるようになりました。

ChromeのExperimental Web Platform featuresを有効化

chrome://flags/#enable-experimental-web-platform-featuresにアクセスし、Enabledに変更し、ChromeをRelaunch
スクリーンショット (3).png

画像からOCR

画像を選択すると自動でOCRを行います。

See the Pen Windows 10 Chrome OCR by John Doe (@04) on CodePen.

WebカメラからリアルタイムOCR

WebカメラからOCRを行います。リアルタイムでBoundingBoxが付きます。

[Violation] Feature policy violation: camera is not allowed in this document. で動かないので右上の EDIT ON CODEPEN を押して動かしてください。

See the Pen Windows 10 Chrome Realtime OCR by John Doe (@04) on CodePen.

FullHD、フルスクリーンでOCRするサンプルもあります。
https://codepen.io/04/pen/wvBRQYg

まとめ

  • 英数字や記号しか認識できませんが、Tesseract.jsよりも高速で精度が高いので追跡番号などを認識するのにおすすめです。
  • オフラインで動作するので、Google VisionやAmazon Textractが使えない環境にもおすすめです。画像をクラウドにアップすると時間がかかりますが、エッジでやれば、高速でお金もかかりません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

html2canvasを使ってVue.js のサイトを画像で切り取る

html2canvasを使いたい

今回はVue.jsで作ったhtmlをhtml2canvasで画像にしたいと思います

参考資料

【2019年度版】Windowsでhtmlを画像化する方法(html2canvasの使い方)
【資料2】html2canvasとVue.jsでつくるコラ画像ジェネレータ
【エラー用資料】Vue.jsの初歩的なミス

環境

Windows 10
Visual Studio Code: 1.40.1 (system setup)
Chrome: 76.0.3809.146
Node.js: 12.4.0
V8: 7.6.303.31-electron.0
OS: Windows_NT x64 10.0.18362

index.htmlを作成

基本コード


<!DOCTYPE html>
<html lang="ja" dir="ltr">

<head>
    <meta charset="utf-8">
    <title>"html2canvas" </title>

    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <!--Vue.jsの適用headに入れると全部に適応される-->
    <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.0.0-alpha.12/dist/html2canvas.min.js"></script>
    <!--html2canvasの適用headに入れると全部に適応される-->


    <style type="text/css">
        /* コラ画像用のCSS */
        #preview {
            overflow: auto;
            width: 100%;
        }

        /*
        #preview_inner {
            background: url(https://3.bp.blogspot.com/-cPqdLavQBXA/UZNyKhdm8RI/AAAAAAAASiM/NQy6g-muUK0/s800/syougatsu2_omijikuji2.png) no-repeat left bottom;
            background-size: 200px auto;
            position: relative;
            padding-bottom: 250px;
            width: 600px;
        }*/

        #baloon {
            position: relative;
            border: 4px solid #ff8888;
            background: #fff;
            border-radius: 8px;
            display: inline-block;
            font-weight: bold;
            font-size: 1.2rem;
            color: #444;
            padding: 10px;
            min-width: 200px;
            white-space: pre-wrap;
        }

        #baloon:before,
        #baloon:after {
            content: '';
            display: block;
            width: 0;
            height: 0;
            border-style: solid;
            border-width: 50px 15px 0 15px;
            border-color: #fff transparent transparent transparent;
            position: absolute;
            left: 20px;
            bottom: -50px;
        }

        #baloon:before {
            left: 16px;
            bottom: -60px;
            border-width: 58px 19px 0 19px;
            border-color: #ff8888 transparent transparent transparent;
        }

    </style>

</head>

<body>

    <div id="preview">
            <!--<div id="preview_inner">-->
                <div id="baloon">
                    <p>{{ message }}</p>
                    </div>

                <!--</div>-->
                <p>
                    <img width="200" src="https://3.bp.blogspot.com/-cPqdLavQBXA/UZNyKhdm8RI/AAAAAAAASiM/NQy6g-muUK0/s800/syougatsu2_omijikuji2.png">
                </p>
            <textarea class="form-control" v-model="message"></textarea>
    </div>

    <div>
    <button class="btn btn-primary btn-block" v-on:click="generate">画像を生成</button>
    </div>

    <script>
        var app = new Vue({
            el: '#preview',
            data: {
                message: ''
            },
            methods: {

            }
        })
    </script>

    <script>
    var generate = html2canvas(document.querySelector("#preview")).then(canvas => {
            document.body.appendChild(canvas)
        });
    </script>

</body>

</html>

HTML
にアクセス。

一部は画像に変換できたものの、
ターミナルとブラウザで検証をして見つかるエラーは消せたのですが
画像を含めて生成はできませんでした。
文字とかは画像化できるのですが、、、

image.png

あと、画像を生成を押す前からずっと出ています。
せっかく作ったボタンの意味!!
ここの原因はたぶんここ

    <script>
    var generate = html2canvas(document.querySelector("#preview")).then(canvas => {
            document.body.appendChild(canvas)
        });
    </script>

見るからにうまく組めていない怪しいコード。
ボタンを押した関数のgenerateで動いて欲しい、かつhtml2canvasを使いたい。
色々試して書いたのですが思いついたものはどれも駄目でした(´;ω;`)

あとはおみくじ画像がうまく画像化されない件。
こちらもstyleに埋め込んでいるから駄目なのかとimgタグでdivの中に持ってきたりしましたがどちらも駄目でした。

【エラー】Uncaught ReferenceError: Vue is not defined

これはそもそもHTMLとVue.jsの仕組みがきちんと分かっていない為に起きたエラーでした。
備忘録的に分かった事を書いておこうと思います。
下記コードの黄色線を引いてる部分を/bodyの直前にもってきていたのが失敗で、
読み込めていませんでした。

2CB828C3-033A-42DC-9D26-949822778374.jpg

完成品

2020-01-23_00h41_03.png

html2canvasとVue.jsは使えたものの目指していた形にはなりませんでした。
アドバイスありましたら随時募集中です。もしくは万が一少しでも参考になりましたら幸いです。
ありがとうございました。

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

俺的PWAの振り返り

はじめに

PWAに手を出したものの、手を出してからほったらかして1年近く。
ちょっと復習しておこうと思います。
理解が充分でないところはこれから深めていきます。
もし間違っていることがあればどんどん突っ込んでくださいw

拙作アプリの「LTタイマー」を題材に進めていこうかと思います。
LTタイマーを作ったのが1年以上前なのでもしかしたらやり方が古いかもしれないので、気づいた時点でアップデートしていきたい。
参考サイトのソースをベースとして、少し手を加えています。

参考

PWA: ServiceWorkerを使って、キャッシュをコントロールする(オフラインハンドリング)
MDN ウェブアプリマニフェスト
僕の考えた最強のService Workerキャッシュ戦略で爆速サービスを作った

PWAとは

PWAとはProgressive Web Appsの略です。
ネイティブアプリのような感じのWebアプリを作成できます。
あくまでネイティブアプリのような感じなので、ネイティブアプリにはできるけどPWAではできないこともあります。

PWAでアプリを作成・動作させるためには、PWAに対応したWebブラウザが必要になります。
PWAに対応していないブラウザでは、ただのWebサイトとして扱われるので心配はいりません。
また、localhostまたはHTTPSでないと、PWAは利用できません。

PWA自体は、ウェブアプリマニフェスト(manifest.json) + ServiceWorker + Casch API の組み合わせで成り立っています。

ServiceWorker

Service Workerはブラウザにインストールされ、バックグランドで常駐します。
まだやったことはありませんが、Service Workerでプッシュ通知を扱うことができるとのこと。
あと、ServiceWorkerはDOMにアクセスできません。

manifest.json

manifest.jsonはPWAの設定をJSON形式で記載したものです。
アプリの名前、表示方法、アイコンなどなどが設定できます。

Casch API

Casch APIをつかって、ローカルストレージに読み込んだコンテンツを保存したり、読み込んだりします。
ここではローカルストレージをメインで紹介していますが、実はローカルストレージのほかにSession Strage、IndexedDB、WebSQLなども扱えるとのことです、この辺も、調べないとね。

LTタイマーの構成

GitHubを見てもらうとわかりますが、こんな感じです。

image.png

ちなみに、GitHubの内容は古い場合がありますので注意してください。

manifest.jsonがマニフェスト
sw.jsがサービスワーカーの実装になります。

処理の概要

まずはServiceWorkerの登録処理になります。
登録処理はindex.htmlでおこなっています

index.html
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js')
    .then(function(registration){
        console.log('Service Worker install success.');

        registration.onupdatefound = function() {
            // 更新があると呼び出される
            console.log('Update : ServiceWorker');
            registration.update();
        }
    })
    .catch((error) => {
        // registration failed
        console.log('register faild : ', error);
    });
}

まず、ServiceWorkerに対応しているかどうかのチェックが必要になります。
それを最初のif文で行っています。
次に、ServiceWorkerで動かすJavaScriptファイルを登録します。

index.html
    navigator.serviceWorker.register('./sw.js')

ServiceWorkerに変更があった場合の対応として、registration.onupdatefoundメソッドを実装しています。

index.html
    registration.onupdatefound = function() {
        // 更新があると呼び出される
        console.log('Update : ServiceWorker');
        registration.update();
    }

sw.jsの内容です。
まず、ServiceWorkerではファイルのキャッシュを行います。
キャッシュするファイルの一覧を配列で宣言しています。
CACHE_NAMEとVERSIONを結合してるのは、ServiceWorkerの更新が発生した際に、キャッシュの更新を行うことを目的としているためです。その辺は、後で説明します。

sw.js
var CACHE_NAME = 'lttimer';
var VERSION="1.0.1"
var CACHE_FILE = [
     './index.html'
    ,'./css/DSEG7Classic-Regular.woff'
    ,'./css/PixelMplus10-Regular.woff'
    ,'./js/jquery-1.9.1.min.js'
    ,'./sound/Zihou01-mp3/Zihou01-1.mp3'
    ,'./sound/Zihou01-mp3/Zihou01-1.ogg'
    ,'./sound/silent.mp3'
    ,'./sound/silent.ogg'
];
const CACHE_KEYS = [
  CACHE_NAME + VERSION
];

次にServiceWorkerのインストールイベントでキャッシュへの登録処理を行います。
installイベントは1回しか呼び出されません。

sw.js
self.addEventListener('install', function(e) {
    e.waitUntil(
        caches.open(CACHE_NAME.then(function(cache) {
            return cache.addAll(CACHE_FILE);
        })
    );
});

installイベントが終わるとactivateイベントが呼び出されます。
acticateの中では、キャッシュのキーと一致しないキャッシュの削除処理をおこなっています。

sw.js
self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then(keys => {
            return Promise.all(
                keys.filter(key => {
                    return !CACHE_KEYS.includes(key);
                }).map(key => {
                    // 不要なキャッシュを削除
                    if(key.indexOf(CACHE_NAME) == 0){
                        console.log("ServiceWorker : " + key+ " remove");
                        return caches.delete(key);
                    }else{
                        console.log("ServiceWorker : " + key+ "no remove");
                        return true;
                    }
                })
            );
        })
    );
});

次にfetchイベントでリクエストを処理します。
ここでリクエストをも元にキャッシュから取り出したり、キャッシュにないファイルを登録します。
場合によっては古いキャッシュを削除し、新しいファイルをキャッシュします。
(ここもほぼコピペであまり理解していない・・・^^;)

fetchイベントの中はオンライン、オフラインで処理を分けます。
オンラインオフラインの切り分けは navigator.onLine でできるとのこと。

sw.js
self.addEventListener('fetch', function(event) {
  //ブラウザが回線に接続しているかをboolで返してくれる
  var online = navigator.onLine;
  if(online){
    //オンラインのときの制御
  }else{
    //オフラインのときの制御
  }
});

オンラインの処理はこんな感じ
まだ、処理を完全に理解しきっていないのですが、たぶんこのコメント通りでいいはず。

sw.js
    event.respondWith(
        caches.match(event.request).then(
            function (response) {
                if (response) {
                    // キャッシュを返す
                    return response;
                }
                return fetch(event.request).then(function(response){
                    // キャッシュにないので追加
                    // Responseはストリームなのでキャッシュなので複製
                    cloneResponse = response.clone();
                    if(!response || response.status != 200){
                        //正常に取得できなかったときにハンドリングしてもよい
                        console.log("ServiceWorker : request faild " + response.status);
                    }else{
                        //現行のキャッシュに追加
                        caches.open(CACHE_NAME + VERSION).then(function(cache){
                            cache.put(event.request, cloneResponse).then(function(){
                                //正常にキャッシュ追加できたときの処理(必要であれば)
                                console.log("casshed");
                            });
                        });
                    }
                    return response;
                }).catch(function(error) {
                    //デバッグ用
                    return console.log(error);
                });
            }
        )
    );

event.respondWith()の中で、matchメソッドでキャッシュの検索結果を処理します。
一致すればそれを返却し、一致しなければ、キャッシュに登録します。

オフラインの時はこんな感じ。

sw.js
    event.respondWith(
        caches.match(event.request).then(
            function(response) {
                // キャッシュがあったのでそのレスポンスを返す
                if (response) {
                    return response;
                }
                //オフラインでキャッシュもなかったパターン
                return caches.match("offline.html").then(function(responseNodata){
                    //適当な変数にオフラインのときに渡すリソースを入れて返却
                    //今回はoffline.htmlを返しています
                    return responseNodata;
                });
            }
        )
    );

offline.htmlを返却するようにしているけど、offline.htmlを用意していない・・・w
基本的にはオンラインの時と同じでmatchの結果で処理を行っています。

ここまでで最低限と思われる処理になります。

manifest

manifestファイルには、アプリケーションのアイコンや、名称などの情報を設定します。
設定項目が多くあるので、MDNのウェブアプリマニフェストのページを参考にするとよいと思います。

manifest.json
{
    "name": "LT Timer",
    "orientation": "landscape",
    "display": "standalone",
    "start_url": "./",
    "short_name": "LT Timer",
    "description": "LT Timer",
    "background_color": "#000020",
    "theme_color": "#000020",
    "icons": [
            {
                "src": "./img/icon_48.png",
                "type": "image/png",
                "sizes": "48x48"
            },
        {
            "src": "./img/icon_96.png",
            "type": "image/png",
            "sizes": "96x96"
        },
        {
            "src": "./img/icon_192.png",
            "type": "image/png",
            "sizes": "192x192"
        }
    ]
}

とりあえず、使っている設定の概要を

設定名 概要
name アプリケーション名
orientation アプリケーションの向き
display standalone
start_url アプリケーションの開始URL
short_name アプリケーションの短縮名
description アプリケーションの説明
background_color 背景色
theme_color アプリケーションのテーマ色
icons アプリケーションのアイコン

デバッグ

デバッグはChromeのデベロッパーツールを使うのが良いと思います。
SafariとかFirefoxとかは使わないのでよくわからないけど。

デベロッパーツールをですが、通常のWebの開発で利用する機能は当然ですが、PWAに関するところで重要なのが以下の2点。

  • 保存したコンテンツを削除
  • Service Workerの登録解除
  • オンライン・オフラインの切り替え

これができないとデバッグが大変。というか無理でしょう。
上記の内容は、デベロッパーツールのApplicationタブで確認できます。

また、console.logでログを出力ができます。
というか、デベロッパーツールでApplicationタブのサービスワーカーを見るとエラーがカウントされているけど、確認ができません(自分だけ?)
そのため、ログに出さないとサービスワーカーでエラーが発生しても、エラーがでいるかの把握が困難になります。

最後に

この内容はYoutubeのチャンネルや日本Androidの会 浜松支部の2月の定例会でも扱いたいなぁと思っています。

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