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

JavaScriptの基本文法

はじめに

この記事は、2019/11/1からプログラミング学習を始めた学生がプロのプログラマーになるまでのアウトプットする為の書き込みの場である。

JavaScriptを呼び出そう

HTMLのコード中にJavaScriptのコードを記入しJavaScriptを呼び出す

 <script src="script.js"></script>

基本文法

  • 出力 console.log
  • 変数 変数の前にvarを入れる
  • 変数 変数を宣言する場合、前にletとconstを入れる必要があります。1
    • letは、後で書き換えることのできる変数宣言
    • constは、後で書き換えることのできない変数宣言
  • 条件分岐(ES6バージョン)
    • 条件式は()でくくる必要がある
    • Ifの終わりに{}で囲む必要がある
    • rubyでは、elsifであるが、else ifと記入する
  • 条件分岐(ES5バージョン)
    • 宣言する変数の前にvarをつける!!
  • 関数
    • rubyの場合、関数の前にdefがつきますが、jsの場合、functionと記入する

今日の感想 明日から

インプットをする機関を取りすぎてしまいアウトプットする時間が作る事ができない
今回のように今日インプットした、もの全てをアウトプットする事ができない
明日からは、今は、インプットする事に専念し、アウトプットについては簡単に端的にまとめたアウトプットをしていこうと思います

終わりに

もっとこうした方が良いよなどご指摘頂けると幸いです

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

HTML フォームの部品をまとめてみる(JavaScriptでの関連処理も)

概要

Jsの学習を進める中で、フォームの部品に関しても学習する機会があったので、この際軽くまとめてみようと思う。本当に軽くね。

テキスト

text.html
<input type="text" name="text_name" value="text1" id="text" />
text.js
//テキストの値を取得
$("#text").val();
//テキストのvalueの設定
$("#text").val("設定値");

チェックボックス

check.html
<input type="checkbox" name="check" value="check1" id="check1" checked />
<input type="checkbox" name="check" value="check2" id="check2" />
  • 複数の部品のname属性に同じ値を設定すると、1つのグループを作れる。
  • 「checked」を設定することで最初からチェックされている状態になる。
check.js
//チェックされているかを取得
var check1 = $("#check1").prop("checked");
//チェックを設定
var check2 = $("#check2").prop("checked", check1);
//値を取得するだけ
$("#check1").val();
//同じグループのチェックボックスをまとめて扱いたい場合のセレクタの書き方
//チェックされている値を取得
//チェックされている値がなければ、「undefined」を取得する
$("input[name=check]:checked").val();

ラジオボタン

radio.html
<input type="radio" name="radio_name" value="radioBtn1" id="radio1" />
<input type="radio" name="radio_name" value="radioBtn2" id="radio2" />
  • 複数の部品のname属性に同じ値を設定すると、1つのグループを作れる。
  • Jsについてはチェックボックスに書いてあるもので十分なので割愛。

セレクトボックス

select.html
<select name="select_name" id="list" multiple size=3>
  <option value="リスト1" selected>リスト1</option>
  <option value="リスト2">リスト2</option>
  <option value="リスト3">リスト3</option>
  <option value="リスト4">リスト4</option>
  <option value="リスト5">リスト5</option>
</select>

-「selected」を設定することで最初から選択されている状態になる。
- multiple size属性では同時選択できる数の最大値を設定できる

select.js
//選択されている値の取得
//選択されている値がなければ「null」が取得される
$("#list").val();

テキストエリア

textarea.html
<textarea name="textarea_name" cols="20" rows="3" id="textarea"></textarea>
  • rows属性では入力欄の高さを行数で指定できる
  • cols属性では入力欄の幅を文字数で指定できる

SUBMITボタン

submit.html
<button type="submit" id="submit">提出</button>
submit.js
//submitボタンにイベントを追加
$("#submit").submit(function() {});

おまけ

alert.js
//アラートを出力
alert("アラート");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エンジニアスタンプラリー~フロントエンド編#16

企画主旨

Frontend Developer Roadmapをひたすら巡回する企画
詳しくはこちら

今回の実施内容

モバイルアプリ

Desktop Applications

Electron

Getting Startedを参考に導入及び環境構築を実施。
今まで作成してきたデザインや機能をそのまま移植する。

App.tsx
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Form from './Form'
import List from './List'
import Bottom from './Bottom'

type P = {

}

type S = {
  page: string,
  front_skill: string[],
  back_skill: string[],
  inputText: string
}
export default class App extends React.Component<P, S> {
  constructor(props) {
    super(props)
    this.state = {
      page: 'front',
      front_skill: [],
      back_skill: [],
      inputText: ''
    }
    this._onPushButton = this._onPushButton.bind(this)
    this._onChangeText = this._onChangeText.bind(this)
    this._onChangePage = this._onChangePage.bind(this)
  }

  _onPushButton(): void {
    let { inputText, page, front_skill, back_skill } = this.state
    if (inputText !== '') {
      if (page === 'front'){
        front_skill.push(inputText)
      } else {
        back_skill.push(inputText)
      }
      inputText = ''
      this.setState({ front_skill, back_skill, inputText })
    }
  }

  _onChangeText(inputText: string): void {
    this.setState({ inputText })
  }

  _onChangePage(page: string): void {
    this.setState({ page })
  }

  render() {
    return (
      <View style={styles.container}>
        <View style={styles.form}>
          <Form inputText={this.state.inputText} _onPushButton={this._onPushButton} _onChangeText={this._onChangeText} />
        </View>
        <View style={styles.list}>
          <List skills={this.state.page === 'front' ? this.state.front_skill : this.state.back_skill} />
        </View>
        <View style={styles.bottom}>
          <Bottom _onChangePage={this._onChangePage} />
        </View>
      </View>
    )
  }
}

成果物

あまりコードの流用ができず、同じJavaScriptでも全く別物の印象。
https://github.com/tonchan1216/WDR-frontend-reactNative

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

ChromeならiPadでコンソール出力を確認できる

Chrome for iOSがv.74からjavascriptのコンソールのログ出力に対応していました。

使い方

通常のChromeのように開発者ツールを開くのではなく、アドレスバーに chrome://inspect と入力してアクセスすると以下のページが表示されます。
37CD8AB7-1A3B-4188-BBCB-CE4B376BC921.jpeg
"ログ記録を開始"ボタンを押して、他のタブでページを開くとこのページにログが記載されていきます。
06D60A03-632A-4D6B-B1EA-50561814002B.jpeg
便利!

開発者ツールが使えるようになったわけではありませんが、ログ出力が見れるようになっただけでも大助かりです。

参考
- https://blog.chromium.org/2019/03/debugging-websites-in-chrome-for-ios.html
- https://www.softantenna.com/wp/software/chrome-for-ios-inspect/

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

Axiosの前処理でAPIレスポンスのスネークケースをキャメルケースに変換する

はじめに

サーバサイドからのAPIレスポンスがsome_idのようにスネークケースで定義されていると、JS側で毎回キャメルケースに変換することになりますよね。

{
  some_id: 1,
  some_name: "name",
}

今回はそんな変換処理をAxiosの共通処理でまとめてみました。

レスポンスをキャメルケースに変換する

import { camelCase, snakeCase } from 'change-case';

const isObject = (target: any): boolean => Object.prototype.toString.call(target).slice(8, -1)
  .toLowerCase() === 'object';

const convertSnakeToCamel = (target: any): void => {
  if (Array.isArray(target)) {
    target.forEach((t) => convertSnakeToCamel(t));
  }
  if (isObject(target)) {
    Object.keys(target).forEach((key) => {
      if (isObject(target[key]) || Array.isArray(target[key])) {
        convertSnakeToCamel(target[key]);
      }
      // スネークケースをキャメルケースに変換する
      if (key.includes('_')) {
        target[camelCase(key)] = target[key];
        delete target[key];
      }
    });
  }
};

キャメル変換する部分はchange-case というライブラリを入れましたが、そこまで複雑ではないので自作でもよさそう

参考:javascriptでキャメルケースとスネークケースの相互変換

Axiosに組み込む

import axios, { AxiosInstance } from 'axios';

const Axios: AxiosInstance = axios.create();

Axios.interceptors.request.use((request) => {
  // リクエストの共通処理
  return request;
});

Axios.interceptors.response.use((response) => {
  if (response.data) {
    convertSnakeToCamel(response.data);
  }

  return response;
});

export default Axios;

interceptorsを使うことでできました。interceptorsを使いこなせば、他にも、リクエストに共通のヘッダを毎回差し込んだり、レスポンスでエラー処理をしたりと、何かと便利にできそうです。

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

Phina.jsテキストのスクロール実装について

  • Phina.jsのテキストスクロールについて

    • Phina.jsを使用してアプリ開発をしていたところテキストのスクロールが必要な場面に遭遇したのですが
      ネットで調べてもそれらしい記事が少なかったこととライブラリのコードを調べたときに実装途中のコードが 見つかりどうやら機能自体はあるようですがまだ未実装なようなのでそれを自分用に改良した
      コードを役に立つかどうかは分かりませんが掲載しようと思います。(勘違いだったらすみません)
      テキストのスクロール自体はLabelAreaクラスの要素scrollYのパラメータをいじることで可能なようです。

    以下コード

    //シーンクラス内で  
    this.group = DisplayElement().addChildTo(this);  
    var self = this;  
    this.labelArea = LabelArea({  
    text: "スクロール",  
    width: 580,  
    height: 320,  
    x:350,  
    y:780,  
    fill: "white",  
    stroke: null,  
    fontSize: 30,  
    }).addChildTo(this.group);  
        
    this.labelArea.setInteractive(true);  
    var physical = phina.accessory.Physical();  
    physical.friction = 0.8;  
    var lastForce = 0;  
    var lastMove = 0;  
    this.labelArea.on('pointstart', function(e){  
    lastForce = physical.velocity.y;  
    lastMove = 0;  
    physical.force(0, 0);  
    });  
    this.labelArea.on('pointmove', function(e){  
    var p = e.pointer.deltaPosition;  
    lastMove = p.y;  
    self.labelArea.scrollY -= 3*lastMove;  
    });  
    this.labelArea.on('pointend', function(e){  
    physical.force(0, lastForce + lastMove);  
    });  
    window.onmousewheel = function(e){ //chrome限定です  
        self.labelArea.scrollY -= 0.9*e.wheelDelta ;  
    }  
    

     
     
     

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

vue2-leafletでGeoJsonデータを表示・操作する

はじめに

ウェブ上で簡単に地図を表示する人気ライブラリ「Leaflet.js」をVue.js環境でお手軽に使えるラッパーライブラリ「vue2-leaflet」を5日くらい色々いじって得た知見を本記事にまとめます。いじった結果のゴール地点は以下の画像のようなウェブアプリケーションです。①地図エリアに.geojsonファイルをドラッグドロップすると地図上に地物を表示、②読み込んだGeoJsonレイヤを一覧表示、③地物をクリックするとその地物の属性をすべて表示、という以上の三機能をVue.jsの特性を活かし実装しました。本記事ではvue2-leafletの導入・実装と、機能①に焦点を絞って解説したいと思います。
スクリーンショット 2019-11-27 19.58.33.png

環境

  • npm 6.12.0
  • @vue/cli 4.0.5
  • leaflet 1.6.0

- vue2-leaflet 2.2.1

「vue cli」環境で開発しました。「vue cli」環境構築については本記事では掲載しません(以下の記事が詳しいです)。
Vue.js #001 – Vue CLI 3で環境構築

導入

npm install leaflet vue2-leaflet

インストール後、main.jsにてcssを読み込ませます

main.js
import Vue from 'vue'
import App from './App.vue'

//ここから
import { Icon }  from 'leaflet'
import 'leaflet/dist/leaflet.css'
//ここまで

// this part resolve an issue where the markers would not appear
delete Icon.Default.prototype._getIconUrl;

