20190413のJavaScriptに関する記事は17件です。

WebGL2でGray-Scott反応拡散系シミュレーション

WebGL2でGray-Scott反応拡散系シミュレーションを実装しました。
reaction-diffusion.gif
実際に動いているものはこちらで確認することができます。
https://aadebdeb.github.io/WebGL_GrayScott_ReactionDiffusion_Simulation/
ソースコードはGithubに置いておきました。
aadebdeb/WebGL_GrayScott_ReactionDiffusion_Simulation: Gray-Scott Reaction Diffusion Simulation in WebGL

Gray-Scott反応拡散系はライフゲームと同じような格子ベースのシミュレーションです。各格子がその位置の値を保持するので、Gray-Scott反応拡散系では2成分$u$, $v$の値を保持することになります。WebGLを用いたシミュレーションではテクスチャをシミュレーション空間全体、テクスチャの各ピクセルを1つの格子とします。

今回はcanvas全体でシミュレーションを行うために、以下のようにcanvasと同じ大きさのテクスチャを作成しています。Gray-Scott反応拡散系では各格子が$u$, $v$の2つの実数値を保持するため、texImage2Dのformat引数をgl.RGに、type引数をgl.FLOATにしています。。テクスチャのx要素が$u$、y要素が$v$に対応しています。internalformat引数は32ビット精度のgl.RG32Fにしています。float値を書き込みできるようにEXT_color_buffer_float拡張を有効化しています。

function createFramebuffer(gl, sizeX, sizeY) {
  const framebuffer = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, sizeX, sizeY, 0, gl.RG, gl.FLOAT, null);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.bindTexture(gl.TEXTURE_2D, null);
  return {
    framebuffer: framebuffer,
    texture: texture
  };
}

const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl2');
gl.getExtension('EXT_color_buffer_float');

let stateFbObjR = createFramebuffer(gl, canvas.width, canvas.height);
let stateFbObjW = createFramebuffer(gl, canvas.width, canvas.height);
const swapFramebuffer = function() {
  const tmp = stateFbObjR;
  stateFbObjR = stateFbObjW;
  stateFbObjW = tmp;
};

後述するように、シミュレーション自体はシェーダー用いてテクスチャにオフスクリーンレンダリングすることで行います。その際に1つ前の時間ステップの状態を保持しているテクスチャを参照する必要があります。オフスクリーンレンダリング先のテクスチャに現在参照しているテクスチャは使用できないので、フレームバッファを読み込み用(R=Read)、書き込み用(W=Write)の2つをそれぞれ作成して、swapFramebufferメソッドで入れ替えできるようにしています。

作成したフレームバッファにシミュレーションの初期状態を書き込みます。今回は、canvasの中心から一定距離以内はランダムな値、それ以外は$(u, v)=(0, 0)$となるようにしてます。書き込み後、swapFramebufferメソッドでフレームバッファを入れ替えておきます。

const initialize = function() {
  gl.bindFramebuffer(gl.FRAMEBUFFER, stateFbObjW.framebuffer);
  gl.useProgram(initializeProgram);
  gl.uniform2f(initializeUniforms['u_resolution'], canvas.width, canvas.height);
  gl.uniform2f(initializeUniforms['u_randomSeed'], Math.random() * 1000.0, Math.random() * 1000.0);
  renderToFillScreen();
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  swapFramebuffer();
};
#version 300 es

precision highp float;

out vec2 o_state;

uniform vec2 u_resolution;
uniform vec2 u_randomSeed;

float random(vec2 x){
  return fract(sin(dot(x,vec2(12.9898, 78.233))) * 43758.5453);
}

void main(void) {
  vec2 st = (2.0 * gl_FragCoord.xy - u_resolution) / min(u_resolution.x, u_resolution.y);
  if (length(st) < 0.3) {
    o_state = vec2(
      random(gl_FragCoord.xy * 0.15 + u_randomSeed + vec2(231.32, 171.92)),
      random(gl_FragCoord.xy * 0.21 + u_randomSeed + vec2(131.17, 319.23))
    );
  } else {
    o_state = vec2(0.0);
  }
}

初期化が完了したので、レンダリングループ内でシミュレーションの進行と描画を行います。Gray-Scott反応拡散系シミュレーションは時間ステップを大きくしすぎると発散してしまうため、固定時間ステップでシミュレーションを行います。レンダリングループ内のupdateメソッドでシミュレーションをtimeStep秒だけ進め、renderメソッドでレンダリングをおこなっています。

let simulationSeconds = 0.0;

let previousRealSeconds = performance.now() * 0.001;
const loop = function() {

  const currentRealSeconds = performance.now() * 0.001;
  const nextSimulationSeconds = simulationSeconds + parameters['time scale'] * Math.min(0.2, currentRealSeconds - previousRealSeconds);
  previousRealSeconds = currentRealSeconds;

  const timeStep = parameters['time step'];
  while(nextSimulationSeconds - simulationSeconds > timeStep) {
    update(timeStep);
    simulationSeconds += timeStep;
  }
  render();

  animationId = requestAnimationFrame(loop);
}
loop();

requestAnimationFrameのフレームレートは環境により異なるので、経過時間をもとに複数回updateメソッドを呼び出すようにしています。実時間でシミュレーションを行うとなかなか発展していかないのでparameters[time scale]で時間の進む速度を大きくできるようにしています。parameters[time scale]が大きくなるほど一度のloopメソッドの呼び出しで実行するupdateメソッドの回数が多くなり、結果として負荷が大きくなるので注意してください。

updateメソッドでは以下のようにシミュレーションの次の状態を計算しています。u_stateTextureからその格子と近傍の現在の状態を取得して、陽解法で次の状態を求めています。今回は上端と下端、左端と右端が連続しているという境界条件になるようにしています。Gray-Scott反応拡散系のアルゴリズムはこの記事を参考にそのまま実装しています。

const update = function(deltaTime) {
  gl.bindFramebuffer(gl.FRAMEBUFFER, stateFbObjW.framebuffer)
  gl.useProgram(updateProgram);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, stateFbObjR.texture);
  gl.uniform1i(updateUniforms['u_stateTexture'], 0);
  gl.uniform2f(updateUniforms['u_diffusion'], parameters['diffusion U'], parameters['diffusion V']);
  gl.uniform1f(updateUniforms['u_feed'], parameters['feed']);
  gl.uniform1f(updateUniforms['u_kill'], parameters['kill']);
  gl.uniform1f(updateUniforms['u_timeStep'], deltaTime);
  gl.uniform1f(updateUniforms['u_spaceStep'], parameters['space step']);
  renderToFillScreen();
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  swapFramebuffer();
};
#version 300 es

precision highp float;

out vec2 o_state;

uniform sampler2D u_stateTexture;
uniform float u_timeStep;
uniform float u_spaceStep;
uniform vec2 u_diffusion;
uniform float u_feed;
uniform float u_kill;

void main(void) {
  ivec2 coord = ivec2(gl_FragCoord.xy);
  ivec2 stateTextureSize = textureSize(u_stateTexture, 0);

  vec2 state = texelFetch(u_stateTexture, coord, 0).xy;

  vec2 left = texelFetch(u_stateTexture, ivec2(coord.x != 0 ? coord.x - 1 : stateTextureSize.x - 1, coord.y), 0).xy;
  vec2 right = texelFetch(u_stateTexture, ivec2(coord.x != stateTextureSize.x - 1 ? coord.x + 1 : 0, coord.y), 0).xy;
  vec2 down = texelFetch(u_stateTexture, ivec2(coord.x, coord.y != 0 ? coord.y - 1 : stateTextureSize.y - 1), 0).xy;
  vec2 up = texelFetch(u_stateTexture, ivec2(coord.x, coord.y != stateTextureSize.y - 1 ? coord.y + 1 : 0), 0).xy;

  vec2 laplacian = (left + right + up + down - 4.0 * state) / (u_spaceStep * u_spaceStep);

  o_state = state + u_timeStep * (u_diffusion * laplacian + vec2(
    state.x * state.x * state.y - (u_feed + u_kill) * state.x,
    -state.x * state.x * state.y + u_feed * (1.0 - state.y)
  ));
}

renderメソッドのJavaScriptコードとフラグメントシェーダー以下のようになっており、ここでスクリーンへの描画を行います。フラグメントシェーダー内ではu_targetu_renderingでどの値を描画するか、どのように描画するかをそれぞれ決定しています。2Dの場合は値をそのまま描画し、3Dとして描画する場合は、この記事を参考に法線を計算してシェーディングしています。

const render = function() {
  gl.useProgram(renderProgram);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, stateFbObjR.texture);
  gl.uniform1i(renderUniforms['u_stateTexture'], 0);
  gl.uniform1i(renderUniforms['u_target'], parameters['target']);
  gl.uniform1i(renderUniforms['u_rendering'], parameters['rendering']);
  gl.uniform1f(renderUniforms['u_spaceStep'], parameters['space step']);
  renderToFillScreen();
}
#version 300 es

precision highp float;

out vec4 o_color;

uniform sampler2D u_stateTexture;
uniform int u_target;
uniform int u_rendering;
uniform float u_spaceStep;

float getValue(ivec2 coord) {
  vec2 state = texelFetch(u_stateTexture, ivec2(coord), 0).xy;

  if (u_target == 0) {
    return state.x;
  } else if (u_target == 1) {
    return state.y;
  } else {
    return abs(state.x - state.y);
  }
}

vec3 render2d(ivec2 coord) {
  return vec3(getValue(coord));
}

vec3 lambert(vec3 color, vec3 normal, vec3 lightDir) {
  return color * max(dot(normal, lightDir), 0.0);
}

vec3 render3d(ivec2 coord) {
  ivec2 stateTextureSize = textureSize(u_stateTexture, 0);
  float state = getValue(coord);
  float left = getValue(ivec2(coord.x != 0 ? coord.x - 1 : stateTextureSize.x - 1, coord.y));
  float right = getValue(ivec2(coord.x != stateTextureSize.x - 1 ? coord.x + 1 : 0, coord.y));
  float down = getValue(ivec2(coord.x, coord.y != 0 ? coord.y - 1 : stateTextureSize.y - 1));
  float up = getValue(ivec2(coord.x, coord.y != stateTextureSize.y - 1 ? coord.y + 1 : 0));

  vec3 dx = vec3(2.0 * u_spaceStep, 0.0, (right - left) / (2.0 * u_spaceStep));
  vec3 dy = vec3(0.0, 2.0 * u_spaceStep, (up - down) / (2.0 * u_spaceStep));

  vec3 normal = mix(normalize(cross(dx, dy)), vec3(0.0, 0.0, 1.0), 0.5);

  vec3 color = vec3(0.0);
  color += lambert(vec3(0.8), normal, vec3(1.0, 1.0, 1.0));
  color += lambert(vec3(0.3), normal, vec3(-1.0, -1.0, 0.3));
  return color;
}