Icon.Default.mergeOptions({
    iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
    iconUrl: require('leaflet/dist/images/marker-icon.png'),
    shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

以上で導入は完了です。

実装

すべて単一ファイルコンポーネントとして記載しています。

script

<script>
    import {
        LMap,
        LTileLayer,
        LControlLayers,
        LControlScale,
        LGeoJson,
    } from 'vue2-leaflet';

    export default {
        name: 'MapPane',
        components: {
            LMap,
            LTileLayer,
            LControlLayers,
            LControlScale,
            LGeoJson,
        },
        data() {
            return {
                center: [38, 140],
                zoom:5,
                options: {
                    onEachFeature: function(feature, layer) {
                        layer.options.smoothFactor = 2;
                    }
                },
                geojson: null,
            }
        },
</script>

template

<template>
    <div class="mapPane"
        @dragover.prevent="dragover"
        @drop.prevent="drop"
        >
        <l-map
            :zoom="zoom"
            :center="center"
            :preferCanvas="true"
        >

            <l-control-layers
                position="topright"
                :collapsed="false"
            ></l-control-layers>
            <l-control-scale
                position="bottomleft"
                :imperial="false"
                :metric="true"
            ></l-control-scale>

            <l-tile-layer
                name="MIERUNE MONO"
                visible="true"
                url="https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png"
                attribution="Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a 
                href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
                layer-type="base"
            ></l-tile-layer>
            <l-geo-json
                :geojson="geojson"
                :options="options"
                :options-style="styleFunction"
                @click="onFeatureClick"
            ></l-geo-json>
        </l-map>
    </div>
</template>

長いので分解してみましょう。
まずLeafletマップ本体はl-mapです。l-mapの内側に、必要なUIパーツや表示したいレイヤーを記述します。

<l-map
    :zoom="zoom"
    :center="center"
    :preferCanvas="true"
>
<!-- ここにタイルレイヤーだとかコントロールだとかを追加する -->
</l-map>

素のl-mapだけだと背景地図すら表示されません。なのでまずタイルレイヤーを追加してみましょう。タイルレイヤーはl-tile-layerをl-map内に記述する事で追加されます。

<l-tile-layer
    name="MIERUNE MONO"
    visible="true"
    url="https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png"
    attribution="Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a 
    href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
    layer-type="base"
></l-tile-layer>

このとおり、l-map内に各種コンポーネントを追加記述して、望む機能をもつ地図をつくれる訳ですね。おなじみのレイヤーコントロール(レイヤー一覧、L.control.layers)は、前述の例のとおりl-control-layersで実装できたりと、基本的な機能をvueの記法で実装出来ます。そういったビルトインのUIパーツなどはおそらく実装には困らないと思いますので、本記事ではGeoJsonレイヤーの取扱いについて掘り下げていきたいと思います。

GeoJsonレイヤーの取扱

実装

<l-geo-json
    :geojson="geojson"
    :options="options"
    :options-style="styleFunction"
    @click="onFeatureClick"
></l-geo-json>

l-geo-jsonをl-map内に追加します。v-bindでgeojsonオブジェクトを渡してやる必要があります。ここで、このgeojsonはリアクティブです。つまりdata内のgeojsonの変更がマップに即時反映されます。vue2-leafletの前にvue-mapboxで遊んでいて、同様にgeojsonレイヤーのコンポーネントはあるのですが、リアクティブではありませんでした。この一点だけでもLeafletを優先して使う価値があると思います(GeoJsonビューア愛好家として)。

という訳でまずは.geojsonファイルのドラッグドロップ機能を実装しましょう。

<div class="mapPane"
    @dragover.prevent="dragover"
    @drop.prevent="drop"
    >
    <!-- l-mapなどなど -->
</div>

vueでドラッグドロップイベントを実装する場合はdragoverとdropをv-onで書きます。ここではドロップ時にdropというメソッドを実行せよ、という意味になります。.preventはブラウザの基本機能の実行を防ぐ構文です(例:ファイルをドロップすると、ブラウザ自体がそのファイルを開こうとするため)。さてここで、ドロップイベントだけ監視したいのだから、dragoverは不要ではないか?と考えると思います。しかしながらそれでは動作しません。おそらくdragover時にブラウザ処理が先行してしまうから(.preventが走らないから)だと思います。

さて、dropメソッドは、script内のmethodsにて宣言します。

methods: {
    drop: function(event) {
        let fileList = event.dataTransfer.files;
        let vm = this
        for ( let i = 0; i < fileList.length; i++ ) {
            let reader=new FileReader()
            reader.onload=function(e){
                let geojson = JSON.parse(reader.result)
                vm.geojson = geojson
            }
            reader.readAsText(fileList[i])
        }
    },
}

drop内の無名関数の引数eventにはドロップされたファイルの情報などが含まれています。File APIにより、ドロップされた.geojsonファイルからgeojson形式のオブジェクトを取得します。File APIについての解説はここでは省きます。
さて、ここで

let vm = this

この文の意味ですが、本当ならfor文内でもvueコンポーネントをthisで呼び出したい訳ですが、スコープが(良い言葉が思いつきませんがイメージ的には)一段深くなっており、thisで参照出来ません。そこで、thisでコンポーネントを参照出来るうちにvmという変数で保持している訳です。
さて、FileAPIでの読み込みが完了すると

let geojson = JSON.parse(reader.result)
vm.geojson = geojson

このとおり、vueコンポーネント内の、data内の、geojsonに、たった今File APIで取得したGeoJson型オブジェクトを突っ込みます。するとl-geo-jsonのgeojsonはリアクティブなので地図に地物が追加されます。

地物ごとの処理(onEachFeature)

l-geo-jsonのoptionsは、その他のコンポーネントと異なり、v-bindで:optionsに、オブジェクトをまとめて渡してやらなければなりません。

<!-- l-geo-json内 -->
:options="options"
//data()内
options: {
    onEachFeature: function(feature, layer) {
        layer.options.smoothFactor = 2;
    }
},

この例では、LeafletにおけるL.GeoJSONでおなじみのonEachFeature()を設定しています。地物ごとに個別の処理を行える関数です。ここでは、各地物の描画を簡素化しています。

まとめ

とてもとても長くなってしまいましたが、vue2-leafletの使い方を色々まとめました。どうやら私は、MapboxでもなんでもGISフレームワークの勉強の際は、とりあえず手持ちのGeoJsonやらを表示させるまでをチュートリアルと考えているフシがあります。vue.jsは使い始めですが、すげぇ便利だなって…。ここ数ヶ月はコードを書くばかりで知識のアウトプットもとい備忘録の作成を怠っていたため、来たるアドベントカレンダーへ向け、溜まっている下書きを清書していきたいです。

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

vue2-leafletの使い方、GeoJsonデータの表示・操作など

はじめに

ウェブ上で簡単に地図を表示する人気ライブラリ「Leaflet.js」をVue.js環境でお手軽に使えるラッパーライブラリ「vue2-leaflet」を5日くらい色々いじって得た知見を本記事にまとめます。いじった結果のゴール地点は以下の画像のようなウェブアプリケーションです。①地図エリアに.geojsonファイルをドラッグドロップすると地図上に地物を表示、②読み込んだGeoJsonレイヤを一覧表示、③地物をクリックするとその地物の属性をすべて表示、という以上の三機能をVue.jsの特性を活かし実装しました。本記事ではvue2-leafletの導入・実装と、機能①に焦点を絞って解説したいと思います。
スクリーンショット 2019-11-27 19.58.33.png

環境

  • npm 6.12.0
  • @vue/cli 4.0.5
  • leaflet 1.6.0

- vue2-leaflet 2.2.1

「vue cli」環境で開発しました。「vue cli」環境構築については本記事では掲載しません(以下の記事が詳しいです)。
Vue.js #001 – Vue CLI 3で環境構築

導入

npm install leaflet vue2-leaflet

インストール後、main.jsにてcssを読み込ませます

main.js
import Vue from 'vue'
import App from './App.vue'

//ここから
import { Icon }  from 'leaflet'
import 'leaflet/dist/leaflet.css'
//ここまで

// this part resolve an issue where the markers would not appear
delete Icon.Default.prototype._getIconUrl;

Icon.Default.mergeOptions({
    iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
    iconUrl: require('leaflet/dist/images/marker-icon.png'),
    shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

以上で導入は完了です。

実装

すべて単一ファイルコンポーネントとして記載しています。

script

<script>
    import {
        LMap,
        LTileLayer,
        LControlLayers,
        LControlScale,
        LGeoJson,
    } from 'vue2-leaflet';

    export default {
        name: 'MapPane',
        components: {
            LMap,
            LTileLayer,
            LControlLayers,
            LControlScale,
            LGeoJson,
        },
        data() {
            return {
                center: [38, 140],
                zoom:5,
                options: {
                    onEachFeature: function(feature, layer) {
                        layer.options.smoothFactor = 2;
                    }
                },
                geojson: null,
            }
        },
</script>

template

<template>
    <div class="mapPane"
        @dragover.prevent="dragover"
        @drop.prevent="drop"
        >
        <l-map
            :zoom="zoom"
            :center="center"
            :preferCanvas="true"
        >

            <l-control-layers
                position="topright"
                :collapsed="false"
            ></l-control-layers>
            <l-control-scale
                position="bottomleft"
                :imperial="false"
                :metric="true"
            ></l-control-scale>

            <l-tile-layer
                name="MIERUNE MONO"
                visible="true"
                url="https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png"
                attribution="Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a 
                href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
                layer-type="base"
            ></l-tile-layer>
            <l-geo-json
                :geojson="geojson"
                :options="options"
                :options-style="styleFunction"
                @click="onFeatureClick"
            ></l-geo-json>
        </l-map>
    </div>
</template>

長いので分解してみましょう。
まずLeafletマップ本体はl-mapです。l-mapの内側に、必要なUIパーツや表示したいレイヤーを記述します。

<l-map
    :zoom="zoom"
    :center="center"
    :preferCanvas="true"
>
<!-- ここにタイルレイヤーだとかコントロールだとかを追加する -->
</l-map>

素のl-mapだけだと背景地図すら表示されません。なのでまずタイルレイヤーを追加してみましょう。タイルレイヤーはl-tile-layerをl-map内に記述する事で追加されます。

<l-tile-layer
    name="MIERUNE MONO"
    visible="true"
    url="https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png"
    attribution="Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a 
    href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
    layer-type="base"
></l-tile-layer>

このとおり、l-map内に各種コンポーネントを追加記述して、望む機能をもつ地図をつくれる訳ですね。おなじみのレイヤーコントロール(レイヤー一覧、L.control.layers)は、前述の例のとおりl-control-layersで実装できたりと、基本的な機能をvueの記法で実装出来ます。そういったビルトインのUIパーツなどはおそらく実装には困らないと思いますので、本記事ではGeoJsonレイヤーの取扱いについて掘り下げていきたいと思います。

GeoJsonレイヤーの取扱

実装

<l-geo-json
    :geojson="geojson"
    :options="options"
    :options-style="styleFunction"
    @click="onFeatureClick"
></l-geo-json>

l-geo-jsonをl-map内に追加します。v-bindでgeojsonオブジェクトを渡してやる必要があります。ここで、このgeojsonはリアクティブです。つまりdata内のgeojsonの変更がマップに即時反映されます。vue2-leafletの前にvue-mapboxで遊んでいて、同様にgeojsonレイヤーのコンポーネントはあるのですが、リアクティブではありませんでした。この一点だけでもLeafletを優先して使う価値があると思います(GeoJsonビューア愛好家として)。

という訳でまずは.geojsonファイルのドラッグドロップ機能を実装しましょう。

<div class="mapPane"
    @dragover.prevent="dragover"
    @drop.prevent="drop"
    >
    <!-- l-mapなどなど -->
</div>

vueでドラッグドロップイベントを実装する場合はdragoverとdropをv-onで書きます。ここではドロップ時にdropというメソッドを実行せよ、という意味になります。.preventはブラウザの基本機能の実行を防ぐ構文です(例:ファイルをドロップすると、ブラウザ自体がそのファイルを開こうとするため)。さてここで、ドロップイベントだけ監視したいのだから、dragoverは不要ではないか?と考えると思います。しかしながらそれでは動作しません。おそらくdragover時にブラウザ処理が先行してしまうから(.preventが走らないから)だと思います。

さて、dropメソッドは、script内のmethodsにて宣言します。

methods: {
    drop: function(event) {
        let fileList = event.dataTransfer.files;
        let vm = this
        for ( let i = 0; i < fileList.length; i++ ) {
            let reader=new FileReader()
            reader.onload=function(e){
                let geojson = JSON.parse(reader.result)
                vm.geojson = geojson
            }
            reader.readAsText(fileList[i])
        }
    },
}

drop内の無名関数の引数eventにはドロップされたファイルの情報などが含まれています。File APIにより、ドロップされた.geojsonファイルからgeojson形式のオブジェクトを取得します。File APIについての解説はここでは省きます。
さて、ここで

let vm = this

この文の意味ですが、本当ならfor文内でもvueコンポーネントをthisで呼び出したい訳ですが、スコープが(良い言葉が思いつきませんがイメージ的には)一段深くなっており、thisで参照出来ません。そこで、thisでコンポーネントを参照出来るうちにvmという変数で保持している訳です。
さて、FileAPIでの読み込みが完了すると

let geojson = JSON.parse(reader.result)
vm.geojson = geojson

このとおり、vueコンポーネント内の、data内の、geojsonに、たった今File APIで取得したGeoJson型オブジェクトを突っ込みます。するとl-geo-jsonのgeojsonはリアクティブなので地図に地物が追加されます。

地物ごとの処理(onEachFeature)

l-geo-jsonのoptionsは、その他のコンポーネントと異なり、v-bindで:optionsに、オブジェクトをまとめて渡してやらなければなりません。

<!-- l-geo-json内 -->
:options="options"
//data()内
options: {
    onEachFeature: function(feature, layer) {
        layer.options.smoothFactor = 2;
    }
},

この例では、LeafletにおけるL.GeoJSONでおなじみのonEachFeature()を設定しています。地物ごとに個別の処理を行える関数です。ここでは、各地物の描画を簡素化しています。

参考

公式ドキュメントですが、あまり詳しい事は書いてありません。あんまり複雑な事しないなら早いか。

ソースですが、ここにexampleが多数ありとてもかなり非常に参考になります。

まとめ

とてもとても長くなってしまいましたが、vue2-leafletの使い方を色々まとめました。どうやら私は、MapboxでもなんでもGISフレームワークの勉強の際は、とりあえず手持ちのGeoJsonやらを表示させるまでをチュートリアルと考えているフシがあります。vue.jsは使い始めですが、すげぇ便利だなって…。ここ数ヶ月はコードを書くばかりで知識のアウトプットもとい備忘録の作成を怠っていたため、来たるアドベントカレンダーへ向け、溜まっている下書きを清書していきたいです。

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

【初心者チャレンジ】BMIを計算する Herokuで実装

axios と javascript を使ってサイトをHerokuで実装

今週はHerokuを使ってサイトの実装をしてみたいと思います。

参考資料

【資料1】【喉頭がんの治療プロトコルをNode.jsでVueに表示しHerokuにデプロイ

環境

Node.js v10.16.3
Windows 10 pro
Visual Studio Code v1.39.1

概要

image.png

フォルダを作成し、中に >node_modules:node.jsのデータが入っているフォルダ
            >public:htmlデータを入れるフォルダ
             >index.html:サイトを構成する静的ファイル
            >index.js:作成したpublicの静的ファイルをexpressで表示させるコード
            >gitignore:herokuで実装する時に不要なデータを送らないよう指定するファイル
            >package-lock.json:npm init -yで作成される
            >package.json:npm init -yで作成される
             インストールしたライブラリデータ等のパッケージが登録されている
            >Procfile:Herokuを起動するのに必要なファイル

上記のファイルを作成し、Herokuで実装。
なんだかフォルダ名が楽天となっているのは本当は別のものを実装しようとしていて心折れた形跡です。

今回のコードを起動必要なライブラリは

npm init -y
npm i body-parser express                   

をそれぞれターミナルに入力し、インストール。

index.html

 <!DOCTYPE html>
<html>

<head>
    <title>Step 01</title>
    <script src="https://unpkg.com/vue"></script>
</head>

<body>

    <h1>あなたのBMI</h1>


    <script>
        // 体重の数値を得る
        let weight;
        weight = prompt(`BMIを測定します。まずはあなたの体重(kg)を入力して下さい`);
        // 身長の数値を得る
        let height;
        height = prompt(`BMIを測定します。次にあなたの身長(m)を入力して下さい`);
        //体重と身長からBMIを計算して、警告ダイアログに表示する
        let bmi = weight / (height * height);
        let message = `あなたのBMIは「` + bmi + `」です。`;
        alert(message);

        console.log(message);

    </script>
</body>

</html>

gitignore

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# next.js build output
.next

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

Procfile

web: node index.js

こちら、Procfileをindex.jsと同じフォルダに置かなければHerokuをうまく使えないようですので、作成。

Herokuの実行

あとはHerokuを実行して繋げる。

完成

image.png

image.png

image.png

リアルな数字はみっともないので適当な数字を、、、
身長も体重も盛り盛りです。
良ければ一度自分のBMIを試してみてくださいw

完成

、、、と見せかけて。

実は先程しれっと飛ばしたherokuの実行、出来ておりません。
既にばれていたかもしれませんが
image.png
しっかりローカルで起動しております。
エラーばっかりで諦めて後まわしていたのですがひとつうまく行ったので自白を。。。

まずherokuの起動にあたって次を順番にターミナルに入力。

git init
git add --a
git commit -m "commit"
heroku create s191127-sample
//heroku create 自分のアプリ名
git push heroku master

①~⑤の手順で順番に入力すればうまくURLを取得できるはずが④でエラー。
コミットできず。。。

Creating ⬢ s191127-sample... !
! You've reached the limit of 5 apps for unverified accounts. Delete some apps or add a credit card to verify your account.

英語は読めん、、、が何やらクレジットカード的なサムシング。。。
色々調べると
【heroku create のエラー】
なんと無料herokuでは5つまでしか使えないらしい!!
そもそも5つもアプリ作った覚えない…と思いつつ疑いながらもリンクサイトを参考に下記をターミナルに入れて試してみると

heroku apps
//自分の全アプリが表示される

作った覚えのないアプリがきっちり5つ表示されました(笑)

heroku destroy --app 消したいアプリ名

もう一度しつこく聞いてくるので
消したいアプリ名だけを再入力し削除!

改めて④を再実行!
image.png
⑤も再実行!!
image.png

やったね!!
本当に完成!!!

image.png
https://s191127-sample.herokuapp.com

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

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

知ってるとかっこよくなれるJS小技集2019

はじめに

コードレビューでさらっと
「こんな書き方もできますよ」って言ってくれる先輩ってかっこいいですよね。
いつかそんな先輩になれるように、メモ化しておこうという試みです。クリスマスのお供にどうぞ。

記法

変数名が同じなら省略できる

const params = {
   id: id
   name: name
}
const params = {
   id,
   name
}

{}で分割代入

似てるものでこういう省略系もあります。分割代入と言うらしいです。

const id = response.id;
const {id} = response;

複数もOK

const {id, name} = response;

実務でよくみる使い方だと、Reactでpropsから値取り出すときとかでしょうか。

const {width, height} = props;
return (
  <div style={{ width, height }} />
)

スプレッド演算子...で展開できる

const hoge = {
    id: fuga.id,
    name: fuga.name,
    type: fuga.type
}
const hoge = {
    ...fuga
}

展開してさらに追加してもOK

const hoge = {
    ...fuga,
    date: Date.now()
}

実例だと、ReduxでStateを一部更新するときなんかに使えますね

  [HogeAction.SOMETHING]: (state: IHogeState, action: any): IHogeState => ({
    ...state,
    fuga: action.payload
  })

オブジェクトをコピーするためにも使えますが、Object.assign同様ディープコピーではないので注意です。

const original = {
   id: 1,
   obj: {
      name: "shwan"
   }
};

const copy = { ...original };
console.log(copy.obj.name); // "shwan"
original.obj.name = "changed";
console.log(copy.obj.name); // "changed"

オブジェクトのディープコピー技だとこんなのもありますが、undefindを扱えない罠があったりするので、できるだけ避ける人生を歩みたいですね。

const copy = JSON.parse(JSON.stringify(original));

スプレッド演算子に話をもどすと、引数でも展開できたりします。使う場面はあまりなさそうですが。

doSomething(fuga.id, fuga.name, fuga.type);
doSomething(...fuga);

条件分岐

論理演算子 || で条件分岐

let name;
if (response.name) {
    name = response.name
} else {
    name = 'Guest';
}
const name = response.name || 'Guest';

nullかもしれない配列を空配列にするときによく使います

response.names.forEach() // response.namesがnullやundefindだとエラー
const arr = response.names || [];
arr.forEach()

論理演算子 && で条件分岐

if (isGuest) {
    doSomething();
}
isGuest && doSomething();

この記法、 if( A && B ) if( A || B ) なときと違う動きをしてるようにも見えますが
&&左が偽なら左の値を、真なら右の値を返す
||左が真なら左の値を、偽なら右の値を返す
という共通の仕組みです。

const name = response.name || 'Guest';

の例ではresponse.name'shwan'などの文字列であれば左が真とみなせるので、左の値が返り、''nullであれば、偽なのでGuestが返ります。

キャスト

!! でのbooleanキャスト

const isSelected = this.state.type ? true : false;
const isSelected = !!this.state.type;

この記法も!を2つつなげると if(!isSelected) なときと違う動きをしているようにみえますが、
!値を真偽値とみなしたうえで真偽を反転する という動きでどちらも共通です。

+ でのnumberキャスト

const id = '1'
console.log(+id); // 1
console.log(new Date()); // Wed Nov 27 2019 21:00:38 GMT+0900 (日本標準時)
console.log(+new Date()); // 1574856056863

キャストできなければNaN になるので使い所は見きわめましょう。
ちなみにnew DateはDate.now()を使えばキャストせずとも数値で出せます。

console.log(Date.now()); // 1574856056863

letしない

三項演算子でletの回避

let type = 'a';
if (isB) {
    type = 'b';
}
const type = isB ? 'b' :'a';

即時関数でletの回避

複雑な分岐なら即時関数を使って回避することもできますね。このあたりは可読性とトレードオフでしょうか。

const type = ((flag) => {
  if (flag) {
    return 'a';
  } else {
    return fukuzatuNaSomething();
  }
})(isB);

その他Tips

関数の引数に初期値を設定する

初期値をつけておけば条件分岐を減らせます

const doSomething = (a, b = 1) => a * b;
doSomething(1); // 1
doSomething(1, 2); // 2

someで繰り返し処理を途中で抜ける

arr.forEach((obj) => {
  doSomething
  if(shouldBreak){
    //break; ←できない
  }
})
arr.some((obj) => {
  doSomething
  if (shouldBreak) {
    return true;
  }
  return false;
})

someで配列の中を調べる

includes代わりに使えます

const arr = ['a', 'b', 'c'];
arr.includes('b') // true
const arr = [{
  name: 'shwan'
},{
  name: 'taro'
}];
arr.some((person) => person.name === 'shwan') // true

対象が配列なのか調べる

Array.isArray(target)

対象がオブジェクトなのか調べる

Object.prototype.toString.call(target).slice(8, -1).toLowerCase() === 'object';

ここでいうオブジェクトは {id: 1, name:'a'}こういうのです。配列のようにスッキリは書けないので一工夫必要になります。

そろそろネタがなくなって極小ネタになってきたので、また来年に向けて溜め直してきます!

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

関数型言語に爪先レベルで入門したので、まずはJavaScriptで値をイミュータブル(不変)に扱ってみる

Mikatus Advent Calendar 2019 3日目の記事。

最初に

こんにちは。

普段はTypeScriptでVue.jsを書いているのですが、関数型言語を勉強しようと、最近社内ですごいHaskellたのしく学ぼう!通称すごいH本の勉強会を立ち上げました。

Haskellの特徴は色々あると思いますが、カリー化とか部分適用とか、JavaScriptに活かすのはなかなか難しそうだなと感じるものが多いので、わかりやすく効果がありそうな、値をイミュータブルに扱う方法について紹介していきます。

私自身JavaScriptも入門レベルなので、紹介する内容は基礎的なものですが、同じように最近JavaScriptに入門した人がいたら参考にしてみてください。

では参ります。

イミュータブルのメリット

まずイミュータブルとは、Wikipediaさんによると、

イミュータブル (英: immutable) なオブジェクトとは、作成後にその状態を変えることのできないオブジェクトのことである。
イミュータブル - Wikipedia

らしいです。

プログラミング関係の言葉をWikipediaで調べると難解なことが多いのですが、これは比較的わかりやすいですね。

この定義ではわかりづらいという場合でも、後に出てくる実際のコードを見ればなんとなく理解できるのではないかと思います。

で、イミュータブルにプログラミングするメリットですが、

  • 不測の値が混入するのを防ぐ
  • 値が置き換えられてないかいちいち確認する手間や心理的負荷がなくなる
  • プログラムの保守性が上がる

あたりが考えられ、特にチームで開発する上では大切なことだと感じています。

私自身独学期間を経て、今年エンジニアになったばかりなのですが、一人で開発する時には意識することもなかった、他のメンバー・将来のメンバーの負荷を減らすコードの必要性を実感しています。

というわけで、例外はあると思いますが、基本的に値はイミュータブルな方が良いのではないでしょうか。

では、これらのメリットを享受するため、JavaScriptでは実際どう書くのかを3つ見ていきましょう。

再代入しない

第一に、一度変数を宣言したら、再代入しないということです。

JavaScriptでは変数を宣言する時に、varletもしくはconstが使えます。

varはよっぽどの例外がない限り、使わないという方向で問題ないでしょう。
私にはよっぽどの例外が思いつきません。

残るは、letconstということになりますが、

  • letは再代入可能
  • constは再代入不可

になります。

実際のコードで見ていきましょう。

まずはletで変数を宣言した場合です。

let.js
let firstName = 'Ai'
let lastName = 'Katou'

firstName = 'Kai'
lastName = 'Atou'

let fullName = `${firstName} ${lastName}`
console.log(fullName)
// Kai Atou
// 書き換えられている!

加藤あいが阿藤快に書き換えられてしまっています。

次にconstです。

const.js
const firstName = 'Lewis'
const lastName = 'Ann'

firstName = 'Rice'
// TypeError: Assignment to constant variable.
// 再代入しようとするとTypeErrorに

const fullName = `${firstName} ${lastName}`
console.log(fullName)
// Lewis Annが必ず表示される

const obj = { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi' }

obj.number = 100

console.log(obj)
// {number: 100, firstName: 'Hinata', lastName: 'Kashiwagi'}
// あくまで再代入の禁止で、オブジェクトのプロパティ変更は可能なため注意

アン・ルイスを半ライスに書き換えようと思ったのですが、できませんでした。

というわけで変数の宣言にはconstを使っていきましょう。
JavaScriptを本格的に書き始めて2ヶ月弱ですが、letでの宣言が必要になる機会はほぼないという印象です。

なお、constでもあくまで再代入ができなくなるだけであって、オブジェクトのプロパティは変更できてしまうので注意しましょう。

次行きます。

配列をイミュータブルに扱う

今度は配列をイミュータブルに扱う方法です。

配列の操作には破壊的操作非破壊的操作が存在します。

配列に対して破壊的操作を行うと、操作を行なった配列が直接変更されてしまい、変更前の値を参照することができなくなります。

対して、非破壊的操作は元の配列はそのままに新しい配列を返すので、変更前の値も参照することができます。

実際のコードで見ていきましょう。

array.js
const array = [3, 5, 6, 10, 11, 12]

const sortedArray = array.sort((a, b) => b - a)

console.log(array)
// [ 12, 11, 10, 6, 5, 3 ]
// 元の配列もソートされてしまっている!

sortメソッドは破壊的操作なので元の配列もソートされてしまいます。
sortメソッドは便利なので、ぜひ使いたいところですが、このままではイミュータブルな状態を実現できません。

なので下記のように書いていきましょう。

array.js
const array = [3, 5, 6, 10, 11, 12]

const copiedArray = [...array]
// 元のarrayを変更しないようにスプレッド構文で一旦コピーする

const sortedArray = copiedArray.sort((a, b) => b - a)

console.log(array)
// [ 3, 5, 6, 10, 11, 12]
// ソート前の配列も呼び出せる

console.log(sortedArray)
// [ 12, 11, 10, 6, 5, 3 ]

sortメソッドを適用する前に配列をコピーしました。
一手間かかるだけのように思えますが、こうすることで上に挙げたメリットを享受できます。

array.js
const array = [3, 5, 6, 10, 11, 12]

array.push(13)
// pushも元の配列を変更する

const addedArray = [...array, 13]
// スプレッド構文を使って要素が追加された新しい配列を作る

ここではsortpushメソッドを例に挙げていますが、破壊的操作は他にもあります。

配列にメソッドを適用しようと思ったら、それが破壊的非破壊的かを確認してみましょう。

次行きます。

オブジェクトをイミュータブルに扱う

配列に続いて今度はオブジェクトです。

再代入しないの項目でも少し触れましたが、オブジェクトについてはconstで宣言しようとプロパティの変更ができてしまいます。

ここもイミュータブルに扱いたいところです。

実際のコードで見ていきましょう。

object.js
const obj = { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi' }

obj.color = 'orange'
// obj[color] = 'orange' も同様

console.log(obj)
// { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi', color: 'orange' }
// 変更されてしまっていて、変更前の状態が参照できない

const addedObj = { ...obj, color: 'orange' }
// スプレッド構文を使って追加する

console.log(addedObj)
// { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi', color: 'orange' }

console.log(obj)
// { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi' }
// 元の状態も参照できる

元のオブジェクトのプロパティを直接変更するのではなく、スプレッド構文を使って新しいオブジェクトを作っています。

スプレッド構文便利ですね。

ただ、この例については、TypeScriptを導入しているなら、型をちゃんと定義することで、後からプロパティの追加とかができないようにする方がいいと思います。

最後に

値をイミュータブルに扱う方法を3つ紹介しました。

意識したことがなかった人がいたら、ぜひ意識して今回紹介した方法などを試してみてください。

すごいH本勉強会では、まだモナド等には触れていないので、万が一モナドを理解できたらJavaScriptを書く際にも活かせないか模索したいと思います。

それでは!

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

[FULL STATIC] Nuxt generate<4つのアプローチまとめ>

最近ContentfulとNetlify、Nuxt.jsを利用してブログを立ち上げました。

ヘッドレスCMSとNuxt generateで静的なサイトを!と思ったのですが、実際にはクライアントからのアクセスのたびにContentfulへの通信が発生していました。
Screenshot from.png

理由は明らかです。
asyncDataでContentfulからのデータ取得処理を行っているからです。サーバー側だけでなく、クライアント側でも再度取得処理が実行されてしまっているのですね...。

Contentfulの記事更新時にNetlify上で generate⇒デプロイ まで自動で行うように設定しているので、記事は常に最新です。クライアントが新着問い合わせを行う必要はありません。「不要なアクセスがあるのは気持ち悪いな〜」と思って調べてみました。

この記事のまとめ

はじめに結論をまとめておきます。

通信を減らす・無くす手法は大きく4つに分けられます

A.nuxtServerInitで全データ取得&JSONに保存。

非常に直感的な手法です。完全に0にはできませんが、そこそこ減らせるはずです。個人ブログなどの場合はこれでも良いかもしれません。tagやキーワード検索などの機能が豊富なCMSも多いのですが、vueのfilterなどでリアルタイム検索すれば十分そうです。ただし、不要な通信は依然として残ります。

B.window.__NUXT__からJSONに保存。

nuxt generateで吐き出されたhtmlには window.__NUXT__ からはじまる箇所にJavaScriptが記載されています。また、サーバーサイドで取得したデータも含まれています。そのデータをJSONにキャッシュして利用しようという考え方です。
こちらのアプローチを取っているのはnuxt-payload-extractorです。下記で軽く触れますが、詳しくは実装を参考ください。

asyncDataで取得している場合はシンプルに導入できます。ただし、fetchやstore(ひとまとめにvuex)では不具合が起こる可能性があるとreadmeに記載されています。実は、full staticなgenerateはネイティブでの実装も検討されています。その1つの方策としても魅力的なようですが、課題もあるようです。

C.APIリクエスト時にJSONに保存。

こちらも直感的ですね。個人でのアプローチ例も多い手法です。
nuxt-staticではプラグインとしてaxiosリクエスト時にキャッシュしています。
また、下記で触れますがQiitaでも2例紹介されています。
トラブル時にも原因を突き止めやすそうで安心です:smiley:

D.htmlからJavaScriptを削除。

正反対かつド直球なアプローチ。htmlからscriptを削除してしまいます。セキュリティの観点からがきっかけですが、以下のブログ記事でcheerioを使用した方法が紹介されています。
Nuxt.js Generate後の<script>window.__NUXT__=を消したい

さらにパフォーマンスを求めるなら

JSONデータをキャッシュする場合(A,B,C)

⇒下記Qiita記事で紹介されているように、webworkerでクライアント側にもキャッシュさせる。
Firebase、Flamelink、Nuxt、Netlify、PWAを使ってJAMstackなブログを作る

htmlからJavaScriptを削除する場合

⇒AMPの導入。

以降ではGithub Issue、Qiita、ブログ記事の3つのソースから上記の4つの方法をもう少し詳細に紹介します。

目次

  • 1.Github Issueから(generate時にJSONに保存)
  • 2.Qiitaから(generate時にJSONに保存)
  • 3.ブログ記事から(htmlからJavaScriptを削除)

1.JSONに保存(Github Issueから)

Full static generated mode #22で静的なgenerateについて議論されています。

別の形で解決しましたが、以前にもnuxt generateには不要なJavaScriptがあるというIssueは上がっていました。現在は上記のIssueが中心のようです。(以前のIssue:Lots of unnecessary JavaScript in generated Nuxt static build

Full static generated mode #22では

nuxt generate --full-static

という新しいオプションも提案されています。
ただし、queryの処理など課題も多いようです。

Issueでは個人での対処例が2つ紹介されています。

どちらもJSONファイルにデータを保存し、

if(process.static && process.client)

で条件分岐し、クライアントサイドでの通信であれば、JSONファイルからのフェッチに変更しています。

仕組みは非常にシンプルなので、それぞれ簡単に紹介します。

stursby/nuxt-static

axiosでの通信時に条件分岐。JSONとして保存しておき、ベースURLを変更。
src/plugins/axios.jsのファイルがメインの処理部分です。以下Githubからの引用にコメントを追記したものです。

src/plugins/axios.js
import axios from 'axios'

let baseURL = 'https://jsonplaceholder.typicode.com'

if (process.browser && process.static) {
  baseURL = '/data'  //クライアントサイドではaxiosのbaseurlを変更していますね!
}

const api = axios.create({ baseURL })

//クライアントでの実行時
if (process.browser && process.static) {
  //interceptorsでリクエスト前にリクエスト先のurlを書き換えています
  api.interceptors.request.use(config => {
    config.url = config.url + '.json'
    return config
  })
}

//サーバーサイドでの実行時
if (process.server && process.static) {
  const mkdirp = require('mkdirp-promise')
  const { join, dirname } = require('path')
  const { writeFileSync } = require('fs')

  api.interceptors.response.use(
    async function(response) {
      // Do something with response data
      //envファイルで予め格納先を指定しておいて、リクエストのパス名で取得データを保存
      const path = join(process.env.dataDir, response.request.path + '.json')
      console.log('Save', path)
      await mkdirp(dirname(path))
      writeFileSync(path, JSON.stringify(response.data))
      return response
    },
    function(error) {
      // Do something with response error
      return Promise.reject(error)
    }
  )
}

export { api }

nuxt-payload-extractor

こちらはgenerate時にデータをタイムスタンプ付きでJSONファイルに保存しています。
以下moduleから抜粋

nuxt-payload-extractor/lib/module.js
//略
//生成されたhtmlから取得したデータが格納されている箇所を抜き出して保存
let extractPayload = function(html, route, base, timestamp){
  let chunks = html.split('<script>window.__NUXT__=')
  let pre = chunks[0]
  let payload = chunks[1].split('</script>').shift()
  let post = chunks[1].split('</script>').slice(1).join('</script>')
  let path = route === '/' ? '' : route

  return {
    html: pre + '<script defer src="' + base + path + '/payload' + timestamp + '.js"></script>' + post,
    payload
  }
}
//略
//nuxt.hook
this.nuxt.hook('generate:page', async page => {
    if(!this.nuxt.options.generate.subFolders) throw new Error('generate.subFolders should be true for nuxt-payload-extractor')
    if(blacklist && blacklist.includes(page.route)) return page
    //上記処理extractPayloadをgenerate時に呼び出しています
    let { html, payload } = extractPayload(page.html, page.route, base, timestamp)
    writePayload(payload, page.route, distDir, timestamp)

    page.html = html

    return page
  })
//略

その後、asyncData内で処理を分岐しています。(※$payloadURLはmodule読み込み時に指定するblacklistです)

以下nuxt-payload-extractorのサンプルからです。

nuxt-payload-extractor/example/pages/extracted.vue
//略
<script>
export default {
  async asyncData({ $axios, $payloadURL, route, error }){
    try {
      if(process.static && process.client && route.path !== '/'){
        //route.path !== '/' - because this route is blacklisted for nuxt-payload-extractor
        let {data} = await $axios.get($payloadURL(route))
        return data
      }
      let post = await $axios.$get(`/post.json`)
      return {
        post
      }
      //Or alternative way
      // let payload = {};
      // if(process.static && process.client && route.path !== '/')
      //   payload = await app.$axios.$get(document.location.origin + route.path + '/payload.json')
      // else
      //   payload.post = await app.$axios.$get(`/post.json`)
      //
      // return payload
    } catch (e) {
//略

どちらも導入も簡単ですが、nuxt-payload-extractorを利用される際はreadmeの以下の注意点もご確認ください。

Caveats
There may be issues with vuex data requests and nested routes. Keep in mind that payload.json has not hash in its name, so it shouldn't be cached in browser.

翻訳

注意事項
vuexデータリクエストとネストされたルートに問題がある可能性があります。 payload.jsonの名前にはハッシュがないため、ブラウザーにキャッシュしないでください。

workerでブラウザキャッシュしたい場合は次項のQiitaが参考になるかと思います。

2.JSONに保存(Qiitaから)

上記2つの例と同様、JSONファイルへの保存というアプローチを紹介しているQiita記事があります。

1つめの記事はFirebase向けのCMS Flamelinkの紹介のほか、JAMstackの説明なども丁寧に解説されています。素敵な記事ですね。その一環でJSONファイルへの保存も紹介しつつ、また、webworkerでキャッシュすることによって更にパフォーマンスを上げていることが紹介されています。先述したnuxt-payload-extractorはブラウザにキャッシュできませんでしたが、パフォーマンスを求めるならこちらを参考にしたほうが良さそうですね。

2つめの記事は1つめの記事が参照されています。JSONへの保存・読み込み処理部分だけの記事なので参考にしやすいと思います。(なくすト.js:thumbsup:)

3.直接htmlから削除する(ブログ記事から)

nuxt generateで生成されたhtmlには

<script>window.__NUXT__=

からはじまる箇所にJavaScriptの処理が記載されています。先述した、nuxt-payload-extractorはこの記載箇所からデータを取得しているので、そちらのソースも参照するとわかりやすいかもしれません。(そのほかの例はaxiosから直接データを取得しています)

単純ですが、htmlからこの記載を削除してしまえば通信も発生しません。
サクッと導入するには以下の記事で紹介されているcheerioを利用した方法が手軽かと思います。

Nuxt.js Generate後の<script>window.__NUXT__=を消したい

以下上記ブログ記事からの引用です。

nuxt.config.js
    const cheerio = require('cheerio')
    export default {
      // 省略
      hooks: {
        'generate:page': page => {
          const doc = cheerio.load(page.html);
          doc(`body script`).remove();
          page.html = doc.html();
        },
      },
      // 省略
    }

行っているのはnuxt.config.jsにcheerioで直接該当箇所を削除する処理を書くことだけです。アニメーションなどのために、クライアントサイドでJavaScriptを使う場合はscript全部ではなく、payload部分のみを削除すれば良さそうですね。(テストなどはしていません)

ケースごとのまとめ

リンクも再掲します。

case1 サクッと手軽になくしたい

htmlを軽くしたい。クライアントサイドのJavaScriptはいらない。既存のプロジェクトから不要なリクエストをとにかくサクッとなくしたい。セキュリティも気になる。
⇒D.直接htmlから削除する が手軽です。
Nuxt.js Generate後の<script>window.__NUXT__=を消したい

case2 webworkerやPWAは使わない

サクッと手軽になくしたい。webworkerやPWAは使わない。一番多そうなケースです。
⇒A.nuxtServerInitで全データ取得&JSONに保存。
⇒B.window.__NUXT__からJSONに保存。
⇒C.APIリクエスト時にJSONに保存。

中でもB.nuxt-payload-extractorか、C.「Nuxtでビルド時にAPIを静的化して、完全にサーバーへのリクエストをなくすト」が導入しやすそうだなと思います。

case3 パフォーマンスを向上させたい

サイトのパフォーマンスを向上させたい。
⇒A.nuxtServerInitで全データ取得&JSONに保存。
⇒C.APIリクエスト時にJSONに保存。

加えてwebworkerでキャッシュ。更にhtmlから不要な処理は削除しても良いかもしれません。
Firebase、Flamelink、Nuxt、Netlify、PWAを使ってJAMstackなブログを作る

⇒更に更に、軽さを追求する場合は、AMPの導入を検討してみても良いかもしれません。
Nuxtは公式でAMPのサンプルもあげられています。nuxt.js/examples/with-amp/

以上です。
至らない点あるかもしれません。不備などありましたら、ご指摘いただけるとありがたいです。

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

Primitives vs Objects

What is the difference between primitives and objects?

Variables holding primitives actually holds the value of the primitive inside the variable.

Variables associated to an object doesn't hold the object but holds a reference to where the object is stored. It points to the object.

// Primitives
var a = 23;
var b = a;
a = 46;
console.log(a); // 46
console.log(b); // 23

// Objects
var obj1 = {
  name: 'John',
  age: 26
};

// New object is not created but rather a new reference to the same object is created
var obj2 = obj1;
obj1.age = 30;
console.log(obj1); // 30 
console.log(obj2); // 30

// Functions
var age = 27;
var obj = {
  name: 'Jonas',
  city: 'Lisbon'
};

function change(a,b){
  a = 30;
  b.city = 'San Francisco';
}

change(age,obj);
console.log(age); // 27
console.log(obj.city); // San Francisco

When a primitive is passed to a function as an argument, rather than the actual variable, a copy will be passed. This means that the variable passed will not be affected.

When an object is passed, it's actually the reference to the object that is being passed and that is why the change is made to the object.

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

競馬の回収率、ディープラーニングを使わなくても100%をいくらでも超えられる

煽ってすみません。まぁ後出しでいいんだったらですが。

例えば、今年の 11/24 までの、種牡馬、馬主、調教師、騎手別の単勝の配当を集計して、出走数>100、回収率>100、勝率>10% で抜き出して回収率の高い順に並べたのが以下の表です。

データは JRA-VAN のデータを使っています。(残念ながら有料ですが、1ヶ月は無料期間があるようです)
データを以下のアプリを使って mongodb に落としたあと、javascript で集計しています。

jv2mongo

無料です。私作です。手前味噌ですみません(汗;

ちなみに1カラム目の意味は以下のとおりです。

  • hn は種牡馬
  • bn は馬主
  • ch は調教師
  • ks は騎手

2019.all.png

上の表にあるエピファネイアと松永幹夫調教師の回収率のグラフもつけておきます。

スクリーンショット 2019-11-28 15.27.45.png

スクリーンショット 2019-11-27 19.23.19.png

ちなみに上のアプリを使って構成した mongodb のデータを集計しやすい json に変換するためのスクリプトは以下のようになります。よかったら参考にしてください。

main.js
const MongoClient = require( 'mongodb' ).MongoClient;
const assert = require('assert');

function
IsSiba( _ ) {
    switch ( _ ) {
    case "10"   :
    case "11"   :
    case "12"   :
    case "13"   :
    case "14"   :
    case "15"   :
    case "16"   :
    case "17"   :
    case "18"   :
    case "19"   :
    case "20"   :
    case "21"   :
    case "22"   :
    case "51"   :
    case "53"   :
    case "54"   :
    case "55"   :
    case "56"   :
    case "57"   :
    case "58"   :
    case "59"   :
        return true
    default     :
        return false
    }
}

const fs=require( 'fs' )
const masUM=JSON.parse( fs.readFileSync( 'um.json', 'utf8' ) )

function
Get( db, p, cb ) {
    const pRA = Object.assign( { 'head.DataKubun': '7' }, p )
    const pSE = Object.assign( { 'head.DataKubun': '7' }, p )
    const pHR = Object.assign( { 'head.DataKubun': '2' }, p )
    Promise.all(
        [   db.collection( 'RA' ).find( pRA ).sort( { 'head.MakeDate.Year': -1, 'head.MakeDate.Month': -1, 'head.MakeDate.Day': -1 } ).toArray()
        ,   db.collection( 'SE' ).find( pSE ).sort( { 'head.MakeDate.Year': -1, 'head.MakeDate.Month': -1, 'head.MakeDate.Day': -1 } ).toArray()
        ,   db.collection( 'HR' ).find( pHR ).sort( { 'head.MakeDate.Year': -1, 'head.MakeDate.Month': -1, 'head.MakeDate.Day': -1 } ).toArray()
        ]
    ).then(
        _ => {
            let RA = {}
            {   _[ 0 ].map(
                    _ => ({
                        Year        : _.jvid.Year
                    ,   MonthDay    : _.jvid.MonthDay
                    ,   JyoCD       : _.jvid.JyoCD
                    ,   Kaiji       : _.jvid.Kaiji
                    ,   Nichiji     : _.jvid.Nichiji
                    ,   RaceNum     : _.jvid.RaceNum
                    ,   GradeCD     : _.GradeCD
                    ,   SyubetuCD   : _.JyokenInfo.SyubetuCD
                    ,   KigoCD      : _.JyokenInfo.KigoCD
                    ,   JyuryoCD    : _.JyokenInfo.JyuryoCD
                    ,   JyokenCD    : _.JyokenInfo.JyokenCD[ 4 ]
                    ,   Kyori       : _.Kyori
                    ,   TrackCD     : _.TrackCD
                    ,   TenkoCD     : _.TenkoBaba.TenkoCD
                    ,   BabaCD      : IsSiba( _.TrackCD ) ? _.TenkoBaba.SibaBabaCD : _.TenkoBaba.DirtBabaCD
                    })
                ).forEach(
                    _ => {
                        const key = _.Year + _.JyoCD + _.Kaiji + _.Nichiji + _.RaceNum
                        if ( RA[ key ] ) console.error( 'RA DUP', key )
                        else {
                            _[ 'SE' ] = []
                            RA[ key ] = _
                        }
                    }
                )
            }

            {   let w = {}  //  Checking purpose only
                _[ 1 ].forEach(
                    _ => {
                        const raKey = _.jvid.Year + _.jvid.JyoCD + _.jvid.Kaiji + _.jvid.Nichiji + _.jvid.RaceNum
                        const key = raKey + _.Umaban
                        const data = {
                            Umaban          : _.Umaban
                        ,   KettoNum        : _.KettoNum
                        ,   Sire            : masUM[ _.KettoNum ].Sire
                        ,   ChokyosiCode    : _.ChokyosiCode
                        ,   BanusiCode      : _.BanusiCode
                        ,   KisyuCode       : _.KisyuCode
                        ,   KakuteiJyuni    : _.KakuteiJyuni
                        ,   Odds            : _.Odds
                        }
                        if ( w[ key ] ) console.error( 'SE DUP', key )
                        else {
                            switch ( _.IJyoCD ) {
                            case 1:
                            case 2:
                            case 3:
                                break
                            default:
                                w[ key ] = data
                                RA[ raKey ].SE.push( data )
                                break
                            }
                        }
                    }
                )
            }
            {   let w = {}  //  Checking purpose only
                _[ 2 ].forEach(
                    _ => {
                        const key = _.jvid.Year + _.jvid.JyoCD + _.jvid.Kaiji + _.jvid.Nichiji + _.jvid.RaceNum
                        if ( w[ key ] ) console.error( 'HR DUP', key )
                        else {
                            RA[ key ].HR = _
                        }
                    }
                )
            }
            cb(
                Object.values( RA ).sort(
                    ( p, q ) => p.Year == q.Year
                    ?   p.MonthDay == q.MonthDay
                        ?   p.RaceNum == q.RaceNum
                            ?   0
                            :   p.RaceNum < q.RaceNum ? -1 : 1
                        :   p.MonthDay < q.MonthDay ? -1 : 1
                    :   p.Year < q.Year ? -1 : 1
                )
            )
        }
    )
}

function
Main( db ) {
    Get(
        db
    ,   { 'jvid.Year': { '$gte': '2016' } }
    ,   _ => {
            console.log( JSON.stringify( _, null, '\t' ) )
            process.exit( 0 )
        }
    )
}

MongoClient.connect(
    'mongodb://192.168.1.23:27017'
,   {   useNewUrlParser     : true
    ,   useUnifiedTopology  : true
    }
,   ( err, client ) => {
        assert.equal( err, null )
        Main( client.db( 'JRA-VAN' ) )
    }
)

上で使う um.json を作成するための、Mongo Shell スクリプト

um.ms
const _ = db.UM.find(
    null
,   { 'head.MakeDate': true, 'KettoNum': true, 'Bamei': true, 'Ketto3Info': true }
).sort(
    { 'head.MakeDate.Year': -1, 'head.MakeDate.Month': -1, 'head.MakeDate.Day': -1 }
);

const dict = {}
_.forEach(
    _ => {
        if ( ! dict[ _.KettoNum ] ) dict[ _.KettoNum ] = {
            Bamei           : _.Bamei
        ,   Sire            : _.Ketto3Info[ 0 ].HansyokuNum
        ,   Broodmare       : _.Ketto3Info[ 1 ].HansyokuNum
        ,   BroodmareSire   : _.Ketto3Info[ 2 ].HansyokuNum
        }
    }
);

ObjectId.prototype.tojson = function() { return '"' + this.valueOf() + '"'; };
Date.prototype.tojson = function() { return '"' + this.toISOString() + '"'; };

printjson( dict );

参考

ディープラーニングさえあれば、競馬で回収率100%を超えられる

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

Objects and Functions in Javascript

There is a saying that everything is an object in JavaScript...

Primitives

  • Numbers
  • Strings
  • Booleans
  • Undefined
  • Null

Objects

  • Arrays
  • Functions
  • Objects
  • Dates
  • and more...

Almost everything is an object in JavaScript and this is what differentiates JavaScript.

Object-Oritented Programming

Object Oriented Programming is a programming paradigm which heavily relies on usage of objects.

  • Objects interact with one another with methods and properties
  • Used to store data and structure applications into modules in order keep code clean

In JavaScript, classes might be reffered to as constructor or prototype

Inheritance

Inheritance is when one object is based on another object and has access to other object's properties and methods.

Prototypes

Prototype property of an object is where we put methods and properties that we want other objects to inherit.

Inheritance is possible in JavaScript thanks to the existance of Prototypes. **Every JavaScript object has a Prototype property. **

All objects in JavaScript is an instance of Object constructor / prototype.

Instance -> Occurance of an object.

Think of prototype as a blueprint or a class

Prototype Chain

Chain of prototypes. When an object doesn't have a method or a property, it will look at the parent prototype. If the Object prototype doesn't have it, it simply returns null.

Implementation

Function Constructor

var Person = function(name, yearOfBirth, job) {
  this.name = name;
  this.yearOfBirth = yearOfBirth;
  this.job = job;
};

// Instatiation
var john = new Person('John',1990,'teacher');

The 'new' operator points the 'this' variable to the empty object created when the new operator was called instead of the global object. This is why we can have multiple instances of the same Prototype.

Prototype property

Person.prototype.calculateAge = function(){  
  console.log(2019-this.yearOfBirth);
}

john.calculateAge() // Outputs '29'

'calculateAge' method is attached to constructor function's prototype property.

A property can also be attached to prototype.

john.hasOwnProperty('job'); // true
john.hasOwnProperty('calculateAge'); //false
john instanceof Person; // true

^ Checking if an instance has a specified property

Property for objects

Arrays

Arrays have array prototype

var x = [1,2,3]; // x has property of length
x.length; // 3

Another way to create an object : Object.create

This is another way to create an object. Also inherits from a prototype

  1. Create an object that will act as a prototype

  2. Create an object based on the prototype object

var personProto = {
  calculateAge: function() {
    console.log(2019 - this.yearOfBirth);
  }
};

// Pass in the object that will act as the prototype.

// Method 1
var john = Object.create(personProto);
john.name = 'John';
john.yearOfBirth = 1990;
john.job = 'teacher';

// Method 2
var jane = Object.create(personProto, {
  name: { value: "Jane" },
  yearOfBirth: { value: 1969 },
  job: { value: "designer" }
});

Object.create inherits from the prototype passed in as an argument whereas the function constructor inherits from the constructor's prototype property.

Object.create allows more complex inheritance.

Function constructor is more popular

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

Vuetifyのv-messagesのmin-heightを0pxにしたい

背景

  • エラー文言とかを入れるdivがvuetify使うとデフォルトでついてくる
  • デフォルトはmin-height 12pxだからレイアウトが結構崩れるので0pxにしたい

解決

  • deep selectorで解決
<style>
>>>.v-messages {
  min-height: 0px !important;
}
</style>

参考

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

画像変更なPull Requestでちょっと便利なTips

Diverse Advent Calendar 2019 13日目の記事です。

突然ですが、Pull Requestちゃんとレビューできてますか?

先日私はやらかしました :innocent:

本日はそのやらかし話とそれに対しての対策したお話しします。

序章 ~罠との遭遇~

まず、下記の図はお馴染みのGitHubのPull Requestのdiff画面です。

スクリーンショット 2019-11-27 14.47.41.png

GitHubには画像差分を比較するために強い機能1を持っています。
しかし、デフォルトで画像の差分は展開されません。
見るためには、指定のボタン icon.png を押す必要があります。

スクリーンショット_2019-11-27_14_47_52.png

すると、削除された画像は、 Binary file not shown. から Deleted file not shown. に変わったことがわかります。

本題はここからです。

変更画像がたくさんあった場合のPull Requestのとき、どうしましょう。
ボタン押さないと差分はわかりません。

3こ、4こ程度ならボタンポチポチで大丈夫だと思います。
10こ辺りから悩ましくなります...

意を決してポチポチ作戦を試みたものの、上記の画像みたいにファイル削除が続いた後に、「あー、全部削除なのねー」って気を抜いた次の瞬間、

スクリーンショット 2019-11-27 14.48.12.png

やらかしましたね :innocent: :innocent: :innocent:

第1章 ~罠に立ち向かう術~

画像変更差分が多いPull Requestが突きつけられたとき、ポチポチはめんどくさい。

そこで気づきました。

JSあるじゃん

ということで早速、画像差分を表示するボタン icon.png を確認。

<button class="btn btn-sm BtnGroup-item tooltipped tooltipped-w rendered js-rendered selected" aria-label="Display the rich diff" type="submit" data-disable-with="" aria-current="true">...</button>

すると、js-renderedクラスが該当ボタン icon.png に割り当てられてそうな臭いを察知 :eyes:

早速ボタンポチポチスクリプトを作成。

Array.from(document.getElementsByClassName('js-rendered')).forEach((b) => b.click());

これをconsoleで実行すると、

スクリーンショット 2019-11-27 15.50.02.png

:raised_hands: :raised_hands: :raised_hands:

第2章 ~それでもボク(自動化)はやってない~

なんとかボタンポチポチマンからスクリプトポチーマンまで進化しました。

しかし、便利になったとはいえPull Requestの都度スクリプトポチーはだるい...

そこで、 Tampermonkey の登場です。

Tampermonkeyとは

Tampermonkeyは、指定のページを表示時にユーザが組んだスクリプトを実行してくれる無料で使えるGoogle Chromeの拡張機能2です。

環境

  • Google Chrome (78.0.3904.108)
  • Tampermonkey (v4.9)

ユーザスクリプト

実行させるスクリプトを定義します。

// ==UserScript==
// @name         画像差分を表示するくん
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://github.com/*/*/*/files*
// @grant        none
// ==/UserScript==

window.onload = () => {
  Array.from(document.getElementsByClassName('js-rendered')).forEach((b) => b.click());
};

スクリプト本体に関しては、先ほどのスクリプトを window.onload で実行するようにしました。

そしてポイントは、ヘッダー内の @match に、スクリプト実行したいサイトのURLを定義します。34

すると、GitHub File Changedの画面を開くと、自動で画像差分を表示してくれるようになりました! :tada: :congratulations:

スクリーンショット 2019-11-27 15.50.02.png

最後に

今回、Tampermonkeyをご紹介しましたが、これ以外に似たような拡張機能を使っても同様のことは実現できると思います。
また今回実装したスクリプトは必要最低限の実装のみのため、変更ファイル数が多いPRだと遅延読み込みが発生して全ての差分が表示できない場合があるので、自分にあったスクリプトを書いて快適なレビューライフを送りましょう! :christmas_tree:

参考


  1. https://help.github.com/en/github/managing-files-in-a-repository/rendering-and-diffing-images#viewing-differences 

  2. Google Chrome以外に、Firefox, Safari, Microsoft Edgeなどの主要ブラウザ用のプラグインもあります。 

  3. Tampermonkey • Documentation 

  4. @match の記述方法はこちらを参照。 

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

仮想DOMの時代にDOMを返すAPIを扱う(CSR編)

DMMグループ Advent Calendar 2019 12日目の記事です。

アドカレ映えしない、地味~な内容ですが、よろしくお願いします。

TL;DR

Angular / React / Vue製サイトにWebAPI経由で返されたDOMを挿入する

Angular React Vue
image.png image.png image.png

前書き

大規模Webサイトの各ページへ横断的に要素を挿入したい要件があるとします。

  • ブランドロゴ、ナビゲーション、トラッキングタグなど
  • 大規模なので、ページ間で管轄が分かれていたり、別々のアーキテクチャで構築されていたりする
  • 要素の管轄は一部署に集約したい

これらの要件を満たす手段として、 DOM Stringを返すようなAPIが提供される ことがあります。

他の手段としては「DOMを構築・挿入するスクリプトを配布する」という方法もありますが、それと比べて以下のようなメリットが期待できます。

  • 呼び出しタイミングや組み込み位置をある程度制御できる
  • サーバサイド・クライアントサイドのどちらからでも呼び出せる

「クライアントサイドからでも呼び出せる」とはいえ、必ずしもすんなりできるとは限らず、特に昨今のJSフレームワークで開発されたWebサービスに組み込むにはハマりどころが色々あります。

今回はAngular, React, Vue製でクライアントサイドレンダリングするようなWebサイトにそのようなAPIを組み込む際の方法や注意を纏めています。

[全般]DomStringをDOMとしてrenderされるようにinnerHtmlラッパを使う

いずれのフレームワークもXSS対策のため、テンプレートやJSX中で使用される変数はHTMLエスケープされるようになっています。

そのため、APIレスポンスのDOM stringをそのままテンプレートやJSXへ書き出そうとしても、HTMLがプレーンテキストとして表示されるだけでHTML要素としては読み込まれません。

そこで、変数中のHTMLをHTML要素として表示するための機能が提供されておりますので、そちらを利用します。

大前提としてAPI側のXSS対策はなされているものとします。

Angular React Vue
innerHTML dangerouslySetInnerHTML v-html

[linkタグ]Angularの場合はlinkタグの扱いに注意

APIレスポンスの中に、スタイルシートを読み込むためのlinkタグが含まれる場合の話です。

React, Vueは上記の方法でlinkタグを出力することが出来るのですが、AngularではinnerHTMLでHTML要素を出力する際に、やはりXSS対策のためlinkタグやscriptタグが削除されるようになっています。

これを回避する方法も提供されており、DomSanitizerのbypassSecurityTrustHtmlメソッドを利用することでlinkタグを出力することができます。

[scriptタグ]innerHTMLではJSを実行出来ない

APIレスポンスにscriptタグが含まれる場合は厄介です。

innerHTML(やこれまで挙げたFWのinnerHTMLラッパ)を利用することで、DOMやスタイルを反映させることができますが、scriptタグ経由のJSを呼び出すことは出来ません。

これはW3CのHTML5 scriptタグの仕様として記述されています。

When inserted using the document.write() method, script elements execute (typically synchronously), but when inserted using innerHTML and outerHTML attributes, they do not execute at all.

これについては、フレームワーク側のサポートはなく、動的にJSを実行する方法などに記載されているように、

  1. document.createElement('script'); でscript要素を作成、
  2. domStringをパースしてscript要素を組み立てていき、
  3. 最終的に document.body.appendChild などで完成したscript要素を挿入する

という、骨の折れる作業をする必要がありそうです。

[実践]ヘッダー・フッターでコンテンツを挟み込む

ここで少し具体的な話になります。

APIのレスポンスとして、複数のUI、例えばヘッダーとフッターが一遍に返されるような場合はどのように実装すればよいでしょうか。

image.png

この場合、ヘッダー・フッターを1つのコンポーネントとして扱うのが良さそうです。

各フレームワークとも、似たようなかたちで任意のコンテンツを挟み込むようなコンポーネントを実装出来ます。

Angular React Vue
ng-content props.children slot

実装例 / Angular

app.component.html
<navigation>
  <!-- サイトコンテンツ -->
</navigation>
navigation.component.html
<div [innerHTML]='header'></div>
<ng-content></ng-content>
<div [innerHTML]='footer'></div>

実装例 / React

App.js(内のJSX)
<Navigation>
  <!-- サイトコンテンツ -->
</Navigation>
Navigation.js(内のJSX)
<div dangerouslySetInnerHTML={{ __html: header }} />
  {this.props.children}
<div dangerouslySetInnerHTML={{ __html: footer }} />

実装例 / Vue

index.html
<navigation>
  <!-- サイトコンテンツ -->
</navigation>
template
<div v-html="header"></div>
  <slot></slot>
<div v-html="footer"></div>

おわり

  • 本記事はDOMを返すようなAPIをmanageする、というのがテーマであり、共通UIを提供する手段としてDOMを返すAPIの設計を推奨するものではありません。
  • アプリケーションをSSRする場合は更なる課題がありそうですので、それはまた次の機会に。
  • 明日は @uruha さんより、Virtual DOMの時代の楽しい話が聞けるんじゃないかと思うので、乞うご期待!

サンプルアプリの元ネタ

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

Emacs 26.3 で vue-mode の js パートのインデントが効かなくなる件の対処法

syntax-ppss-table に何かが設定されているのが原因のようで、Issueにあった次の設定を追加したら直りました。

(setq mmm-js-mode-enter-hook (lambda () (setq syntax-ppss-table nil)))
(setq mmm-typescript-mode-enter-hook (lambda () (setq syntax-ppss-table nil)))

参照

Indentation problems in <script> tags #74
https://github.com/AdamNiederer/vue-mode/issues/74#issuecomment-539711083

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

01. 「パタトクカシーー」

01. 「パタトクカシーー」

「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.

Go

package main

import "fmt"

func main() {
    var src string = "パタトクカシーー";
    var des string = "";

    //  rune 型へ変換
    msrc := []rune(src);

    //  1.ループで処理
    for i := 0; i <= 7; i++ {
        //  問題では 1,3,5,7 指定だが奇数判定で対応
        if (i % 2) != 0 {
            des += string(msrc[i]);
        }
    }
    fmt.Println(des);
}

python

# -*- coding: utf-8 -*-
src = u"パタトクカシーー"
des = ""

#   1.ループで処理(range)
reverse = ""
for i in range(len(src)):
    # 問題では 1, 3, 5, 7 指定だが奇数判定で対応
    if i % 2 != 0 :
        des += src[i]
print(des)

# -*- coding: utf-8 -*-
src = u"パタトクカシーー"
des = ""

#   1.ループで処理(range)
reverse = ""
for i in range(len(src)):
    # 1, 3, 5, 7 指定だが奇数判定で対応
    if i % 2 != 0 :
        des += src[i]
print(des)

#   2.スライスで処理(ステップを指定)
print(src[1::2])

Javascript

var src = "パタトクカシーー";
var dsc = ""

//  1.ループで処理
for (var i = 0; i < src.length; i++) {
    //  問題では 1, 3, 5, 7 指定だが奇数判定で対応
    if (i % 2 != 0) {
        dsc += src[i];
    }
}
console.log(dsc);

まとめ

単純なループ処理しか思いつかない。
他のロジックって有るのかな?。100本ノックの記事探してみる。
1,3,5,7 を渡すと、切り出してくれる標準関数とか有るのかな?。

トップ

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

jQueryで顔認識機能を実装できる「jQuery Face Detection Plugin」を試してみた

jQuery Face Detection Pluginを試してみた

いつの間にかJavaScriptで顔認識を実装できるような時代になっていたのでjQuery Face Detection PluginというjQueryライブラリを使ってみました。

ダウンロード

まず、公式サイトよりjQuery Face Detection Pluginをダウンロードします。
ダウンロードしたらjquery.facedetection.zipというファイルが落ちてきます。
解凍したら、jquery.facedetection.min.jsjquery.facedetection.jsが入っているので好きな方を
bodyの閉じタグの上あたりに読み込ませます。

読み込み

bodyの閉じタグの上に以下のスクリプトを読み込ませます。

HTML
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="js/jquery.facedetection.min.js"></script>
<script>
// 顔認識の処理を書いていく場所
</script>

プラグインで取得できる値

取得できる値についてはこちらのブログで丁寧に解説されていますが主に取得できる値は以下になります。

内容
x 画像の中の顔範囲のX座標
y 画像の中の顔範囲のY座標
width 顔範囲の横幅
height 顔範囲の縦幅
positionX 親要素内の顔範囲の横位置
positionY 親要素内の顔範囲の縦位置
offsetX ドキュメント内の顔範囲の横位置
offsetY ドキュメント内の顔範囲の縦位置
confidence 顔認識の信頼性を示すレベル値

顔認識のサンプルを試してみた

HTML

HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>faceDetection</title>
<style>
  .picture-container {
    position: relative;
  }
  .face {
    border: solid 3px #fff;
  }
</style>
</head>
<body>
<div class="picture-container">
<!-- 認識させたい顔の画像 -->
<img id="picture" src="img/face.jpg">
</div>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="js/jquery.facedetection.min.js"></script>
<script src="js/script.js"></script>
</body>
</html>

JavaScript

JavaScript
$('#picture').faceDetection({
    complete: function (data) {
        for (var i=0; i<data.length; i++) {
            /*顔認識したところをボーダーで囲む*/
            $('<div>', {
                'class':'face',
                'css': {
                    'position': 'absolute',
                    'left'    : data[i].x * data[i].scaleX + 'px',
                    'top'     : data[i].y * data[i].scaleY + 'px',
                    'width'   : data[i].width * data[i].scaleX + 'px',
                    'height'  : data[i].height * data[i].scaleY + 'px'
                }
            }).insertAfter(this);
        }
    },
    error: function (code, message) {
        alert('Error: ' + message);
    }
});

検証結果

しっかり顔を認識してボーダーが付きました!
face.jpg

複数人でも認識するか?

faces.jpg

なぜか一人だけ認識されない結果になりました…。
複数人いた場合、認識の精度はイマイチのようです。

まとめ

実務で取り入れるには若干精度に不安が有りそうですが、遊びでやるには面白いかなと思いました。
ちなみにJavaScriptで顔認識のできるライブラリは他にもあってclmtrackr.jsというライブラリも良く使われており、そちらは顔の目や口などパーツ単位での認識も可能なようです。

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

WebRTCでストリームの音量を変更する

やりたかったこと

音声通話をするときに、マイクの音量を変更したい。
そのときにストリームの音量を変更して、通信相手にも変更した音量で音声が聞こえるようにしたい。

MediaStreamの音量を変更する方法

  1. getUserMediaでMediaStreamを取得する
  2. AudioContextを作成する。
    AudioContextはユーザーの操作(クリックなど)があったときに生成しないとエラーやWarningがでたりする。

    audioContext = new (window.AudioContext || window.webkitAudioContext)();
    
  3. 1で取得したMediaStreamを指定して、MediaStreamAudioSourceNodeを生成する。
    MediaStreamAudioSourceNodeオブジェクトからの音声は再生や編集が可能になる。

    source = audioContext.createMediaStreamSource(stream);
    
  4. AudioContextからMediaStreamAudioDestinationNodeを生成する。
    MediaStreamAudioDestinationNodeのstreamプロパティはMediaStreamと同じような使い方ができる。
    このstreamをRTCPeerConnectionで送信することができる。

    audioDestination = audioContext.createMediaStreamDestination();
    
  5. AudioContextからGainNodeを生成する。
    GainNodeで音声のボリュームを操作できる。

    gainNode = audioContext.createGain();
    
  6. 3〜5で生成したNodeをconnectで接続する
    audioDestination(MediaStreamAudioDestinationNode)のstreamで音量が変更されたstreamにアクセスすることができる。

    source.connect(gainNode);
    gainNode.connect(audioDestination);
    

↓ Sample(スライダーで音量変更するだけ)

See the Pen gainNode-test by mossan245 (@mossan245) on CodePen.

WebRTCで通信相手に音量変更したStreamを送信する

送信しているMediaStreamの音声のトラックをMediaStreamAudioDestinationNodeのstreamのAudioTrackで置き換えることで実現できる。
Trackの置き換えにはRTCRtpSender.replaceTrack()を利用する。

RTCRtpSender.replaceTrack()

しかし、FFやSafariだとうまく音量が変更されたStramの送信がうまいくが、Chromeから送信すると受信側で音声が再生されなくなってしまう。
どうやらChromeにバグがあるらしく現状のバージョンだとうまく動かない。
そのため、Chromeの場合は別途音量変更を通知する仕組みを用意して、通信相手側で直接音量を変更する必要がある。

参考

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

React + TypeScript でTodoリストを作りながら入門

昔Vueを勉強する前にチラっとReactをやったものの挫折して、それ以来触って無かった者です。

最近Reactは分かりやすくなってるとか、TypeScriptとの相性が良いとか聞いたので少しやってみることにしました。

今回は色々な資料を当たりながら最終的にシンプルなTodoリストを作成しました。

React用の型が追加される「@types/react」も使ってみます。
何か間違ってたら指摘お願いします。

:bee: 作るもの

new - CodeSandbox.png
Codesandbox

追加と削除だけのとてもシンプルなTODOリスト。

:tools: コードを見てみる

Todoの型を決める

import * as React from "react";
import * as ReactDOM from "react-dom";

// ***************************************
// Todoの型定義
// ***************************************
interface Todo {
  id: number;
  name: string;
}

Todoは、idとnameというプロパティを持つ型だと決めている。
idはnumber型であり、nameはstring型である。

Todoコンポーネントを作る

Todoリスト1つづつの部分である。
HTMLタグで言うと ”li” の部分に当たる。

// ***************************************
// Todoコンポーネント
// ***************************************
// TodoListItemPropsの型定義
interface TodoListItemProps {
  todo: Todo;
  onDelete: (todo: Todo) => void;
}

const TodoListItem: React.FunctionComponent<TodoListItemProps> = ({
  todo,
  onDelete
}) => {
  const onClick = () => {
    onDelete(todo);
  };

  return (
    <li>
      {todo.name} <button onClick={onClick}>Delete</button>
    </li>
  );
};

TodoListItemコンポーネントは、「React.FunctionComponent」という型に従いそのPropsは「TodoListItemProps」という型に従うと決める。

「FunctionComponent」は自分では定義していない。
「@types/react」が提供してくれる型。

interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement | null;
    propTypes?: WeakValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

Macなら「Option + クリック」、winなら「ctrl + クリック」で定義されている型のところへ飛べるので、見てみるとこんな感じらしい。

うん、よく分からんけど関数のコンポーネントなんやろ(適当)

Todoリストコンポーネントを作る

フォームを含まないTodoリスト全体の部分。
HTMLタグで言うと ”ul” の部分に当たる。

上記の「TodoListItem」の親になる。

// ***************************************
// Todoリストの一覧表示部分
// ***************************************
// Propsの型定義
interface TodosListProps {
  todos: Todo[];
  onDelete: (todo: Todo) => void;
}

const TodosList: React.FunctionComponent<TodosListProps> = ({
  todos,
  onDelete
}) => (
  <ul>
    {todos.map(todo => (
      <TodoListItem todo={todo} key={todo.id} onDelete={onDelete} />
    ))}
  </ul>
);

フォーム部分のコンポーネントを作る

ユーザーが入力するフォームや、追加ボタンの部分。

// ***************************************
// Todo追加用フォーム
// ***************************************
// Propsの型定義
interface NewTodoFormProps {
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  onAdd: (event: React.FormEvent<HTMLFormElement>) => void;
  todo: Todo;
}

const NewTodoForm: React.FunctionComponent<NewTodoFormProps> = ({
  onChange,
  onAdd,
  todo
}) => (
  <form onSubmit={onAdd}>
    <input onChange={onChange} value={todo.name} />
    <button type="submit">Add a todo</button>
  </form>
);

特筆するところはないけど、「React.ChangeEvent」「React.FormEvent」なども上記のように「Option + クリック」「ctrl + クリック」でどういう型なので見れるので興味があれば見てみると良いかもしれない。

Todoアプリ全体のコンポーネント

一番親の部分

// ***************************************
// Todoリスト本体部分
// ***************************************
// 状態(State)の型定義
interface State {
  newTodo: Todo;
  todos: Todo[];
}

class App extends React.Component<{}, State> {
  state = {
    newTodo: {
      id: 1,
      name: ""
    },
    todos: []
  };

  render() {
    return (
      <div>
        <h2>React + TypeScript Todoリスト</h2>
        <NewTodoForm
          todo={this.state.newTodo}
          onAdd={this.addTodo}
          onChange={this.handleTodoChange}
        />
        <TodosList todos={this.state.todos} onDelete={this.deleteTodo} />
      </div>
    );
  }

  // Todoの追加
  private addTodo = (event: React.FormEvent<HTMLFormElement>) => {
    // デフォルトの送信機能を使わない
    event.preventDefault();

    // 空なら処理を止める
    if (!this.state.newTodo.name.length) {
      return;
    }

    // TodoリストStateの更新、引数には現在(更新前)のStateが入ってくる
    this.setState(previousState => ({
      newTodo: {
        id: previousState.newTodo.id + 1,
        name: ""
      },
      // 現在のTODOの配列と、新しいTODOを結合する
      todos: [...previousState.todos, previousState.newTodo]
    }));
  };

  // フォームの内容が変わったらnewTodoの内容も変える
  private handleTodoChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const inputedValue = event.target.value;

    this.setState({
      newTodo: {
        ...this.state.newTodo,
        name: inputedValue
      }
    });
  };

  // Todoの削除
  private deleteTodo = (todoToDelete: Todo) => {
    this.setState(previousState => ({
      todos: [
        ...previousState.todos.filter(todo => todo.id !== todoToDelete.id)
      ]
    }));
  };
}

ReactDOM.render(<App />, document.getElementById("root"));

:star: まとめ

何だかReactは凄く難しいイメージがあったのですが、意外とシンプルで基本普通のJavaScriptに近い形で書けるのが良いと思いました。

またTypeScriptも趣味でチョロっとしか触ったこと無かったのですが、補完やコードジャンプやエラー通知が便利だなと実感出来ました!

ここ数ヶ月Swiftを勉強していたのですが、結構TypeScriptと似ている印象です。

以上

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

言語処理100本ノック 2015 をやってみた 00

言語処理100本ノック 2015 をやってみた

動機と目的

新たな言語を勉強してみるが使わないと忘れていく。
Qiita で 100本ノックの記事を見た。なるほど。やってみよう。
言語処理100本ノック 2015

目標

毎日(たぶん)、数問を解くことでそれぞれの言語に慣れるようにしてみる。
Qiita に書くことで Markdown に慣れる。

言語

  • Go
  • Python
  • Javascript

を主にやってみる。

00.文字列の逆順

Go

package main

import_ "fmt"

func main() {
    var data string = "stressed"
    var reverse string

    // 1.ループ
    for i := len(data) - 1; i >= 0; i-- {
        reverse += string(data[i])
    }

    fmt.Println(reverse)
}

python

# -*- coding: utf-8 -*-
data = "stressed"
reverse = ""

#   1.逆カウントのループ
i = len(data) - 1
while i >= 0:
    reverse += data[i]
    i-=1
print(reverse)

#   2.range->reversed
reverse = ""
for i in reversed(range(len(data))):
    reverse += data[i]
print(reverse)

#   3.reversed->join
print(''.join(reversed(data)))

#   4.スライス
print(data[::-1])

Javascript

var data = "stressed";
var reverse = ""

//  1.逆カウントのループ
for (var i = data.length - 1; i >= 0; i--) {
    reverse += data[i];
}
console.log(reverse);

//  2.split->reverse->join
console.debug(data.split("").reverse().join(""));

まとめ

Javascript以外は日本語非対応。
後でマルチバイト対応予定。

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

00. 文字列の逆順

00.文字列の逆順

文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.

Go

package main

import_ "fmt"

func main() {
    var data string = "stressed"
    var reverse string

    // 1.ループ
    for i := len(data) - 1; i >= 0; i-- {
        reverse += string(data[i])
    }

    fmt.Println(reverse)
}

python

# -*- coding: utf-8 -*-
data = "stressed"
reverse = ""

#   1.逆カウントのループ
i = len(data) - 1
while i >= 0:
    reverse += data[i]
    i-=1
print(reverse)

#   2.range->reversed
reverse = ""
for i in reversed(range(len(data))):
    reverse += data[i]
print(reverse)

#   3.reversed->join
print(''.join(reversed(data)))

#   4.スライス
print(data[::-1])

Javascript

var data = "stressed";
var reverse = ""

//  1.逆カウントのループ
for (var i = data.length - 1; i >= 0; i--) {
    reverse += data[i];
}
console.log(reverse);

//  2.split->reverse->join
console.debug(data.split("").reverse().join(""));

まとめ

goは日本語非対応。
問題ページとトップページ分割。

トップ

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

Google広告コンバージョンタグのソースコードでどうクリックIDを処理しているのか見てみる

概要

Google広告のコンバージョンタグのソースコードで2種類の広告クリックIDのCookieを処理している部分を見てみました。

背景

【Google広告】クロスドメインのコンバージョン計測-実装パターンと検証でcookieを調べたところ、Google広告に送るためのクリックIDをセットしたcookieとして以下の2種類存在することが確認できました:
- _gcl_aw
- _gac_UA-***
この2種類のCookieをGoogle広告のコンバージョンタグではどのようにハンドリングしているのか見てみたいと思いました。

※なお、本動機は以下の記事に影響を受けています。
Google AnalyticsのCookieの仕組みを解説
Googleアナリティクスのトラッカー生成処理を徹底解説

前提

コンバージョンタグを仕込んだページでconversion_async.jsを見てみます。
Google-Ads-Conversion-js_Source.png
https://www.googleadservices.com/pagead/conversion_async.js

ソースコードを整形

詳細

途中で力尽きてざっくりと見ただけになりましたが、2つのCookieをハンドリングし分けているコードの箇所を確認することができました。何か誤りがあればコメントで教えてください_(..)_

conversion_async.js
(function() {
...
    //718行目:URLパラメータ(a=b)を作る
    function T(a, b) {
        b = S(b);
        return "" != b && (a = S(a), "" != a) ? "&".concat(a, "=", b) : ""
    }

    //792行目:cookieからクリックID(※)を作る
    //※ "a=b" つまり "_gcl_aw=***"または"_gac_UA-***=***"
    function Kb(a, b) {  
        ...
        var d = "";
        //797行目:cookie"_gcl_aw"にマッチする値を"gclaw"にセットしてリターン
        if (b.google_gcl_cookie_prefix && /^[a-zA-Z0-9_]+$/.test(b.google_gcl_cookie_prefix) && "_gcl" != b.google_gcl_cookie_prefix)
            return d = mb(a, b.google_gcl_cookie_prefix), T("gclaw", d);
        ...
        if ...
        //804行目:"_gac_UA-***"にマッチするcookieの値をセット
        else {
            ...
            b = [];
            a = a.cookie.split(";");
            for (var e = /^\s*_gac_(UA-\d+-\d+)=\s*(.+?)\s*$/, f = 0; f < a.length; f++) {
                //808行目:"_gac_UA-***"にマッチするcookieがあれば値を取得する
                var g = a[f].match(e);
                g && b.push({
                    c: g[1],
                ...
            }
        ...
        }
        //829行目:cookie"_gac_UA-***"にマッチする値を"gac"にセットしてリターン
        return d + (c ? T("gac", c) : "")
    }

    //893行目:Google広告へのリクエストURLを作る
    function V(a, b, d, c, e, f) {  
        ...
        switch (e) {
            default: return "";
            case 2:
                    case 3:
                    var g = "googleads.g.doubleclick.net/pagead/viewthroughconversion/";
                break;
            case 1:
                    g = "www.google.com/pagead/1p-conversion/";
                break;
            case 0:
                    g = (c.google_conversion_domain || "www.googleadservices.com") + "/pagead/conversion/"
        }
        g = ["https://", g, S(c.google_conversion_id),...,"?random=",S(c.google_conversion_time)].join("");
        ...
        //909行目コンバージョンラベルやクリックIDを含む各種パラメータを設定
        a = [...,T("label", c.google_conversion_label),...Kb(d, c),...].join("");
        ...
        g += a;
        ...
        return g
    }
...
//1140行目
}).call(this);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Google広告コンバージョンタグのソースコードでどうクリックIDを処理しているのか見てみる

概要

Google広告のコンバージョンタグのソースコードで2種類の広告クリックIDのCookieを処理している部分を見てみました。

背景

【Google広告】クロスドメインのコンバージョン計測-実装パターンと検証でcookieを調べたところ、Google広告に送るためのクリックIDをセットしたcookieとして以下の2種類存在することが確認できました:
- _gcl_aw
- _gac_UA-***
この2種類のCookieをGoogle広告のコンバージョンタグではどのようにハンドリングしているのか見てみたいと思いました。

※なお、本動機は以下の記事に影響を受けています。
Google AnalyticsのCookieの仕組みを解説
Googleアナリティクスのトラッカー生成処理を徹底解説

前提

コンバージョンタグを仕込んだページでconversion_async.jsを見てみます。
Google-Ads-Conversion-js_Source.png
https://www.googleadservices.com/pagead/conversion_async.js

ソースコードを整形

詳細

途中で力尽きてざっくりと見ただけになりましたが、2つのCookieをハンドリングし分けているコードの箇所を確認することができました。何か誤りがあればコメントで教えてください_(..)_

conversion_async.js
(function() {
...
    //718行目:URLパラメータ(a=b)を作る
    function T(a, b) {
        b = S(b);
        return "" != b && (a = S(a), "" != a) ? "&".concat(a, "=", b) : ""
    }

    //792行目:cookieからクリックID(※)を作る
    //※ "a=b" つまり "_gcl_aw=***"または"_gac_UA-***=***"
    function Kb(a, b) {  
        ...
        var d = "";
        //797行目:cookie"_gcl_aw"にマッチする値を"gclaw"にセットしてリターン
        if (b.google_gcl_cookie_prefix && /^[a-zA-Z0-9_]+$/.test(b.google_gcl_cookie_prefix) && "_gcl" != b.google_gcl_cookie_prefix)
            return d = mb(a, b.google_gcl_cookie_prefix), T("gclaw", d);
        ...
        if ...
        //804行目:"_gac_UA-***"にマッチするcookieの値をセット
        else {
            ...
            b = [];
            a = a.cookie.split(";");
            for (var e = /^\s*_gac_(UA-\d+-\d+)=\s*(.+?)\s*$/, f = 0; f < a.length; f++) {
                //808行目:"_gac_UA-***"にマッチするcookieがあれば値を取得する
                var g = a[f].match(e);
                g && b.push({
                    c: g[1],
                ...
            }
        ...
        }
        //829行目:cookie"_gac_UA-***"にマッチする値を"gac"にセットしてリターン
        return d + (c ? T("gac", c) : "")
    }

    //893行目:Google広告へのリクエストURLを作る
    function V(a, b, d, c, e, f) {  
        ...
        switch (e) {
            default: return "";
            case 2:
                    case 3:
                    var g = "googleads.g.doubleclick.net/pagead/viewthroughconversion/";
                break;
            case 1:
                    g = "www.google.com/pagead/1p-conversion/";
                break;
            case 0:
                    g = (c.google_conversion_domain || "www.googleadservices.com") + "/pagead/conversion/"
        }
        g = ["https://", g, S(c.google_conversion_id),...,"?random=",S(c.google_conversion_time)].join("");
        ...
        //909行目コンバージョンラベルやクリックIDを含む各種パラメータを設定
        a = [...,T("label", c.google_conversion_label),...Kb(d, c),...].join("");
        ...
        g += a;
        ...
        return g
    }
...
//1140行目
}).call(this);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Chrome/78.0.3904.108 にて起こっているpreload問題とその暫定的対応

何が起こっているのか

内容は上記の記事を見てもらうとすごくわかりやすくまとめてくれています。(同僚さんの記事です)
おそらく、ページ内の行動履歴?に関係する関連の高いaタグのhrefの中身を「自動で」preloadしてしまっているという問題

で、フロントとしてどうしたのか

まず、被害の大きい箇所、ページを調べて

a href="/hogehoge/bar"

span data-href="/hogehoge/bar"

という風に書き換えた上で、あとはJS側でdata-hrefの中身をclickでlocation.hrefに入れて飛ばす方法で暫定対応。
それか

a data-href="/hogehoge/bar"

という風にして、load時にdata-hrefの中身を抜き取ってhrefに入れるとかでも行けそうな。

雑感

ひとまず、被害の大きい箇所だけの暫定対応とはいえ、まだ対応ページがそこまでではなかったのでできる技かなと。
これでも完璧ではないので、追っかけ対応になることもありえる。
サイト規模の大きいところはその限りではないだろうから大変かと思います。

早くchrome側のアップデートなりで修正されることを望むばかり

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

テスト投稿「〜についての記事」

見出し

〜ついての記事内容〜

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

A-FRAME: 物理演算でボーリングっぽい動きを実現してみる6(ピンの形状)

A-Frameをつかって物理演算ができるようにしてみます。
ピンに物理的な形状を設定してみて、振る舞いの違いをみてみます。

ピンは細長いひょうたん型で、プリミティブにはそのような形状はありません。
cylinderがふさわしいような気がしますが、自動的に割り当てた場合にどのような形状が割当たるのでしょうか。

例1)shape=auto

autoは使用可能な形状から自動的に選択されます。
shape1.gifdemo
boxが選択されたようです。

例2)shape=primitive

primitiveは平面/円柱/球の内、対応するAフレームプリミティブが自動的に使用されます。
shape2.gifdemo
boxが選択されたようです。
説明にはboxは候補に書かれていないですが、置いておきましょう。

例3)shape=box

boxを設定します。
shape3.gifdemo
ピンの形状にマッチしたboxが設定されました。

例4)shape=hull

hullはシュリンクラップのようなモデルをラップします。
hullという単語には殻や外皮といった意味があります。
shape4.gifdemo
レーンをすり抜けていきました。
ボールにhullを設定した時はレーンを転がっていったので、思っていたのと違う感じがします。

例5)shape=mesh

meshはJS物理エンジンでモデル化するのが難しく、特定の他のシェイプを「フォールスルー」し、パフォーマンスに重大な制限があります。
shape5.gifdemo
hullと同じように、レーンすり抜けていきました。
ボールにmeshを設定した時もレーンをすり抜けたので、この振る舞いはボールと同じように感じます。

例6)shape=none

noneは衝突ジオメトリを追加しません。
shape6.gifdemo
ボールにnoneを設定した時と同様、レーンをすり抜けていきました。

例7)shape=sphere

sphereを設定します。
shape7.gifdemo
ピンは球体とは異なるので、違和感が大きいです。
あと、sphereの中心とピンの中心がずれているように見えます。
boxの場合はboxの中心とピンの中心は一致していました。

例8)shape=cylinder

最後にもっとも有力に思っていたcylinderを設定します。
shape8.gifdemo
cylinderもsphereと同様に、sphereの中心とピンの中心が一致していません。

まとめ

ピンの物理的な形状は、自動で設定するとboxが選択されてcylinderにはなりませんでした。
そして、今回設定した形状はどれもしっくりきませんでした。

今回みえた課題としては、
- sphereとcylinderを設定した時に、ピンと物理的な形状の中心が一致しない原因が何か
- 複合シェイプを利用する事で、中心の不一致に対応する事もできそう(noneの本来の使い方っぽい)
- hullを設定した時の振る舞いがボールとピンで異なる

があるので、次以降で見ていきます。

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