void main(void) {
  vec2 state = texelFetch(u_stateTexture, ivec2(gl_FragCoord.xy), 0).xy;

  if (u_rendering == 0) {
    o_color = vec4(render2d(ivec2(gl_FragCoord.xy)), 1.0);
  } else {
    o_color = vec4(render3d(ivec2(gl_FragCoord.xy)), 1.0);
  }
}

WebGLでGray-Scott反応拡散系シミュレーションを行う方法について解説しました。GPUを使ってシミュレーションを進めることで、格子をピクセル単位で設定してもリアルタイムに計算することができました。

値をハイトマップと見なして法線を計算することで3D的にシェーディングする方法は、Polygon Lounge #1で見たもの(@kaiware007さんの作品?)がかっこよかったので真似しました。

NEORTに色と初期状態を変更したものを置いたのでそちらも見ていただけると幸いです。
https://neort.io/art/bilttjs3p9f9psc9oafg
reaction-diffusion2.gif

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

VuejsのGUIジェネレーターを改良した話

はじめに

作成中のGUIジェネレータを改良した。

改良点

vuetify 以外のコンポーネントフレームワークを利用可能にした

任意の css と javascript を読み込めるようにしたため好きなフレームワークを利用できるようになった。

change_lib.gif

プロジェクトデータの保存と読み込みを可能にした

作成中のプロジェクトをjsonファイルでダウンロードできるようにし、ファイルを読み込むことで保存した状態を再現できるようにした。

project_export.gif

任意のインラインスクリプトを挿入できるようにした

Vue.use(plugin)

などが書けるようにすることが目的

inline.gif

技術的な話

iframeのリロード

スクリプトを読み直したりしたときはiframeをリロードしないといけないが以下のような感じで書けた

private reload() {
  const ele = obtainIframe();
  ele.onload = () => {
    // 必要な処理
  };
  ele.contentWindow.location.reload();
}

インラインスクリプトが更新された時もリロードするが、イベントの間引きがしたかった。
_.debounce を使おうと思っていたが、https://github.com/duxiaofeng-github/typescript-debounce-decorator が使いやすくてよかった。

こんな感じで使える。

@debounce(500, { leading: false })
private reload() {
}

類似ツール

今回の更新中に見つけた。

  • PreVue
    • 完全に使い方を理解していないが目的が異なるようなイメージを持った

終わりに

今後やりたいこと。

  • 追加したい機能
    • 複数ページへの対応 (vue-routerで)
    • attribute の型指定
  • 機能以外で対応したいこと
    • リファクタリング
    • CI・CDの導入
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js to TypeScriptの書き方一覧

最近ひたすら既存のVueプロジェクトのTypeScript化をやっているのでメモとしてまとめます。
随時追加予定。

前提

  • .vueファイルで<script lang="ts">を使う
  • vue-property-decoratorを使ったデコレータ方式のTypeScript化を行う

data

dataはそのままクラスのプロパティとして宣言します。

JavaScript

<script >
export default {
  name: 'Human',
  data() {
    return {
      name: '山田 太郎',
      age: 19
    };
  }
};
</script>

TypeScript

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  name: string = '山田 太郎';
  age: number = 19;
}
</script>

components

SFCで他Vueコンポーネントを参照する際に使うComponentsは
@Component()デコレータにcomponentsをkeyに持つオブジェクトを渡し定義します.

JavaScript

<script>
import Header from './Header'
import Footer from './Footer'

export default {
  name: 'Page',
  components: {
    Header,
    Footer
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Header from './Header.vue';
import Footer from './Footer.vue';

@Component({
  components: {
    Header,
    Footer
  }
})
export default class Page extends Vue {
}
</script>

props

親コンポーネントからデータを受け取るpropsは@Propデコレータを使い定義します。

@Prop(options: (PropOptions | Constructor[] | Constructor) = {})

JavaScript

<script>
export default {
  name: 'Post',
  props: {
    contents: { default: '', type: String, require: true },
    postNumber: { default: 0, type: [Number, String], require: true },
    publish: { default: true, type: Boolean, require: true },
    option: { type: Object, require: false } // optionには {new: boolean, important: boolean, sortNumber: number} が設定される想定
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class Post extends Vue {
  @Prop({ default: '' })
  contents!: string;

  @Prop({ default: 0 })
  postNumber!: number | string;

  @Prop({ default: false })
  publish!: boolean;

  @Prop
  option?: {
    new: boolean,
    important: boolean,
    sortNumber: number
  };
}
</script>

computed

算出プロパティはgetter付きのメソッドで定義します。

JavaScript

<script>
export default {
  name: 'Human',
  data () {
    return {
      firstName: '太郎',
      lastName: '山田'
    }
  },
  computed: {
    fullName () {
      return `${this.firstName} ${this.lastName}`
    }
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  firstName: string = '太郎';
  lastName: string = '山田';

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

}
</script>

emit

親コンポーネントに値を送信する$emitは@Emitデコレータを使いfunctionとして定義します。
メソッド名が、$emitに渡すイベント名がメソッド名と同様の場合は、@Emit()の引数(イベント名)は省略できます。
※ 以下のcountUpのケース。camelCaseとkebabe-caseは自動で変換されます

@Prop(options: (PropOptions | Constructor[] | Constructor) = {})

JavaScript

<script>
export default {
  name: 'Counter',
  data () {
    return {
      count: 0
    }
  },
  methods: {
    countUp (n) {
      this.count += n
      this.$emit('count-up', n)
    },
    resetCount () {
      this.count = 0
      this.$emit('reset')
    }
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Emit, Vue } from 'vue-property-decorator';

@Component
export default class Counter extends Vue {
  count: number = 0;

  @Emit('count-up')
  countUp(n) {
    this.count += n;
    return n;
  }

  @Emit('reset')
  resetCount() {
    this.count = 0;
  }
}
</script>

model

input要素のコンポーネント化の際にvalueが競合しないように用いるmodelは
@Modelデコレータを使ってプロパティを定義します。

@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})

JavaScript

<script>
export default {
  name: 'MyRadio',
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: { type: Boolean }
  }
}
</script>

TypeScript
```vue

```

watch

dataの変更を検知するWatchは@Watchデコレータを使ってメソッドを定義します。
optionにwatchオプションのimmediateやdeepもオブジェクトとして渡すことで指定できます。

@Watch(path: string, options: WatchOptions = {})

JavaScript

<script>
export default {
  name: 'InputText',
  data () {
    return {
      text: '',
      lengthDiff: 0
    }
  },
  watch: {
    text: function (newText, oldText) {
      this.lengthDiff = newText.length - oldText.length
    }
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';

@Component
export default class InputText extends Vue {
  text: string = '';
  lengthDiff: number = 0;

  @Watch('text')
  onTextChanged(newText: string, oldText: string) {
    this.lengthDiff = newText.length - oldText.length;
  }
}
</script>

provide, inject

深い階層でも親コンポーネントと子コンポーネントでデータを共有する際に用いるprovideとinjectは@provide, @injectデコレータで定義します。

@Provide(key?: string | symbol) 
@Inject(options?: { from?: InjectKey, default?: any } | InjectKey)

JavaScript

Parent.vue

<script>
export default {
  name: 'Parent',
  provide: {
    commonValue: 'foo'
  }
}
</script>

Child.vue

<script>
export default {
  name: 'Child',
  inject: {
    commonValue: { default: 'hoge' }
  }
}
</script>

TypeScript

Parent.vue

<script lang="ts">
import { Component, Provide, Vue } from 'vue-property-decorator';

@Component()
export default class Parent extends Vue {
  @Provide() commonValue = 'foo';
}
</script>

Child.vue

<script lang="ts">
import { Component, Inject, Vue } from 'vue-property-decorator';

@Component
export default class Child extends Vue {
  @Inject({ default: 'hoge' })
  readonly commonValue!: string;
}
</script>

mixins

コンポーネント間の共通処理を定義するミックスインはMixins()の継承を使って表現します。
ミックスイン自身もVueを継承したクラスとして定義します。
複数のミックスインを使う場合も、Mixins()の引数として与えることが可能です。
(最大5つ)
https://github.com/vuejs/vue-class-component/blob/master/src/util.ts#L35

JavaScript

mixinLogger.js

export default {
  created () {
    const now = new Date
    console.log(`created: ${now}`)
  },
  methods: {
    log (param) {
      console.log(`log: ${param}`)
    }
  }
}

Human.vue

<script>
import MixinLogger from './mixinLogger'

export default {
  name: 'Human',
  mixins: [MixinLogger],
  methods: {
    greeting () {
      console.log('おはよう')
      this.log('call greeting')
    }
  }
}
</script>

TypeScript

mixin.ts

import { Component, Vue } from 'vue-property-decorator';

@Component
export default class mixinLogger extends Vue {
  created() {
    const now = new Date;
    console.log(`created: ${now}`);
  }

  log(param) {
    console.log(`log: ${param}`);
  }
}

Human.vue

<script lang="ts">
import MixinLogger from './mixinLogger';
import { Component, Mixins } from 'vue-property-decorator';

@Component
export default class Human extends Mixins(MixinLogger) {
  greeting() {
    console.log('おはよう');
    this.log('call greeting');
  }
}
</script>

created, mounted, updated, destroyed ...etc

ライフサイクルフックはそのまま同名のメソッドとして宣言することですることで実装可能です。

JavaScript

<script>
export default {
  name: 'Human',
  created: function () {
    // 何か処理
  },
  mounted: function () {
    // 何か処理
  },
  updated: function () {
    // 何か処理
  },
  destroyed: function () {
    // 何か処理
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  created() {
    // 何か処理
  }

  mounted() {
    // 何か処理
  }

  updated() {
    // 何か処理
  }

  destroyed() {
    // 何か処理
  }
}
</script>

参考

https://github.com/vuejs/vue-class-component
https://github.com/kaorun343/vue-property-decorator

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

Vue.js to TypeScriptの覚え書き

最近ひたすら既存のVueプロジェクトのTypeScript化をやっているのでメモとしてまとめます。
随時追加予定。

前提

  • .vueファイルで<script lang="ts">を使う
  • vue-property-decoratorを使ったデコレータ方式のTypeScript化を行う

data

dataはそのままクラスのプロパティとして宣言します。

JavaScript

<script >
export default {
  name: 'Human',
  data() {
    return {
      name: '山田 太郎',
      age: 19
    };
  }
};
</script>

TypeScript

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  name: string = '山田 太郎';
  age: number = 19;
}
</script>

components

SFCで他Vueコンポーネントを参照する際に使うComponentsは
@Component()デコレータにcomponentsをkeyに持つオブジェクトを渡し定義します.

JavaScript

<script>
import Header from './Header'
import Footer from './Footer'

export default {
  name: 'Page',
  components: {
    Header,
    Footer
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Header from './Header.vue';
import Footer from './Footer.vue';

@Component({
  components: {
    Header,
    Footer
  }
})
export default class Page extends Vue {
}
</script>

props

親コンポーネントからデータを受け取るpropsは@Propデコレータを使い定義します。

@Prop(options: (PropOptions | Constructor[] | Constructor) = {})

JavaScript

<script>
export default {
  name: 'Post',
  props: {
    contents: { default: '', type: String, require: true },
    postNumber: { default: 0, type: [Number, String], require: true },
    publish: { default: true, type: Boolean, require: true },
    option: { type: Object, require: false } // optionには {new: boolean, important: boolean, sortNumber: number} が設定される想定
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class Post extends Vue {
  @Prop({ default: '' })
  contents!: string;

  @Prop({ default: 0 })
  postNumber!: number | string;

  @Prop({ default: false })
  publish!: boolean;

  @Prop
  option?: {
    new: boolean,
    important: boolean,
    sortNumber: number
  };
}
</script>

computed

算出プロパティはgetter付きのメソッドで定義します。

JavaScript

<script>
export default {
  name: 'Human',
  data () {
    return {
      firstName: '太郎',
      lastName: '山田'
    }
  },
  computed: {
    fullName () {
      return `${this.firstName} ${this.lastName}`
    }
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  firstName: string = '太郎';
  lastName: string = '山田';

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

}
</script>

emit

親コンポーネントに値を送信する$emitは@Emitデコレータを使いfunctionとして定義します。
メソッド名が、$emitに渡すイベント名がメソッド名と同様の場合は、@Emit()の引数(イベント名)は省略できます。
※ 以下のcountUpのケース。camelCaseとkebabe-caseは自動で変換されます

@Prop(options: (PropOptions | Constructor[] | Constructor) = {})

JavaScript

<script>
export default {
  name: 'Counter',
  data () {
    return {
      count: 0
    }
  },
  methods: {
    countUp (n) {
      this.count += n
      this.$emit('count-up', n)
    },
    resetCount () {
      this.count = 0
      this.$emit('reset')
    }
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Emit, Vue } from 'vue-property-decorator';

@Component
export default class Counter extends Vue {
  count: number = 0;

  @Emit('count-up')
  countUp(n) {
    this.count += n;
    return n;
  }

  @Emit('reset')
  resetCount() {
    this.count = 0;
  }
}
</script>

model

input要素のコンポーネント化の際にvalueが競合しないように用いるmodelは
@Modelデコレータを使ってプロパティを定義します。

@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})

JavaScript

<script>
export default {
  name: 'MyRadio',
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: { type: Boolean }
  }
}
</script>

TypeScript
```vue

```

watch

dataの変更を検知するWatchは@Watchデコレータを使ってメソッドを定義します。
optionにwatchオプションのimmediateやdeepもオブジェクトとして渡すことで指定できます。

@Watch(path: string, options: WatchOptions = {})

JavaScript

<script>
export default {
  name: 'InputText',
  data () {
    return {
      text: '',
      lengthDiff: 0
    }
  },
  watch: {
    text: function (newText, oldText) {
      this.lengthDiff = newText.length - oldText.length
    }
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';

@Component
export default class InputText extends Vue {
  text: string = '';
  lengthDiff: number = 0;

  @Watch('text')
  onTextChanged(newText: string, oldText: string) {
    this.lengthDiff = newText.length - oldText.length;
  }
}
</script>

provide, inject

深い階層でも親コンポーネントと子コンポーネントでデータを共有する際に用いるprovideとinjectは@provide, @injectデコレータで定義します。

@Provide(key?: string | symbol) 
@Inject(options?: { from?: InjectKey, default?: any } | InjectKey)

JavaScript

Parent.vue

<script>
export default {
  name: 'Parent',
  provide: {
    commonValue: 'foo'
  }
}
</script>

Child.vue

<script>
export default {
  name: 'Child',
  inject: {
    commonValue: { default: 'hoge' }
  }
}
</script>

TypeScript

Parent.vue

<script lang="ts">
import { Component, Provide, Vue } from 'vue-property-decorator';

@Component()
export default class Parent extends Vue {
  @Provide() commonValue = 'foo';
}
</script>

Child.vue

<script lang="ts">
import { Component, Inject, Vue } from 'vue-property-decorator';

@Component
export default class Child extends Vue {
  @Inject({ default: 'hoge' })
  readonly commonValue!: string;
}
</script>

mixins

コンポーネント間の共通処理を定義するミックスインはMixins()の継承を使って表現します。
ミックスイン自身もVueを継承したクラスとして定義します。
複数のミックスインを使う場合も、Mixins()の引数として与えることが可能です。
(最大5つ)
https://github.com/vuejs/vue-class-component/blob/master/src/util.ts#L35

JavaScript

mixinLogger.js

export default {
  created () {
    const now = new Date
    console.log(`created: ${now}`)
  },
  methods: {
    log (param) {
      console.log(`log: ${param}`)
    }
  }
}

Human.vue

<script>
import MixinLogger from './mixinLogger'

export default {
  name: 'Human',
  mixins: [MixinLogger],
  methods: {
    greeting () {
      console.log('おはよう')
      this.log('call greeting')
    }
  }
}
</script>

TypeScript

mixin.ts

import { Component, Vue } from 'vue-property-decorator';

@Component
export default class mixinLogger extends Vue {
  created() {
    const now = new Date;
    console.log(`created: ${now}`);
  }

  log(param) {
    console.log(`log: ${param}`);
  }
}

Human.vue

<script lang="ts">
import MixinLogger from './mixinLogger';
import { Component, Mixins } from 'vue-property-decorator';

@Component
export default class Human extends Mixins(MixinLogger) {
  greeting() {
    console.log('おはよう');
    this.log('call greeting');
  }
}
</script>

created, mounted, updated, destroyed ...etc

ライフサイクルフックはそのまま同名のメソッドとして宣言することですることで実装可能です。

JavaScript

<script>
export default {
  name: 'Human',
  created: function () {
    // 何か処理
  },
  mounted: function () {
    // 何か処理
  },
  updated: function () {
    // 何か処理
  },
  destroyed: function () {
    // 何か処理
  }
}
</script>

TypeScript

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Human extends Vue {
  created() {
    // 何か処理
  }

  mounted() {
    // 何か処理
  }

  updated() {
    // 何か処理
  }

  destroyed() {
    // 何か処理
  }
}
</script>

参考

https://github.com/vuejs/vue-class-component
https://github.com/kaorun343/vue-property-decorator

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

JavaScriptと仲良くなろう

はじめに

jQueryなどJavaSriptをより扱いやすくしてくれる便利なファイルはありますが、
理解を深めるためにも今回は便利なものは使用せず、一からコードを記述することにしました。

チェックボタンを押すと対応する3色のボタンが表示される機能を作ります。

HTML

code.html
<div class="all_section">
    <div class="color">
      <p>Check-Box</p>
      <div class="Check_word">チェック</div>
    </div>
  <div class="list">
    <!--チェック機能の作成-->
    <div class="section">
      <input type="checkbox" id="check_one1" data-name="あか"></input>
      <input type="checkbox" id="check_one2" data-name="あお"></input>
      <input type="checkbox" id="check_one3" data-name="きいろ">黄色</input>
    </div>
  </div>
    <!--チェック機能に対応するボタンを作成-->
    <div class="images">
      <input type="button" class="one1" data-name="あか"/>
      <input type="button" class="one2" data-name="あお"/>
      <input type="button" class="one3" data-name="きいろ"/>
    </div>
</div>

CSS

code.css
/*全体への装飾*/
.all_section {
  width: 100%;
  background-color: white;
  text-align: center;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/*タイトル画面の装飾*/
.color {
  overflow: hidden;
  margin-top: -20px;
  height: 200px;
  background-color: #3c3d3d9a;
  color: antiquewhite;
  border-radius: 40px;
}
/*タイトル画面の文字への装飾*/
.color p {
  margin-top: 80px;
  font-size: 25px;
}
/*チェック機能部分の装飾*/
.list {
  width: 100%;
  margin-top: 100px;
  font-size: 20px;
  color: antiquewhite;
  border-radius: 20px;
  background-color: #9cce8b;
}
/*チェックの装飾*/
.list input {
  margin: 10px;
  color: #3c3d3d9a;
  border-radius: 20px;
}
/*ボタン機能の装飾*/
.images input {
  width: 100px;
  height: 100px;
  margin-top: 100px;
  margin-left: 10px;
  margin-right: 10px;
}
/*赤ボタンの装飾*/
.one1 {
  background-color: #fa1919;
}
/*青ボタンの装飾*/
.one2 {
  background-color: #1900ff;
}
/*黄色ボタンの装飾*/
.one3 {
  background-color: #fbff04;
}

JavaScript

code.js
// 画面の対象の画像を全て隠す関数
function hideAllImages() {
  const images_off = document.querySelectorAll('.images input');
  images_off.forEach(function (image) {
    //画像を非表示
    image.style.visibility = "hidden";
  });
}

// 値が変更された時に呼ばれる関数
function onChangeValue() {
  // 全ての画像を非表示
  hideAllImages();

  //input(チェックボタン)のデータをすべて取得
  const sec_checkboxes = document.querySelectorAll('.section input');
  //imagesのデータをすべて取得
  const all_images = document.querySelectorAll('.images input');

  //1つずつ取得したデータのdata-nameを1つずつ取得
  sec_checkboxes.forEach(function (checkbox) {
    const checkbox_name = checkbox.dataset.name;

    //その中でもしチェックされていたらimagesデータを取得
    if (checkbox.checked) {
      all_images.forEach(function (image) {
        const image_name = image.dataset.name;

        //該当のdata-nameがあったら表示
        if (image_name.indexOf(checkbox_name) >= 0) {
          image.style.visibility = "visible";
        }
      });
    }
  });
}

// 画面を読み込んだときに呼ばれる関数
function onLoad() {
  // 全て非表示
  hideAllImages();

  //  値が変更された時に呼ばれる関数(イベントを登録する)
  document.addEventListener("change", onChangeValue);
}
//ウィンドウがロードした時に起動
window.addEventListener("load", onLoad);

完成

Check-Box.png

左から 赤をチェック 赤と青をチェック 赤と青と黄色すべてをチェックした時です。

まとめ

基本的なことですが、コードが長くなるので自分で書いたコードにはメモをこまめに記載する。
関数を作って細かく分けてわかりやすくなるように心がけると見直した時に助かります。
今回の記事を書くために見直したときに改めて大切だと思いました。

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

1行もコードを書かずにネイティブアプリ(もどき)をリリースする7つのステップ

前提

これだけでちゃんとしたアプリが作れるとは言いませんし、ネイティブアプリオワタとは言いません。
ただ、ノーコードでアプリをデプロイまでできるのは結構すごいなあと思ったので、共有します。
(やはりY Combinatorすごい)

必要とされる知識など

  • インターネット接続できる環境にあること。
  • PCで画面をみれて、スプレッドシートが開けること。(&作れること)
  • Gmailのアカウントがあること。
  • 作りたいアプリのイメージがあること。

今回のアプリについて

米国株の配当金、買い時の指標などを管理できるアプリを作ります。

完成イメージは

メインページがこれで

スクリーンショット 2019-04-13 17.55.30.png

詳細画面がこれです。シンプルですね。

スクリーンショット 2019-04-13 17.56.45.png

もう一度言いますが、一行もコードは書いていません。

もう一つ付け加えると15分くらいでリリースまでこぎつけました。

では、作っていきましょう!

事前準備

添付のような感じでスプレッドシートを作リます。
スクリーンショット 2019-04-13 18.09.27.png

手順1

Glideにアクセスする。

手順2

Create an appを押して、下記の画像の画面に遷移する
スクリーンショット 2019-04-13 18.02.23.png

Sign Upを押して、Gmailのアカウントでログインする。

手順3

左上にあるNew Appを押す。
スクリーンショット 2019-04-13 18.04.57.png

手順4

スプレッドシートを読み込む。
(スプレッドシートは事前に作っておいたものを利用)
スクリーンショット 2019-04-13 18.06.13.png
僕の場合はここに書いてある「配当金管理」というスプレッドシートを使います。

手順5

アプリの開発画面になったので、見た目を調整する

スクリーンショット 2019-04-13 18.10.26.png

検索バーがデフォルトでついてて驚愕・・・

大まかにいうと、左側が「どんなことをしたいか」右側が「その詳細を変更する」という感じになっています。めっちゃわかりやすいですね。

機能は色々いじりながら試していってもらいたいのですが、先ほどの見た目に近づけるための方法を簡単に記載します。

まず、「ア」とか「ウ」とか左側に表示されるのがダサいので無くします。
Show initials as default Imageの部分が有効になっているので無効になります。

次に色を黒くしたいので、左側のSettingsを押して、右側に出てくるThemeを押します。

スクリーンショット 2019-04-13 18.16.53.png

これでほぼできたので、あとは細かいところを適当に作ります。(アイコンとか、色合いとかはご自身でどうぞ)

手順6

URLを決める。

Settingを押した後の画面でAPP URLを入れるところがあるので、適当に名前を決めて入力します。

スクリーンショット 2019-04-13 18.21.01.png

手順7

リリースする(まじか・・・!)

左下のopen appを押す。

スクリーンショット 2019-04-13 18.23.32.png

これでアプリができてしまいました。。

完成品がこれです。

PWA対応もしているので、Androidだともはやネイティブアプリです。
ちなみにiOSでもちゃんとアプリという認識で立ち上がってくれました。

ezgif.com-video-to-gif.gif

すごい世界がやってきたものだ。。

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

Tabulatorにて、コンテキストメニューでの絞り込みを実装する

概要

Tabulator を用いた際に、絞り込みをもっと便利にしたいなぁと思い、
右クリックした際に、コンテキストメニュー出してそのセル値で楽に絞り込んでみました。

Tabulator セットアップ

http://tabulator.info/docs/4.2/quickstart
公式のサンプルの通りにセットアップします。
とりあえず動かすだけなので、CDNから読み込みます。

<head>
<meta charset="utf-8">
<link href="https://unpkg.com/tabulator-tables@4.2.4/dist/css/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="https://unpkg.com/tabulator-tables@4.2.4/dist/js/tabulator.min.js"></script>
</head>
<body>
<div id="example-table"></div>
<script>
 var tabledata = [
    {id:1, name:"Oli Bob", age:"12", col:"red", dob:""},
    {id:2, name:"Mary May", age:"1", col:"blue", dob:"14/05/1982"},
    {id:3, name:"Christine Lobowski", age:"42", col:"green", dob:"22/05/1982"},
    {id:4, name:"Brendon Philips", age:"125", col:"orange", dob:"01/08/1980"},
    {id:5, name:"Margret Marmajuke", age:"16", col:"yellow", dob:"31/01/1999"},
 ];

var table = new Tabulator("#example-table", {
    height:205, // set height of table (in CSS or here), this enables the Virtual DOM and improves render speed dramatically (can be any valid css height value)
    data:tabledata, //assign data to table
    layout:"fitColumns", //fit columns to width of table (optional)
    columns:[ //Define Table Columns
        {title:"Name", field:"name", width:150},
        {title:"Age", field:"age", align:"left", formatter:"progress"},
        {title:"Favourite Color", field:"col"},
        {title:"Date Of Birth", field:"dob", sorter:"date", align:"center"},
    ]
});
</script>
</body>

tabulator 実行画面

スクリーンショット 2019-04-13 15.13.25.png

右クリックイベントを検知させる

http://tabulator.info/docs/4.2/callbacks#cell

Cell Right Click
The cellContext callback is triggered when a user right clicks on a cell, it can be set on a per column basis using the option in the columns definition object.

{title:"Name", field:"name", cellContext:function(e, cell){
    //e - the click event object
    //cell - cell component
    },
}

とありますので、これを使います。

cellContext: function(event, cell){
    //event - the click event object
    //cell - cell component
    // 通常の右クリックメニューを停止
    event.preventDefault();
    // 代わりに自作のメニューを表示
    showContextMenu(cell.getField(),cell.getValue(), event.pageX, event.pageY );
},

自作コンテキストメニューを表示させるため、自前のshowContextMenu関数を加えます。
関数内で、リストを作成し、そこに現在クリックしたセルの値とともに、そのリストをクリックした際にイベントを発火し、絞り込みを実行させます。

var showContextMenu = function(field, value, posx , posy) {
    var cm = document.getElementById("contextmenu");

    // 非表示をやめて、クリックした位置に表示する
    cm.style.left = posx + 'px';
    cm.style.top = posy + 'px';
    cm.style.display = '';

    var li = document.createElement('li');
    li.innerText = value + 'で絞り込む';
    // クリックイベント時に、headerfilterを実施する
    li.addEventListener('click', function(){ setHeaderFilter(field, value) });

    var ul = cm.getElementsByTagName('ul');
    // リストの中身を空にして追加
    ul[0].innerHTML = '';
    ul[0].appendChild(li);
} ;

自作コンテキストメニューのためのCSSとHTML

<style>
.contextmenu-wrapper{
  position: absolute;
}
.contextmenu-contents{
  position: relative;
  border: 2px solid #CCC;
  background-color: #FFF;
  box-shadow: 0 .5rem 1rem rgba(0,0,0,.15);
  padding: .25em;
}
.contextmenu-contents ul {
  list-style: none;
  margin: .25em;
  padding: 0;
}

.contextmenu-contents li:hover {
  background-color:#AAF;
  cursor: pointer;
}
</style>
<div id="example-table"></div>
<div id="contextmenu" class="contextmenu-wrapper" style="display: none;">
  <div class="contextmenu-contents">
    <ul></ul>
  </div>
</div>

コンテキストメニュー実装画面

スクリーンショット 2019-04-13 16.27.43.png

HeaderFilterの準備

tabulator設定時の columnsに headerFilter: true を加えます。
※ 公式にNote: At present, the progress and star editors are not available as header filters. とある通り、プログレスおよびスター表示の内容には、ヘッダーフィルターは使えません。

columns:[ //Define Table Columns
    {title:"Name", field:"name", width:150, headerFilter: true},
    {title:"Age", field:"age", align:"left", formatter:"progress"},
    {title:"Favourite Color", field:"col", headerFilter: true},
    {title:"Date Of Birth", field:"dob", sorter:"date", align:"center", headerFilter: true},
],

ヘッダーフィルターへ値を適用する関数

tabulatorに用意されているsetHeaderFilterValue関数に、フィールド名と値を渡す関数を作ります。
クリック後は、不要となるのでコンテンツメニューを隠します。

var setHeaderFilter = function (cell, value) {
    this.table.setHeaderFilterValue(cell, value);
    hideContextMenu();
}
var hideContextMenu = function() {
    var cm = document.getElementById("contextmenu");
    cm.style.display = 'none';
} ;

完成

codepenに貼り付けました。

See the Pen KYvXmV by majirou (@majirou) on CodePen.

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

Tabulatorにて、コンテンツメニューでの絞り込みを実装する

概要

Tabulator を用いた際に、絞り込みをもっと便利にしたいなぁと思い、
右クリックした際に、コンテキストメニュー出してそのセル値で楽に絞り込んでみました。

Tabulator セットアップ

http://tabulator.info/docs/4.2/quickstart
公式のサンプルの通りにセットアップします。
とりあえず動かすだけなので、CDNから読み込みます。

<head>
<meta charset="utf-8">
<link href="https://unpkg.com/tabulator-tables@4.2.4/dist/css/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="https://unpkg.com/tabulator-tables@4.2.4/dist/js/tabulator.min.js"></script>
</head>
<body>
<div id="example-table"></div>
<script>
 var tabledata = [
    {id:1, name:"Oli Bob", age:"12", col:"red", dob:""},
    {id:2, name:"Mary May", age:"1", col:"blue", dob:"14/05/1982"},
    {id:3, name:"Christine Lobowski", age:"42", col:"green", dob:"22/05/1982"},
    {id:4, name:"Brendon Philips", age:"125", col:"orange", dob:"01/08/1980"},
    {id:5, name:"Margret Marmajuke", age:"16", col:"yellow", dob:"31/01/1999"},
 ];

var table = new Tabulator("#example-table", {
    height:205, // set height of table (in CSS or here), this enables the Virtual DOM and improves render speed dramatically (can be any valid css height value)
    data:tabledata, //assign data to table
    layout:"fitColumns", //fit columns to width of table (optional)
    columns:[ //Define Table Columns
        {title:"Name", field:"name", width:150},
        {title:"Age", field:"age", align:"left", formatter:"progress"},
        {title:"Favourite Color", field:"col"},
        {title:"Date Of Birth", field:"dob", sorter:"date", align:"center"},
    ]
});
</script>
</body>

tabulator 実行画面

スクリーンショット 2019-04-13 15.13.25.png

右クリックイベントを検知させる

http://tabulator.info/docs/4.2/callbacks#cell

Cell Right Click
The cellContext callback is triggered when a user right clicks on a cell, it can be set on a per column basis using the option in the columns definition object.

{title:"Name", field:"name", cellContext:function(e, cell){
    //e - the click event object
    //cell - cell component
    },
}

とありますので、これを使います。

cellContext: function(event, cell){
    //event - the click event object
    //cell - cell component
    // 通常の右クリックメニューを停止
    event.preventDefault();
    // 代わりに自作のメニューを表示
    showContextMenu(cell.getField(),cell.getValue(), event.pageX, event.pageY );
},

自作コンテキストメニューを表示させるため、自前のshowContextMenu関数を加えます。
関数内で、リストを作成し、そこに現在クリックしたセルの値とともに、そのリストをクリックした際にイベントを発火し、絞り込みを実行させます。

var showContextMenu = function(field, value, posx , posy) {
    var cm = document.getElementById("contextmenu");

    // 非表示をやめて、クリックした位置に表示する
    cm.style.left = posx + 'px';
    cm.style.top = posy + 'px';
    cm.style.display = '';

    var li = document.createElement('li');
    li.innerText = value + 'で絞り込む';
    // クリックイベント時に、headerfilterを実施する
    li.addEventListener('click', function(){ setHeaderFilter(field, value) });

    var ul = cm.getElementsByTagName('ul');
    // リストの中身を空にして追加
    ul[0].innerHTML = '';
    ul[0].appendChild(li);
} ;

自作コンテキストメニューのためのCSSとHTML

<style>
.contextmenu-wrapper{
  position: absolute;
}
.contextmenu-contents{
  position: relative;
  border: 2px solid #CCC;
  background-color: #FFF;
  box-shadow: 0 .5rem 1rem rgba(0,0,0,.15);
  padding: .25em;
}
.contextmenu-contents ul {
  list-style: none;
  margin: .25em;
  padding: 0;
}

.contextmenu-contents li:hover {
  background-color:#AAF;
  cursor: pointer;
}
</style>
<div id="example-table"></div>
<div id="contextmenu" class="contextmenu-wrapper" style="display: none;">
  <div class="contextmenu-contents">
    <ul></ul>
  </div>
</div>

コンテキストメニュー実装画面

スクリーンショット 2019-04-13 16.27.43.png

HeaderFilterの準備

tabulator設定時の columnsに headerFilter: true を加えます。
※ 公式にNote: At present, the progress and star editors are not available as header filters. とある通り、プログレスおよびスター表示の内容には、ヘッダーフィルターは使えません。

columns:[ //Define Table Columns
    {title:"Name", field:"name", width:150, headerFilter: true},
    {title:"Age", field:"age", align:"left", formatter:"progress"},
    {title:"Favourite Color", field:"col", headerFilter: true},
    {title:"Date Of Birth", field:"dob", sorter:"date", align:"center", headerFilter: true},
],

ヘッダーフィルターへ値を適用する関数

tabulatorに用意されているsetHeaderFilterValue関数に、フィールド名と値を渡す関数を作ります。
クリック後は、不要となるのでコンテンツメニューを隠します。

var setHeaderFilter = function (cell, value) {
    this.table.setHeaderFilterValue(cell, value);
    hideContextMenu();
}
var hideContextMenu = function() {
    var cm = document.getElementById("contextmenu");
    cm.style.display = 'none';
} ;

完成

codepenに貼り付けました。

See the Pen KYvXmV by majirou (@majirou) on CodePen.

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

ESLint v6.0.0 の変更点まとめ

v5.16.0 | 次 (未定)

ESLint 6.0.0-alpha.0 がリリースされました。
以後、6.0.0 安定版リリースまでの間、この記事に変更内容をまとめていきます。

プレリリースであるため、インストールには@nextタグが必要です。

npm install --save-dev eslint@next

機能追加は少なめですが、既存の機能を改善するための非互換な変更があります。特に、グローバル インストール (npm install -g) された ESLint コマンドを利用している場合、今まで通りには利用できない可能性が高いです。逆に、ローカル インストールを利用している場合はほとんど変化を感じないかもしれません。

  • :information_source: 6.0.0-alpha.0では、まだ一部の非互換な変更がマージされていません。状況はプロジェクトボードで確認できます。

互換性のない変更 for ユーザー:

互換性のない変更 for プラグイン開発者:

互換性のない変更 for 連携ツール開発者:

マイグレーション ガイド:

リリースノート:

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット
? 本家リポジトリ
? 本家サポート チャット

? 互換性のない変更

§ Node.js 6.x のサポートを終了しました。

? #11557, #11456, #11022

Node.js 6 は 4 月 30 日に寿命を迎えます。これに伴い、ESLint は Node.js 6 のサポートを終了しました。

新たなサポート範囲は以下の通りです。

  • Node.js 8 (8.10.0 以上)
  • Node.js 10 (10.13.0 以上)
  • Node.js 11 以降 (11.10.1 以上)

§ プラグインの読み込み方が大幅に変更されました。

? #11388, #10125, RFC #7

従来は ESLint がインストールされた場所を基準に (即ち、ESLint がインストールされた node_modules ディレクトリから) プラグインを読み込んでいました。これは babel 等の一緒に使われることが多いツール群とは異なる振る舞いであり、混乱の原因になっていました。また、lernaYarn Plug n' Play 等の特定の環境下で ESLint が正常に動作しない原因にもなっていました。

そのため、ESLint 6 からはこの動作が変更され、常にプロジェクト ローカル (より厳密には $CWD/node_modules とその祖先ディレクトリ) からプラグインを読み込むようになりました。

これまでグローバル インストールされた ESLint とプラグインを利用していた方は、利用方法を変更する必要があります。ESLint とプラグイン類をプロジェクト毎にインストールし、コマンドは eslint-cli 等を利用する事をお勧めします。

§ eslint:recommendedに含まれるルールが更新されました。

? #11518, #11357, #10873, #10768

以下のルールが新たに有効になりました。

  • no-async-promise-executor Promiseクラスのコンストラクタに渡す関数が非同期関数だった場合に警告します。その関数内で例外がスローされた場合にプログラムがクラッシュする (正確には unhandledRejection が発生する) ためです。
  • no-misleading-character-class 正規表現の文字クラスに複数のコードポイントから構成される文字がある場合に警告します。それは正しく解釈されないためです。
  • no-prototype-builtins hasOwnPropertyのようなObjectクラスの一部メソッドを警告します。オブジェクトをMap的に使う場合はObjectクラスのメソッドを持っていないObject.create(null)が好まれるためです。現代ではMapクラスを利用するのが良いでしょう。
  • no-shadow-restricted-names undefined等の重要な変数のシャドーイングを警告します。
  • no-useless-catch 無駄なcatch句を警告します。
  • no-with with構文を警告します。
  • require-atomic-updates 非同期関数にて、共有変数がアトミックに変更されない場合に警告します。

以下のルールが有効にならなくなりました。

  • no-console このルールは多くの場合に有用ですが (例えば、誤ってデバッグ用の記述をプロダクション コードに残さないようにする)、CLI プログラムなどconsole.logが有効なケースもあるため、推奨設定から外されました。

また、従来のeslint:recommendedには推奨以外のルールを無効にする設定が含まれていました。ESLint 6 からは、eslint:recommendedはいかなるルールも無効にしません。推奨されるルールを有効にするだけです。

§ no-redeclare ルールのチェックがより厳しくなりました。

? #11509, #11405, #11370

  1. no-redeclare ルールのbuiltinGlobalsオプションがデフォルトで有効になりました。グローバル スコープでの変数定義が、設定ファイルで定義したグローバル変数と同じ名前の時に再定義エラーとなります。
  2. /*globals xxx*/のようなコメントによるグローバル変数定義と、他のグローバル変数定義との再宣言をチェックするようになりました。
/*globals Object */ "ERROR: Object は既に定義されています。"

§ comma-dangle ルールのチェックがより厳しくなりました。

? #11519, #11502

comma-dangle ルールのfunctionsオプションがデフォルトで有効になりました。

§ オプションに正規表現を指定できるルールが正規表現構文の誤りに厳しくなりました。

? #11516, #11423

オプションに正規表現パターンを指定できるすべてのルールについて、その正規表現をuフラグがついているものとして解釈するようになりました。これによりサロゲート ペアを正しく扱えるようになるほか、ブラウザ互換性のための緩い構文解釈 (a.k.a. Annex B) が無効化され、より厳密な構文解釈が行われるようになります。

以下のルールが影響を受けます。

§ 設定のrulesプロパティが存在しないルールを無効にする場合にエラーを投げるようになりました。

? #9505

以下のような、存在しないルールに対する無効化設定は、従来は無視されていました。ESLint 6 からはエラーになります。

.eslintrc.json
{
    "rules": {
        "non-existence-rule": "off"
    }
}

§ 設定のparserOptionsプロパティが不正な値にエラーを投げるようになりました。

? #11610, #9687, espree #412, espree #384

  1. sourceType: "module" を指定したにもかかわらず、ecmaVersion5 以下であった (未指定を含む) 場合にエラーになります。ES Modules は ecmaVersion: 2015 とそれ以降のみ利用可能です。
  2. ecmaVersion が不正な値の時にエラーになります。
  3. sourceType が不正な値の時にエラーになります。

なお、これらのエラーはデフォルトのパーサーを利用している場合のみ発生します。

§ 設定のglobalsプロパティが不正な値にエラーを投げるようになりました。

? #11517, #11338 (comment)

設定ファイルのglobals項目について、昔はtrue/falseによって書き込み可能かどうかを指定していました。そして、互換性のために、不正な値も許可されていました (暗黙の型変換でtrueになると書き込み可能として)。

ESLint 6 では、不正な値はエラーになります。厳密には以下の値だけが許可されます。

  • 公式の設定値
    • "writable" (読み書き可能)
    • "readonly" (読み取り専用)
    • "off" (未定義にする)
  • 互換性のために許可される値
    • "writeable" (読み書き可能)
    • "readable" (読み取り専用)
    • "true" (読み書き可能)
    • "false" (読み取り専用)
    • true (読み書き可能)
    • false (読み取り専用)

§ 設定のoverrides[].filesプロパティが dotfiles にマッチするようになりました。

? #11225, #11201

.eslintrc.yml
overrides:
  - files: ["*.ts"]
    parser: "typescript-eslint-parser"

従来は、上記設定は.で始まる TypeScript ファイル (例: .foo.ts) にはマッチしませんでした。ESLint 6 からはマッチするようになります。

§ 設定のexperimentalObjectRestSpreadプロパティが削除されました。

? #11420, #9990

ESLint 5 から非推奨になっていたexperimentalObjectRestSpread設定が削除されました。ecmaVersion: 2018をご利用ください。

§ 共有設定のoverridesプロパティ内の項目が、それをextendsする設定ファイルの項目を上書きしなくなりました。

? #11546, #11510

従来は、共有設定のoverrides内にある設定を普通に書くと、その設定は黙って無視されていました。

extendee.yml
overrides:
  - files: "*.js"
    rules:
      no-console: error
extender.yml
extends: "./extendee.yml"
rules:
  no-console: off # extendee の設定で上書きされてしまう

ESLint 6 からは、あなたの設定ファイルに書かれた設定は常に共有設定より優先されます。

§ スコープ解析 API の中にあるeslintExplicitGlobalCommentプロパティが削除されました。

? #11509, #11370, RFC #17

ESLint のスコープ解析機能の変数オブジェクトが持っていたeslintExplicitGlobalCommentプロパティが削除されました。このプロパティはこれまで正式にドキュメントに書かれたことはありません。

もし、あなたのルールがこのプロパティを利用していた場合、新たに追加されたeslintExplicitGlobalCommentsプロパティを利用するよう修正してください。

TODO: ドキュメントへのリンク

§ ルールのオプション スキーマが不正なデフォルト値を持つ場合にエラーを投げるようになりました。

? #11599, #11473

ESLint v5.14.0 から各ルールの schema 定義でデフォルト値を指定できるようになりましたが、このデフォルト値の書き方が不正な場合にRuleTesterがエラーを出力するようになりました。

§ Linterクラスはカスタムパーサーを自動的に読み込まなくなりました。

? #11388, RFC #7

従来はLinter#verify(code, config)メソッドに渡した設定が未知のパーサーを持っていた場合、require(config.parser)を試みてパーサーを読み込んでいました。この動作が削除されました。

あなたの連携ツールがこの動作に依存していた場合、事前にLinter#defineParser(id,parser)メソッドを用いてパーサーを定義するように修正してください。

§ CLIEngineクラスは過去に利用したプラグインを保持しなくなりました。

? #11546

従来はCLIEngineクラスが過去の検証で利用したプラグインを保持していて、同じインスタンスを使い回す場合に、設定ファイルにpluginsキーがなかったとしても、過去に利用したプラグインを利用できてしまっていました。

ESLint v6.0.0 からは、過去に利用したプラグインを保持しなくなりました。そのため、CLIEngineインスタンスを再利用している場合に、plugins設定が不足している不完全な設定が新たにエラーになる可能性があります。

✨ 本体への機能追加

特になし。

? 新しいルール

特になし。

? オプションが追加されたルール

§ function-paren-newline "multiline-arguments"

? #11406

"multiline"オプションと同様に動作するが、引数が1個の場合は括弧の内側の改行を許可するオプション? が追加されました。

? その他の変更

特になし。

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

JavaScriptで変数のコンテキストを指定してevalする

JavaScriptにはeval関数という、文字列をJavaScriptの式として評価することのできる便利なものがある。

その式の中で変数を参照することは可能だが、通常はソースコード上で定義された変数しか用いることができない。

const x = 1
const y = 2
console.log(eval('x + y'))

だが、その変数の名称や値をコードに直書きするのではなく、プログラム上で動的に設定したい場合がある。
この記事では、その場合のコードの書き方を説明する。

ちなみに、大昔のFirefoxのeval関数にはそのような機能があったが非標準の機能として削除されてしまったようだ。

方法1: with文を使う

変数名と変数値を保持するハッシュを作成し、with文と組み合わせてハッシュのメンバーをトップレベルの変数として扱えるようにする。

const context = {
  x: 1,
  y: 2
}
with (context) {
  console.log(eval('x + y'))
}

これで万事解決…と言いたいところだが、残念ながらwith構文はstrict modeでは使えない。
たとえば以下のコードはエラーとなる。

'use strict'
const context = {
  x: 1,
  y: 2
}
with (context) {
  console.log(eval('x + y'))
}

方法2: Functionコンストラクタを使う

Functionコンストラクタを使うことで、with構文を使わなくても同様のことができる。
こちらの方法はstrict modeでも使うことができる。

Functionコンストラクタは複数の引数を受け取ることができる。最後の引数には関数の本体のコード、それ以外の引数には関数の引数の名称を指定する。コード内では引数の名称を変数として参照することができる。
以下に例を示す。

new Function('x', 'y', 'return x + y')

これを利用して、with文を使わなくても方法1と同様の処理を記述することができる。
変数名と変数値を保持するハッシュを用意するのは同じ。
Functionコンストラクタに、ハッシュのキーの配列の末尾に評価したいコードを連結した配列を渡して関数を生成する。実行環境を選ぶが「...」による記法やObject.keysを使えば楽に書ける。
生成された関数を呼び出し、引数としてハッシュの値の配列を渡す。こちらも同じく「...」による記法やObject.valuesを使えば楽だ。
注意点として、Functionコンストラクタに指定するコードは方法1と異なり「return ~」の形にする必要がある。

'use strict'
const context = {
  x: 1,
  y: 2
}
const func = new Function(...Object.keys(context), 'return x + y')
console.log(func(...Object.values(context)))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Javascript】連想配列の中身の確認方法。

配列と連想配列の違い

まずは配列から。

配列

main.js
var array = [];
    array.push('あ'); //console.log(array[0]); あ
    array.push('い'); //console.log(array[1]); い
    array.push('う'); //console.log(array[2]); う
    array.push('え'); //console.log(array[3]); え
    array.push('お'); //console.log(array[4]); お

  array['あ','い','う','え','お']; //配列の中身

これが一般的な配列ですね。
[0]を一番左としインデックス番号で取得できます。
もちろん、番号ではなく値を指定して取得することも可能です。
ちなみに配列の中身はこんな感じになっています。

なんとなく配列はわかりました。

連想配列

次は連想配列です。

main.js
    var ARRAY = [];
        ARRAY['名前'] = '太郎'; //console.log(ARRAY['名前']); 太朗
        ARRAY['年齢'] = '30'; //console.log(ARRAY['年齡']); 30
        ARRAY['住所'] = '東京'; //console.log(ARRAY['住所']); 東京

       //中身 [名前: "太朗", 年齢: "30", 住所: "東京"];

こんな感じです。
何が違うのかというのは見た感じわかりますが、何がどう違うのか。
配列ではarray[値, 値,・・・]だったのに対し、連想配列はARRAY[キー:値, キー:値・・・]といった中身になっています。

違いはこんな感じです。

要素数の確認方法

次は配列と連想配列の要素数の確認方法です。
配列は.lengthで取得できます。

main.js
 var array = [];
        array.push('あ'); //console.log(array[0]); あ
        array.push('い'); //console.log(array[1]); い
        array.push('う'); //console.log(array[2]); う
        array.push('え'); //console.log(array[3]); え
        array.push('お'); //console.log(array[4]); お

        console.log(array.length); //5

連想配列では違うプロパティを使用します。
それが、Object.keys(配列名).length;です。
何故、配列ではlengthが使えて連想配列では使えないのかというと、配列とオブジェクトの違いです。
どう違うのかと言うと、配列はインデックス番号(添字とも言う)で管理されているのに対し、オブジェクトはプロパティで管理されている(キー:値)と言う違いがあります。

そして.lengthは添字を数えるものなんですね。
一方でObject.keys(配列名).lengthはプロパティを数えます。

これを知らずに.lengthで連想配列(オブジェクト)の要素を取得しようとしても、いつまでたっても0のままなので、気を付けて下さい。

まとめ

・配列はインデックス番号で取得する
・連想配列(オブジェクト)はキーで取得する
・配列とオブジェクトの違いは添字で管理されているのが配列。プロパティで管理されているのがオブジェクト。
・配列には.lengthを使用し、オブジェクトにはObject.keys(配列名).lengthを使う。

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

ファイルアップ、ドラックアンドドロップ メモ

個人的なメモ

ドラックアンドドロップ
ファイル名、ファイルサイズ、ファイル種類の取得
取得した要素の表示
保持しているファイル、各情報を削除

/*---------- ファイルアップロードの処理 ----------*/
$(window).on('load', function() {
  //ドロップエリアを格納
  var fileArea = document.getElementById('upload_area');
  var fileInput = document.getElementById('files');
  var file_name = document.getElementById("file_name");
  var file_size = document.getElementById("file_size");

  //要素にドラッグしているときの処理
  fileArea.addEventListener('dragover', function(evt) {
    evt.preventDefault();
    fileArea.classList.add('dragover');
  });
  //ドロップエリアからドラッグ外れた時
  fileArea.addEventListener('dragleave', function(evt) {
    evt.preventDefault();
    fileArea.classList.remove('dragover');
  });
  //ドロップエリアにドロップされた時
  fileArea.addEventListener('drop', function(evt) {
    evt.preventDefault();
    fileArea.classList.remove('dragenter');
    //ドラックされたファイルの情報を取得
    var files = evt.dataTransfer.files;
    //ファイルを取得する要素を非表示
    $("#upload_area").css('display', 'none');
    //取得したファイルの情報を表示
    $("#Download").css('display', 'block');

    //ファイル情報を格納
    // var filesType = files[0].type;
    var filesType = files[0].type;
    var filesName = files[0].name;
    var filesSize = files[0].size;
    //MBで割るための値
    var mega = 1048576;
    //MBで割
    var megaFilesSize = filesSize / mega ;
    //小数点第3位以下切り捨て
    var megaFilesSize_02 = Math.floor(megaFilesSize * 100) / 100;

    //各要素の情報を書き換える
    $("#file_name").text(filesName);
    $("#file_size").text(megaFilesSize_02);

    //アップされたファイルの判定
    var type = filesType.split('/');
    if (type[1].indexOf('pdf') != -1) {
      $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_pdf');
    } else if (type[1].indexOf('word') != -1 || type[1].indexOf('wordprocessingml') != -1) {
      $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_doc');
    } else if (type[1].indexOf('spreadsheet') != -1 || type[1].indexOf('excel') != -1){
        $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_xls');
    } else if (type[1].indexOf('ppt') != -1 || type[1].indexOf('presentation') != -1 || type[1].indexOf('powerpoint') != -1){
        $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_ppt');
    } else if (type[1].indexOf('zip') != -1){
        $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_zip');
    }
  });

  //ファイルクリックで選択されば場合の処理
  $("#files").on('change', function(evt) {
    fileArea.classList.remove('dragenter');
    //ドラックされたファイルの情報を取得
    var files =  $(this).prop('files');
    //ファイルを取得する要素を非表示
    $("#upload_area").css('display', 'none');
    //取得したファイルの情報を表示
    $("#Download").css('display', 'block');

    //ファイル情報を格納
    var filesType = files[0].type;
    var filesName = files[0].name;
    var filesSize = files[0].size;
    //MBで割るための値
    var mega = 1048576;
    //MBで割
    var megaFilesSize = filesSize / mega ;
    //小数点第3位以下切り捨て
    var megaFilesSize_02 = Math.floor(megaFilesSize * 100) / 100;

    //各要素の情報を書き換える
    $("#file_name").text(filesName);
    $("#file_size").text(megaFilesSize_02);

    //アップされたファイルの判定
    var type = filesType.split('/');
    if (type[1].indexOf('pdf') != -1) {
      $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_pdf');
    } else if (type[1].indexOf('word') != -1 || type[1].indexOf('wordprocessingml') != -1) {
      $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_doc');
    } else if (type[1].indexOf('spreadsheet') != -1 || type[1].indexOf('excel') != -1){
        $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_xls');
    } else if (type[1].indexOf('ppt') != -1 || type[1].indexOf('presentation') != -1 || type[1].indexOf('powerpoint') != -1){
        $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_ppt');
    } else if (type[1].indexOf('zip') != -1){
        $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').addClass('type_zip');
    }
  });


  //取得したファイル領域を削除
  var fileClose = $('#Download .dl_area .dl_area_inner li.dl_area_inner_close');
  var fileClose_02 = $('#Download_02 .dl_area .dl_area_inner li.dl_area_inner_close');
  fileClose.on('click', function() {
    $fileClose();
    $("#Download").css('display', 'none');
    $("#upload_area").css('display', 'block');

  });
  fileClose_02.on('click', function() {
    $fileClose();
  });

  function $fileClose() {
    //取得したファイルの情報を非表示
      $("#Download").css('display', 'none');
    $("#Download_02").css('display', 'none');
    //ファイルを取得する要素を表示
    $("#upload_area").css('display', 'block');
    //取得しているファイルを削除
    $('#files').val('');
    //ファイルアイコンの用のクラスを削除
    $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').removeClass('type_pdf');
    $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').removeClass('type_doc');
    $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').removeClass('type_xls');
    $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').removeClass('type_ppt');
    $('#Download .dl_area .dl_area_inner li.dl_area_inner_icn').removeClass('type_zip');
  }

  //既存ファイルがなければ#Download_02を非表示
  if ($('#Download_02').css('display') == null) {
    $('#upload_area').css('display', 'block');
  }

});
/*---------- ファイルアップロードの処理 ----------*/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザで(WebUSBもActiveXも使わずに)FeliCaリーダーを読み込む

他の投稿はこちら

ブラウザでICカードを読み込む場合、IEでActiveXを使用し、FeliCaリーダーに接続する方法が一般的です。
また最近では、WebUSBでFeliCaリーダーに接続する方法も出てきました。

今回は、WebUSBもActiveXも使用せずに、ブラウザからFeliCaリーダーに接続する方法を紹介します。

概要

概要は以下の通りです。

カード_03.png

  • FeliCaリーダーを読み込むネイティブアプリを作成する
  • ネイティブアプリ上でWebSocketサーバーを立ち上げる
  • ブラウザからネイティブアプリのWebSocketサーバーにローカルホスト接続する
  • ネイティブアプリでFeliCaリーダーからICカードを読み込み、WebSocketサーバーを通して、情報をブラウザに送信する。

デモ

chrome.gif

今回はネイティブアプリ部分をC#で作成しました。
Nfcの読み取り部分はTomoSoft様のwinscard.dllラッパーを使用させて頂きました。

メリット

カード読み取り機能がネイティブアプリとしてブラウザから独立しているため
WebSocket通信が可能なら、どんなブラウザでも動きます。

上記のデモはChromeで撮影していますが
Firefoxでも
firefox.gif
Edgeでも
edge.gif
IEでも
ie.gif
動きます。

まとめ

このように、ネイティブアプリ上に立ち上げたWebサーバーに、ブラウザからローカルホスト接続することで
Webアプリにネイティブアプリの機能を追加することが出来ます。

従来のハイブリッドアプリは、ネイティブアプリ内にWebViewを内包しますが
今回紹介した構成は、ネイティブアプリとブラウザが独立して並列に稼働します。
この構成を、従来のハイブリッドアプリに対して、「パラレルハイブリッドアプリ」と呼んでいます。

パラレルハイブリッドアプリの詳細な説明はこちら
Webアプリとの連携において、通信内容を暗号化したい場合はこちら

サンプル&ソース

サンプル&ソースはこちら。
(動作確認にはFeliCaリーダーが必要です)

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

Github +Gatsby + Netlify CMS で個人ブログを公開する

Gatsby と Netlify CMS を使って、静的サイトを作ってみます。

Netlify公式から、Gatsby + Netlify CMSのテンプレートを使用します。(テンプレート)

アプリケーション準備

# テンプレートをローカルにクローン
git clone https://github.com/netlify-templates/gatsby-starter-netlify-cms.git gatsby-demo-app

# モジュールをインストール
npm install

# 動作を確認
npm start

アプリの起動後、 localhost:8000にアクセスすると、ブログアプリが表示されます。
(アプリが起動しない場合、モジュールのインストールに失敗しているので、node_modules、.cache、publicディレクトリを削除して、もう一度インストールとアプリの起動を行うこと。)
スクリーンショット 2019-03-19 21.47.38.png

GithubへのPush

まずは、Githubにリポジトリを作成します。
スクリーンショット 2019-03-19 21.54.26.png

リポジトリ名はなんでもいいです。
スクリーンショット 2019-03-19 22.01.09.png

アプリケーションをPushする前に、既存の.gitディレクトリを削除しておきます。
既存の.gitを削除後、新規で、git設定ファイルを作成します。

# プロジェクトルートで削除を実行
rm -rf .git

# 新規でgit設定ファイルを作成
git init
git remote add origin git@github.com:Kento75/gatsby-demo-app.git

git設定ファイルの作成後、GithubへPushします。

git add .
git commit -m "first commit"
git push -u origin master

リポジトリにコミットが反映されていることを確認しましょう。
スクリーンショット 2019-03-19 22.09.51.png

Netlifyのセットアップ

Netlifyにアクセスしてユーザー登録を済ませましょう。
サードパーティーまたは、メールでの登録ができます。
スクリーンショット 2019-03-20 20.59.13.png

スクリーンショット 2019-03-20 21.01.19.png

ログイン後、サイト公開を行うGithubリポジトリとの連携を行います。
スクリーンショット 2019-03-20 21.03.06.png
スクリーンショット 2019-03-20 21.04.40.png

スクリーンショット 2019-03-20 21.05.40.png

対象のリポジトリを所有するユーザーまたは、Organizationを選択しましょう。
スクリーンショット 2019-03-20 21.08.40.png

今回は、対象のリポジトリのみを選択します。
スクリーンショット 2019-03-20 21.10.33.png

Push、Pull Request権限を付与します。(Netlify CMSからの記事投稿はPushとなるため)
スクリーンショット 2019-03-20 21.12.34.png

権限付与が完了すると、対象のリポジトリが表示されるので選択します。
スクリーンショット 2019-03-20 21.15.07.png

Netlify公式のテンプレートには、netlify.toml という設定ファイルがすでに用意されているので、
その設定値が反映されます。このままデプロイできるので、「Deploy site」をクリックします。
スクリーンショット 2019-03-20 21.17.33.png

ホーム画面に遷移後、ビルド実行結果が赤枠内に表示されます。クリックすると、詳細画面に遷移します。
スクリーンショット 2019-03-20 21.20.08.png

デプロイが完了したら、「Preview deploy」をクリックして、サイトを確認できます。
スクリーンショット 2019-03-20 21.23.08.png

※ トラブルシューティング
今回のリポジトリでは、デプロイに失敗しないとは思いますが、別のテンプレートや自作のアプリケーションの場合、Nodeのバージョン違いで失敗することがあります。
対処としては、NetlifyのNodeのバージョンをローカルに合わせるという方法があります。
ホーム画面から「Deploys」、「Notifications」の順に選択します。
スクリーンショット 2019-03-20 21.36.30.png
スクリーンショット 2019-03-20 21.37.53.png
「Build environment」にNodeのバージョンを指定することで、ビルドに使用するNodeを固定できます。
スクリーンショット 2019-03-20 21.40.14.png

ここまでトラブルシューティング


スクリーンショット 2019-03-20 21.24.21.png

サイトのURLはランダムに設定されてしまうので、編集します。
ホーム画面の「Domain settings」を選択します。
スクリーンショット 2019-03-20 21.28.10.png

ドメイン設定画面に遷移します。
「Edit site name」を選択して、好きなドメイン名をつけましょう。(.netlify.comは変更できません)
スクリーンショット 2019-03-20 21.29.17.png

記事の投稿機能の確認と拡張

まずは、管理者画面にログインする為の設定を行います。
「Settings」を選択します。
スクリーンショット 2019-03-20 21.48.38.png

管理者画面のログイン機能設定

「https://<ブログのURL>/admin」にアクセスすると、画面中央にログイン用ボタンが表示されるので、クリックします。
スクリーンショット 2019-03-19 21.47.38.png

「Sign up」を選択して、ユーザー名、メールアドレス、パスワードを入力後、「Sign up」ボタンをクリックします。
※ サインアップ確認のメールが設定したメールアドレスに送信されます。
スクリーンショット 2019-03-19 21.47.38.png

送信されてきたメールのリンクをクリックすると管理者画面に遷移します。
スクリーンショット 2019-03-19 21.47.38.png
以降、ログインは先ほど作成したユーザーで行うことになります。

以降、ユーザーの作成を禁止する設定を行いましょう。
スクリーンショット 2019-03-19 21.47.38.png
スクリーンショット 2019-03-19 21.47.38.png

記事の投稿

新たに記事を作成する場合、管理者画面から「New Blog」ボタンをクリックすることでworkpressっぽい記事作成画面に遷移します。
スクリーンショット 2019-03-19 21.47.38.png

スクリーンショット 2019-03-19 21.47.38.png

記事の投稿(GithubにPushすることになります)は、「Publish」をクリック、「Publish now」をクリックすると記事の投稿が完了します。
スクリーンショット 2019-03-19 21.47.38.png
スクリーンショット 2019-03-19 21.47.38.png

「https://<ブログのURL>」にアクセスして、記事一覧を確認しましょう。
スクリーンショット 2019-03-19 21.47.38.png

記事の内容も確認しましょう。
スクリーンショット 2019-03-19 21.47.38.png

補足
記事の投稿は、GithubへのPushになるため、リポジトリのcommitログが1つ増えます。
スクリーンショット 2019-03-19 21.47.38.png

以上で、基本的な設定は完了です。
Netlifyのホスティングには、ドメインの設定やNetlify-Lambda(AWS Lambda)など、色々設定できるので試してみると良いかと思います。

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

ツイッターで本人のツイートのみをJavaScriptで見る方法

きっかけ

ツイッターで本人のツイートのみを見たいと思ったことはないだろうか?

多くがリツイートで占められるツイーターアカウントに遭遇することがある。熱心に全部のツイートを読むのではなく、軽く本人のツイートのみを読みたいという需要だ。

JavaScriptでリツイートを非表示にすることを思いついた。

ツイッターで本人のツイートのみを見る方法が分かりません。
例えば、「堀江貴文(Takafumi Horie) @takapon_jp」は見たい。
しかし、「堀江貴文(Takafumi Horie)さんがリツイート」は見たくないという場合です。

出典: Yahoo!知恵袋

とあるように、ニッチな需要があるようだ。

JavaScriptで非表示に

ChromeではデベロッパーツールのConsole(Firefoxではウェブコンソール)を開いて次のJavaScriptを実行する。

target_name=document.querySelector("b.u-linkComplex-target").textContent
document.querySelectorAll("#stream-items-id>li").forEach(l=>{if(l.getElementsByClassName("tweet")[0].getAttribute("data-screen-name")!==target_name){l.style.display="none"}})

元に戻すには

document.querySelectorAll("#stream-items-id>li").forEach(l=>{if(l.getElementsByClassName("tweet")[0].getAttribute("data-screen-name")!==target_name){l.style.display=""}})

console.logがundefinedになる不具合

ツイッターにはconsole.logの出力がundefinedになる不具合があるようだ。ツイッターを閲覧中にデベロッパーツールかウェブコンソールを開いてconsole.logを実行するとundefinedのみが表示された。ツイッター以外の場所では正常にconsole.logの出力が表示された。この不具合のおかげでツイッターでJavaScriptを書くのが大変だった。

console.logのように使用頻度の高い関数を悪意のある関数と置き換えて、任意のスクリプトを実行させる攻撃があるらしいが、まさかツイッターのような大企業が姑息な技を使うとは思わなかった。console.logの出力がツイッター社に送信されていないことを祈るばかりだ。

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

【Salesforce Commerce Cloud】 デベロッパーのトレーニングを受けてきました。

挨拶

こんにちは、イエノカドです。
今回、Salesforce が提供しているCommerce Cloud Digital Developerのトレーニングを受講してきました。
あまり情報も載っていないので、備忘録として記述していきます。
結果的に4日間のトレーニングを受講した1週間後には、Commerce Cloud Digital Developerの資格を取得できました。

Salesforce Commerce Cloudとは

  • セールスフォース・ドットコムが2016年6月に買収したデマンドウェアの製品を基盤とした製品
  • クラウドECプラットフォーム
  • グローバル企業が導入している(アディダス, クロックス, アシックス

Commerce Cloudを開発するためには

  • Commerce Cloud Digital Developer のトレーニングを受講する(34万掛かります)
  • Commerce Cloudのサービスのライセンス契約をする(大手グローバル企業が主に参入しているためバカ高いと思われる)
  • Salesforce Commerce Cloudのパートナー企業となる(資格保有者が4人いる企業がなれるそうです)

開発環境

  • eclipse(Neon)
  • Windows 10 Pro

トレーニングの4日間の中で何を教えてもらえるか

  • Commerce Cloudのカスタマイズ方法(プログラミング)
    • Javascriptコントローラ
    • ISMLテンプレート

基本的に上記内容の通り、Commerce Cloudはサーバー言語がJavascriptで
テンプレートエンジンがISML(HTMLのようなもの)というものです。

トレーニングの中ではJavascriptでサーバー上の処理を書きますが、
JQueryみたいな書き方は基本なく、生書きするようなイメージです。

  • Javascriptコントローラ
JShowProduct.js
'use strict';

// テンプレートエンジンの呼び出し
var ISML = require('dw/template/ISML');
// 関数を新しい名前で設定できる
var guard = require('storefront_controllers/cartridge/scripts/guard');
// 商品マネージャー
var ProductMgr = require('dw/catalog/ProductMgr');

function start() {
    var parameterMap = request.httpParameterMap;
    // Getパラメータ「pid」から値を取得
    var parameterId = parameterMap.pid.stringValue
    // pidから商品データを取得 
    var product = ProductMgr.getProduct(parameterId);
    if (product == null) {
       // 商品情報がnullの場合、テンプレートにメッセージを渡す
       ISML.renderTemplate('productnotfound.isml', {message: 'product with id ' + parameterId +' not found'});  
    } else {
       // 商品データを取得できた場合、商品データをテンプレート変数に設定
       ISML.renderTemplate('productfound.isml', {myProduct: product});
    }
}

// URLの設定
exports.Start = guard.ensure(['get'], start);
  • Javascriptコントローラが実行されるURL
http://xxxx.demandware.net/on/demandware.store/Sites-xxx-Site/default/JShowProduct-Start
  • ISMLテンプレート(「pid」がnullの場合)
productnotfound.isml
<h1> ${pdict.message}  </h1>
  • 表示結果
    image.png

  • ISMLテンプレート(「pid」がある場合)

productfound.isml
<iscomment>Using a custom tag</iscomment>
<isdecorate template="product/pt_productdetails">
    <iscomment> Using the producttile custom tag </iscomment>
    <isinclude template="util/modules.isml" >   
    <isproducttile product="${pdict.myProduct}" showswatches="${true}" showpricing="${true}" showquickview="${false}">
</isdecorate>

<h1> ${pdict.myProduct.name} </h1>
  • 表示結果 image.png

まとめ

  • 基本的にはJavascriptHTML, CSSの理解があればコードは書けそう。
  • ただし、Commerce Cloudならではのお作法(ISML等)があるため、そこは色々試して知っていかなければならない。
  • Commerce Cloudライセンスを契約することはかなり大手(グローバル)企業でないと無理そう。
  • トレーニング費用が個人ではなかなかお高い額のため、「ちょっとやってみたい」ができない。

Commerce Cloud カスタマイズのための情報等がまだまだ日本語では少ないため、今後も調べていき、発信したいと思います。

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

discord.jsを使って二次元画像を返してみよう!

はじめに

この記事ではdiscord.jsを使い、Discordで「猫耳」と送信するとボットが猫耳女の子の画像を送信するプログラムを書いていく記事です。

環境

Node.js: v10.15.0
discord.js: v11.4.2
node-fetch: v2.3.0

Node.jsのダウンロード

ここから

パッケージのインストール

次に必要なパッケージのインストールをします。

discord.js

npm
npm install discord.js --save

node-fetch

npm
npm install node-fetch --save

サクッと書いてみる

nekogirl.js
const fetch = require('node-fetch');
const { Client, Attachment } = require('discord.js');
const bot = new Client();

bot.on('message', async (message) => {
  const file = await fetch('https://api.nekos.dev/api/v3/images/sfw/img/neko')
    .then(res => res.json())
    .then(json => json.data.response.url);

  if (message.content === '猫耳') message.channel.send(new Attachment(file));
})

bot.login('TOKEN');

Nekos.life

猫耳の女の子の画像をゲットする為にNekos.lifeのAPIを使用しました!いろんな二次元画像を返してくれます。
取得できるのは猫耳の女の子の画像だけではありません。
https://github.com/yuigahamabot/Yuigahama/blob/master/src/lib/util/Nekolife.js

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