20190830のJavaScriptに関する記事は29件です。

Expoのアカウントを作ってReact Nativeの開発環境を整える

この記事は、「【連載】初めてのReact Native + Expo開発環境構築入門」の子記事です。環境などの条件は、親記事をご覧ください。


 React Native を開発するにあたって、モジュールのバージョン管理やらデバッグ環境準備やら公開作業やらから解放されたい時には、Expoが便利です。今回は、Expoを使う前に必要な準備を整えていきます。Windows 10で実施しますが、MacやLinuxでも基本は同じです。

アカウント作成

 Expoを使うには、アカウントを作成する必要があります。

まず、Expo.ioを開いて、「Create an account」をクリックします。
image.png

次に、Email、希望するユーザーID、パスワード2回を入れて、「Create your account」をクリック。
image.png

こんな感じになったらOKです。
image.png

Projectsタブの中は空っぽですが、プロジェクトを追加するようなボタンはここにはありません。その代わりに「Learn how to get started」というリンクがありますね。このリンク先の説明に従って、これから開発するプロジェクトを作ることになりますが、ここではそのうちの不要ないくつかをスキップしながら進めます。

 少し待ったらExpoからEmailの確認が届くので、Email本文内のリンクをクリックして確認を済ませてください。

Node.js を準備する

 Expoを使った開発を行うには、まず自分のローカルPCにNode.jsをインストールする必要があります。Node.jsのバージョンは最新を使え、と書いてありますので、まだNode.jsをローカルPCにインストールしてない場合はNode.jsのサイトからLTS版をインストールしましょう。
image.png

Expo-CLI をインストールする

 では、Node.jsのインストールで一緒にインストールされたnpmを使って、Expo-CLI(Expoのプロジェクトを作ったりパブリッシュしたりするためのコマンドライン機能)をローカルPCにインストールしましょう。
 PowerShellを立ち上げて、
image.png

 以下のコマンドを実行します。

npm install expo-cli --global

 WARNはいくつかでますが、ERRORが出てなければ問題ないはずです。

Expoプロジェクトを格納するフォルダを作る

 今後たくさんのExpoプロジェクトを作ることになるので、プロジェクト用フォルダを作っておきましょう。例えば C:\ExpoProjects など。PowerShellで、以下のようにします。

> cd /
> mkdir ExpoProjects

Expoプロジェクトを作成する

 それではテストで1つ、HelloWorldプロジェクトを作りましょう。PowerShellで先ほど作ったプロジェクト用フォルダに入り、プロジェクトを作ります。

> cd /ExpoProjects
> expo init hello-world

 新規プロジェクト作成では、テンプレートが選択できます。ここでは blank を選びます(Enterキー)。
image.png

 また、プロジェクトの表示名を聞かれるので、キーボードで入力してEnterキーで確定します。ここでは「Hello World!」
image.png

 またもやWARNはいくつかでますが、ERRORが出てなければ問題ないはずです。
 では、Hello Worldプロジェクトを実行してみましょう。

> cd hello-world
> expo start

 ファイアーウォールの警告が出た場合は、通信を許可してください。
 うまくいったら以下のようにブラウザが開いて、QRコードが表示されます。

image.png

Expo Clientアプリで実行を確認

 実行結果の確認は、iPhoneまたはAndroidの実機にExpo Clientというアプリをインストールして、そのアプリでQRコードを読むことで実施します。(今回はiPhoneで試します)
 image.png

 手元のスマホにExpo Clientをインストールし、自分のExpoアカウントでログインしたのち、カメラアプリから先ほど表示したQRコードを読み込んでみてください。

Expo Clientがうまくいかない場合

 Expoは、ローカルPC上で expo start することで、Expoサーバーとして動作します。モバイル(iPhoneやAndroid)上のExpo Clientは、そのサーバーにプロジェクトのアプリを取得しに行って、取得が終わったら実行することになります。つまり、モバイルとローカルPCが直接通信できる状態になければExpo Clientが動作しないことになります。

 うまくいかない場合、以下を確認しましょう。

  • ローカルPCのファイアーウォールで19000や19001のポートが拒否されていないか。
  • ローカルPCの参加するサブネットと、モバイルの参加するサブネットが同じか。
  • ローカルネットワーク上で端末同士の通信が許可されているか。
  • 仮想PC上で expo start したなら、仮想PCとモバイルと同じサブネットのIPアドレスに見えているか。

 ほかに、初回ビルドが遅すぎてClient側が待てなくてタイムアウトすることもあります。。。
 いろいろ試してダメな場合は、ConnectionでTunnelを選んでやり直してみてください。

初回画面

 うまくいくと、Expo Clientの初期画面が表示されます。

image.png

 上に表示されているのが初回だけ表示される内容で、意訳すると「デバイスを振るとこのメニューに戻れるからねー」です。Got itでとじましょう。閉じた後に実際の実行結果が表示されます。
image.png

 真っ白な画面に、「App.jsを開けて開発を始めよう!」と出るだけですが、このメッセージはReactのコードから出力されています!簡単!

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

【連載】初めてのReact Native + Expo開発環境構築入門(Windowsベース)

 この記事では、Windows 10にExpoを使ったReact Nativeの開発環境を1から構築し、サンプルアプリを作成するところまでをゴールとします。
 1つの記事ですべてカバーできないので、いくつかの記事に分けて少しずつ投稿していく連載にしていきます。連載は2019年9月から10月の期間を予定しています。
 なお、筆者はアメリカで開発業務を行っているため、選択するトレンドなどはアメリカ主体となります。

環境

開発環境は、2018年11月時点最新の以下のソフトウェアで構築します。

  • Chrome 最新 (76.0.3809.100 (64-bit))
  • Visual Studio Code 最新 (1.37.1)
  • Node.js + npm 最新 (10.16.3)

なぜExpo?

 モバイルアプリ開発のクロスプラットフォームには、Cordova (ionicやMonaca含む)、Xamarin、最近ではFlutterなどいくつもの選択肢がありますが、その中で以下のメリットが強いときにReact Native + Expoを使うのがよいかと思います。

  • とにかく開発工数を小さくしたい。
  • 業務用アプリなどで、細部にはあまりこだわらない
  • Web開発の知識をなるべくそのまま応用したい。
  • JavaScriptの知識をメインにして作りたい
  • Reactの知識がすでにある
  • デバイスに依存した機能は使わないプロジェクト、またはモックアップ用
  • 実機でデバッグを簡単に実施したい
  • 簡単に配布したい

 同じような条件では、Monacaが競合に入ってきます。実際私も過去にMonacaを使って開発しましたが、Reactのメリットを感じない場合はMonacaでもいいかなと思います。ただ、最近よくReactを使うから今さらjQueryはあまり積極的に使いたくないとか、データ取り扱いでやっぱりReactにメリットを感じるといった場合は、Expoサイコーでよいと思います。

 なお、React Native (=react-native-cli)じゃなくてReact Native + Expoな理由は、この辺りの記事が参考になります。

記事一覧

 連載内容の予定です。途中で大幅に変更する可能性があります。

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

Java Script(条件分岐、オブジェクト、for文、関数の定義方法)について

コンソールに記入/表示する
JavaScriptはブラウザ上で動く言語であるため検証ツールからコンソールを開く事でコードが書く事ができる。

変数宣言の際に定義する必要がある

let 後で置き換える事のできる変数宣言
conset 後で置き換える事ができない変数宣言

条件分岐の仕方

条件式は()で囲む
処理内容は{}で囲む
複数条件の際はelse ifとする 

let num = 60;

if (num % 15 == 0) {
  console.log(num + 'は3と5の倍数です');
} else if (num % 3 == 0) {
  console.log(num + 'は3の倍数です');
} else if (num % 5 == 0) {
  console.log(num + 'は5の倍数です');
} else {
  console.log(num + 'は3の倍数でも、5の倍数でもありません');
}

オブジェクト

データのまとまりのこと。配列は順番での管理だが、オブジェクトはデータを名前と値をセットで管理できる。このセットをプロパティという。
作成方法には{}を使う

例1)

let a = {};

例2)

let a = {name: 'imada'};
        プロパティ名  値     
値の取得、変更方法

名前を取り出したい場合

let a = {name: 'imada', age: '25'};
console.log(a.name);

名前をtanakaに変更したい場合

let a = {name: 'imada', age: '25'};
a.name = 'tanaka';
console.log(a.name);

for文

繰り返し処理はfor文を使う

num = 1
for (let i = 0; i < 100; i += 1){
   console.log(num + '回目')
   num += 1
}

関数の定義方法

function文

function 関数名(引数){
  処理内容
}

※注意)引数が無くても記入しなくてはならない

return文について
Rubyでは関数において最後の戻り値が関数の戻り値として返すがJavaScriptではreturnを明示する必要がある

関数の定義種類

1.関数宣言
2.関数式(無名関数)

 関数宣言
function hello(){
  console.log('hello');
}

 関数式(無名関数)
let hello = function(){
    console.log('hello');
}

役割に大きな違いはないが、関数式にする事で代入や他の関数に渡しやすくなる

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

WYSIWYGなMarkdownエディターを目指してContentEditableおよびexecCommandと真っ向勝負してみる(part 1)

今更ながらマークダウン記法について学び、とても感動しています。
最近はメモや簡単な文章作成にはマークダウンを使っています。

そんな時、自分の理想的なマークダウンエディタがブラウザ上で動けばもっと便利になると思い、少し試してみました。

修正点やもっとこうした方がいいなどございましたらご教授いただけると幸いです。m(_ _)m

どんなエディター?

僕がイメージしているエディターは、よくある画面が二分割されてエディター部分とプレビュー部分に分かれているタイプでは無く、書いた部分がそのままHTMLに変換されるものをいいます。
有名なソフトだと、Typoraなどがあるでしょうか。

ざっと調べてもいろいろな実現方法があるみたいですが、今回は一番単純なようで実は大変そうなcontentEditableexecCommandを用いた実装について試行錯誤しながら試してみました。

contentEditableの仕様

実装に当たってまずはcontentEditableの仕様を調べてみます。
以下のファイルでテストをしてみました。

contenteditable-test.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Test</title>
</head>
<body>
  <div contenteditable='true'>
    <h1>h1</h1>
    <h2>h2</h2>
    <div>div</div>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
    <blockquote>block quote</blockquote>
  </div>
</body>
</html>

マークダウン記法でいうと、文頭に所定のマークを入れる要素たちです。
とりあえずここでこのページの簡単な挙動を調べることができます。

実際にお試しいただけるとわかりやすいかと思いますが、特徴をまとめるとこんな感じです。
なお、僕の環境では最新版のChromeをMac上で使っています。

良さそうな点

  • Shift+Enterでは同じエレメント内で改行、Enterのみだと今のエレメントの次にdivが挿入される
  • リストをEnterのみで改行すると次の行にもリストが自動で挿入される。二度Enterを押すと通常のdivが挿入される

少し気になった点

  • blockquoteではEnterのみを押しても、divの代わりにblockquoteが挿入され抜け出せない。

こんな感じでしょうか。
意外と標準機能でマークダウンエディターっぽい動きができている気がします。
あとはマークダウン記法を読み取って、その都度DOMを置き換えればいいような気がしていました。

この時までは...

とりあえず作ってみた

マークダウン記法の判定

さて、先述の特徴を踏まえて、文頭数文字で判断できる系の以下の四つを実装してみます。

  • 見出し:#, ##, ###, ...など
  • リスト:-
  • 番号付きリスト:1.
  • 引用文:>

キャレットのある位置行の文字は以下のコードで取得できます

window.getSelection().getRangeAt(0).endContainer.data

keyupイベントが起こるたびに現在の行の文字列を取得し、マークダウン記法になっているかを判定します。

const element = document.getElementById('markdown')

element.addEventListener('keyup', function (event) {
  const currentLine = window.getSelection().getRangeAt(0).endContainer.data

  if(currentLine.match(/^#{1}\xA0$/)){ // 見出し
    // '# 'を<h1>に置き換える
  } 
  //
  // 見出し2〜6は{}内の数字を変えるだけ
  //
    else if (currentLine.match(/^>\xA0$/)){ // 引用
    // '> 'を<blockquote>に置き換える
  } else if (currentLine.match(/^\d+\.\xA0$/)) { // 順序付きリスト
    // '- 'を<ul>に置き換える
  } else if (currentLine.match(/^[\-+*]\xA0+$/)) { // リスト
    // '1. 'を<ol>に置き換える
  }
})

はい、これで場合分けができました。

マークダウン記法の変換

contentEditableではEnterを押すと改行されてdivが挿入されます。
このdivをマークダウン記法に対応するDOMに変更するためにexecCommandを用います。

以下は<h1>に変換する例です。

document.execCommand('formatblock', false, 'h1')

第一引数のformatblockMDN web docsによると、

formatBlock
現在の選択範囲を含む行の前後に HTML ブロックレベル要素を追加し、すでに存在する場合は、その行を含むブロック要素に置き換えます (Firefox では <blockquote> は例外です。 — これはブロック要素を囲みます)。引数としてタグ名の文字列が必要です。実質的にすべてのブロックレベル要素を利用することができます。
(Internet Explorer および Edge は見出しタグ H1–H6, ADDRESS, PRE のみに対応しており、 "<H1>" のように山かっこで囲む必要があります。)

とのことです。

要するに、今キャレットがあるDOMを指定の要素に変換するってことだと思います。
(詳しくはわからないので、ご教授いただけると幸いです。)

なお、リストと順序付きリストは別のコマンドを用います。
それぞれ以下のようになります。

// リスト
document.execCommand('insertUnorderedList')

// 順序付きリスト
document.execCommand('insertOrderedList')

マークダウン記法の削除

さて、前述までの方法でマークダウン記法を判定して、HTML要素の変換がすることができましたが、文字として打ち込んだマークダウン記法は残ったままです。
これを削除します。

要素内のテキストを変更するといえば、innerTextが思いつきます。
キャレットがある要素のinnerTextを取得することでうまくいきそうです。

先述したwindow.getSelection().getRangeAt(0).endContainerでは現在のテキスト部分、つまり#textが取得されます。
この親要素が現在の要素になります。
すなわち、マークダウン記法を削除するには以下のコードを用います。

window.getSelection().getRangeAt(0).endContainer.parentNode.innerText = ''

とりあえずできた

以上のことをまとめると以下のコードになりました。

const element = document.getElementById('markdown')

element.addEventListener('keyup', function (event) {
  const currentLine = window.getSelection().getRangeAt(0).endContainer.data

  if(currentLine.match(/^#{1}\xA0$/)){ // 見出し
      document.execCommand('formatblock', false, 'h1')
      clearCurrentLine()
  } 
  //
  // 見出し2〜6は{}内の数字を変えるだけ
  //
    else if (currentLine.match(/^>\xA0$/)){ // 引用
      document.execCommand('formatblock', false, 'blockquote')
      clearCurrentLine()
  } else if (currentLine.match(/^\d+\.\xA0$/)) { // 順序付きリスト
      document.execCommand('insertOrderedList')
      clearCurrentLine()
  } else if (currentLine.match(/^[\-+*]\xA0+$/)) { // リスト
      document.execCommand('insertUnorderedList')
      clearCurrentLine()
  }
})

const clearCurrentLine = () => {
  window.getSelection().getRangeAt(0).endContainer.parentNode.innerText = ''
}

デモはこちらから

次回に向けての修正点

さて、デモをお触りいただくとわかると思いますが、このコードChromeではうまく動きませんorz
(Safariでは動きました。他のブラウザは未検討)
要素は挿入されますが、キャレットが直前の要素に飛んでしまい、しかも挿入された要素にはどうやってもキャレットを合わせることができません。

これを踏まえた以下が次回に向けての修正点です。

  • 挿入された要素にキャレットを合わせることができない
  • blockquoteEnterを押しても抜け出すことができない

次回はこれらの問題点を修正するところから始めます。
アドバイスなどございましたら、コメントください。m(_ _)m

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

Put a semicolon there

備忘録です。

'use strict';

let range = {
  from: 1,
  to: 5,

  [Symbol.asyncIterator]() {
    return {
      current: this.from,
      last: this.to,
      async next() {
        await new Promise(resolve => setTimeout(resolve, 1000));
        if (this.current <= this.last)
          return { done: false, value: this.current++ };
        else
          return { done: true };
      }
    };
  }
}; // here

(async () => { for await (const value of range) console.log(value); })();

here のセミコロンを忘れると、 TypeError: {(intermediate value)(intermediate value)(intermediate value)} is not a function というエラーになります。

javascript の行解釈のヤなところorz

参考にしたページ

文法 | JavaScript プログラミング解説

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

テスト

テスト

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

Jestで非同期処理を順番に評価したい場合の対処法

対象

下記のような非同期処理(Vueのmethodsの中、signInアクションはPromiseを返す)を順番(例えば、dispatch評価後にrouterが呼ばれている事を確認したいなど)に評価したい場合、どうしたら良いかわからない方向け

this.$store.dispatch('signIn', { loginId, password })
  .then(() => {
    this.$router.push('/')
  })
  .catch((error) => {
    this.error = error.message
   }
)

問題点

下記のテストコードだとdipatch実行後,then後の処理に移るまでに後者が評価されてしまいテストが失敗する。

expect($store.dispatch).toHaveBeenCalledWith('signIn', testUserInfo)
expect($router.push).toHaveBeenCalledWith('/')

解消方法

前者のPromiseが評価された後に後者が評価されるようにすれば良い。

await expect($store.dispatch).toHaveBeenCalledWith('signIn', testUserInfo)
expect($router.push).toHaveBeenCalledWith('/')

他、

もう少しスマートで可読性に優れた方法があれば教えて頂けると嬉しいです。

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

Object等の呼び出しでkeyに動的な変数を使う [TypeScript]

やりたいこと

object等の呼び出しで Object[key] のようにするとき、key に動的な変数を入れたい。

問題・エラー

何も考えずに下記のように書くと、

const object = {
    aaa: 'aaaa',
    bbb: 'bbb',
};
// keyには動的に生成された値
const key: string = receivedStringValue;
const value = object[key];

下記のエラー

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ aaa: string; bbb: string; }'.

解決策

object に対して型を定義する

よくない例

このエラーは、key に規則性がない(any型)、であるためにでているエラーなので、下記のような型定義ではエラーは治らない。

interface AB {
    aaa: string;
    bbb: string;
}
const data: AB = {
    aaa: 'aaaa',
    bbb: 'bbb',
};
const key: string = receivedStringValue;
const value = data[key];

結論

keyの型がわかるようなobject の型を定義する

interface StringKeyObject {
    // 今回はstring
    [key: string]: any;
}
const object: StringKeyObject = {
    aaa: 'aaaa',
    bbb: 'bbb',
};
// keyには動的に生成された値
const key: string = receivedStringValue;
const value = object[key];

(StringKeyObject ... もっと良い命名とかあれば教えてください…)

参考

Typescriptで、Object[key]とすると出るIndex signature of object type implicitly has an 'any' type.を正しく回避する

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

マウスカーソルを目で追うGopherくんのサイトをDockerで開発してGitHub Pagesで公開した

完成品

gopher.gif
https://cajonito.github.io/gopher/
大変可愛らしい。

ソースコードはGitHubで公開されています。
https://github.com/cajonito/gopher

この記事

フロントの練習のために上記サイトを作成する過程を紹介します。
Webフロントエンドのプログラミングの練習のために作りましたが記事の内容は環境構築の話がメインです。
登場するものは以下の通りです。

  • GitHub Pagesでサイトを公開
  • Dockerで開発環境を構築
  • クリエイティブ・コモンズ・ライセンス
  • HTML + CSS + Javascript + jQuery でGopherくんを作成

動機と構想

HTMLとCSSの練習がしたい。何かサイトを作ろう。
Gopherくんがマウスカーソルを目で追いかける様を見たいから作ってみよう。

どこかで公開したいけどお金はかけたくない。
出来るだけストレス無く開発したい。
絵描けないけどGopherくんの画像をどうやって用意しよう?

GitHub Pagesで公開

静的なサイトなら無料で公開できる環境は多くありますが、個人的にはGitHub Pagesが一番楽だと思います。

https://pages.github.com/

Hosted directly from your GitHub repository. Just edit, push, and your changes are live.

ということで、GitHubのリポジトリを作っただけでページが公開されて、pushするだけでデプロイされます。すごく楽です。
一応細かい制限はあるみたいです。
https://help.github.com/ja/articles/what-is-github-pages

無料プランの場合、GitHub Pagesはパブリックリポジトリのみで利用出来るようです。

Hello Wolrd

今回は任意のリポジトリ名でdocsディレクトリ以下を公開するという設定にします。
(他の公開形式もありますが今回は割愛します。)

今回はgopherというパブリックリポジトリを作成しました。

$ git clone https://github.com/(ユーザー名)/(リポジトリ名).git
$ cd (リポジトリ名)
$ mkdir docs
$ echo 'hello world' > docs/index.html

ツリー構造はこんな感じ

$ tree
.
├── README.md
└── docs
    └── index.html

1 directory, 2 files

作成したファイルをmasterブランチにpushしておきます。

次にGitHubのリポジトリの設定からmasterブランチのdocsディレクトリ以下を公開するよう設定します。
docsディレクトリが存在しないとこの選択肢は選べないので注意です。

スクリーンショット 2019-08-30 13.28.27.png

これで以下のURLにアクセスするとdocs以下に配置したindex.htmlが表示されるはずです。
https://(ユーザー名).github.io/(リポジトリ名)/

スクリーンショット 2019-08-30 13.57.41.png
あとはdocs/以下を編集してmasterブランチにpushするだけで上記ページは更新されていきます。

Dockerで開発環境を構築

何か編集するたびにpushして確認するのも大変なのでローカルで開発環境を作ります。
Dockerを使うとさくっとサーバーを建てて、必要なくなったらさくっと消せるので便利です。

DockerとDockerComposeをインストール

公式サイトより入手してください。
https://www.docker.com/

設定ファイルを作成

今回はNginxコンテナをローカルで建てて利用します。
docker-compose.ymlとnginx.confを以下の位置に作成します。

$ tree
.
├── README.md
├── docker-compose.yml
├── docs
│   └── index.html
└── nginx.conf

1 directory, 4 files

まずnginx.confを作ります。
僕は詳しくないのですが、ローカルで自分で使うだけなので最低限必要そうなものを設定します。

nginx.conf
server {
    listen 80;
    server_name _;

    root /var/www/html;
    index index.html;

    charset utf-8;
}

次にdocker-compose.ymlを作成します。
DockerComposeは本来複数のコンテナを管理するためのものだった気がしますが、使うコンテナが1つでも簡単に構築、削除が出来るので今回みたいにサーバーを建てる用途の時はよく利用します。

docker-compose.yml
version: '3'

services:
  nginx:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./docs:/var/www/html
  • image
    • nginxコンテナの最新バージョンを使います。
    • バージョンを指定することもできます。
  • ports
    • ホストのポート8080をコンテナの80に関連付けます。
    • ホストで使いたいポートとnginx.confで指定したポートに応じて変更してください。
  • volumes:
    • ホストのnginx.confをコンテナ内の所定の位置にマウントします
    • ホストのdocs/以下をnginx.confで指定したrootディレクトリにマウントします

ポイントはGitHubPagesで公開されるdocs/以下をNginxのルートディレクトリにマウントしていることです。
こうすることでローカルではNginxコンテナで表示を確認し、そのままGitHubにpushして公開する事が出来ます。

これらの設定ファイルも同じリポジトリでバージョン管理すれば楽ちんです。

Dockerコンテナを起動

docker-compose.ymlが配置されたディレクトリで以下のコマンドを実行して起動。

$ docker-compose up -d

以下にアクセスするとGitHub Pagesと同じ表示がされるはずです。
http://localhost:8080

終了する時は同じくdocker-compose.ymlが配置されたディレクトリで以下のコマンドです。

$ docker-compose down

あとはdocs/以下を編集して、上記URLを開いて確認を繰り返して開発していきます。
公開したくなったらmasterブランチにpushしましょう。

マウスカーソルを目で追うGopherくんを作る

お待ちかねです。
画面に大きくGopherくんを表示して、マウスカーソルを目で追いかける感じにしたいです。

画像の用意

https://github.com/golang-samples/gopher-vector
こちらにCC BY 3.0ライセンスで公開されたGopherくんの画像がありました。

クリエイティブ・コモンズ・ライセンスは以下のサイトが非常にわかりやすかったです。
https://creativecommons.jp/licenses/

クリエイティブ・コモンズは、クリエイティブ・コモンズ・ライセンス(CCライセンス)を提供している国際的非営利組織とそのプロジェクトの総称です。
CCライセンスとはインターネット時代のための新しい著作権ルールで、作品を公開する作者が「この条件を守れば私の作品を自由に使って構いません。」という意思表示をするためのツールです。

「CC BY」は

原作者のクレジット(氏名、作品タイトルなど)を表示することを主な条件とし、改変はもちろん、営利目的での二次利用も許可される最も自由度の高いCCライセンス。

とのことなので、サイト上にクレジットを表示して利用させて頂く事にしました。
目を動かすために改変をしますがそれもOKのようです。

画像の加工

元の画像のSVGをテキストファイルで編集して目とそれ以外で2ファイル用意しました。
要素を削除したりサイズをいじるだけなのでフィーリングでなんとかなりました。

ページの作成

最終的な構成は以下のようになりました。

$ tree
.
├── README.md
├── docker-compose.yml
├── docs
│   ├── css
│   │   └── style.css
│   ├── image
│   │   ├── gopher.svg
│   │   └── gopher_eye.svg
│   ├── index.html
│   └── javascript
│       └── main.js
└── nginx.conf

4 directories, 8 files

ソースコードは悪戦苦闘した結果以下のようになりました。
色々セオリーを外してるかもしれませんがとりあえず動きました。
マウスカーソルのx, y座標を入力したら目の位置を計算するコードを書いてjQueryで制御するのが簡単かなと思いました。

index.html
index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="./css/style.css" />
    <title>Gopher Eye Tracking</title>
  </head>

  <body>
    <div id="page">
      <header>
        <div id="header_inner">
          <p>
            Gopherくんは <a href="http://reneefrench.blogspot.com/" target="_blank">Renee French</a> さんがデザインしました。
          </p>
          <p>
            画像は <a href="https://twitter.com/tenntenn" target="_blank">Takuya Ueda</a> さんが作成したものを元に加工したものです。
          </p>
        </div>
      </header>
      <main>
        <div id="gopher_image">
          <div id="gopher">
            <img src="./image/gopher.svg" alt="gohperくん" />
            <div id="gopher_right_eye">
              <img src="./image/gopher_eye.svg" alt="gohperくんの右目" />
            </div>
            <div id="gopher_left_eye">
              <img src="./image/gopher_eye.svg" alt="gohperくんの左目" />
            </div>
          </div>
        </div>
      </main>
      <footer></footer>
    </div>
    <script
      src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
      integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="
      crossorigin="anonymous"
    ></script>
    <script src="./javascript/main.js"></script>
    <script>
      var gopher = new Gopher(
        "#gopher",
        "#gopher_right_eye",
        "#gopher_left_eye"
      );

      $(window).on("load mousemove", function(e) {
        var cX = e.clientX;
        var cY = e.clientY;

        if (!cX) {
          cX = $(window).width() / 2;
          cY = $(window).height() / 2;
        }

        gopher.update(cX, cY).render();
      });
    </script>
  </body>
</html>

css/style.css
style.css
html,
body,
div,
span,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
a,
img,
ul,
li,
table,
tr,
th,
td,
tbody,
footer,
header,
main,
nav,
section,
article {
  margin: 0;
  padding: 0;
  border: 0;
  font-weight: normal;
  list-style: none;
  text-decoration: none;
}

body {
  font-family: "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", "メイリオ",
    Meiryo, Osaka, "MS Pゴシック", "MS PGothic", sans-serif;
  width: 100%;
}

#header_inner {
    padding: 10px;
    font-size: 10px;
    position: fixed;
    top: 0%;
    right: 0%;
    z-index: 100;
}

#gopher_image {
  overflow: hidden;
  position: fixed;
  bottom: 0%;
  width: 100%;
  height: 70%;
}

#gopher {
  position: absolute;   
  left: 50%;
  transform: translateX(-50%);
  width: 1000px;
  z-index: 1;
}

#gopher_right_eye {
  position: absolute;
  text-align: center;
    left: calc(50% - 17%);
    top: 120px;
    width: 120px;
    z-index: 2;
}

#gopher_left_eye {
  position: absolute;
  text-align: center;
    left: calc(50% + 14%);
    top: 120px;
    width: 120px;
    z-index: 2;
}

img {
  width: 100%;
}

javascript/main.js
main.js
class Gopher {
  constructor(gopherSel, rightPupilSel, leftPupilSel) {
    this._dom = $(gopherSel);
    var width = this._dom.width();
    var height = this._dom.height();
    this.x = width / 2;
    this.y = height / 2;

    var range = width * 0.07;

    this.rightEye = new Eye(
      rightPupilSel,
      this.x - width * 0.17,
      this.y - height * 0.33,
      range
    );

    this.leftEye = new Eye(
      leftPupilSel,
      this.x + width * 0.14,
      this.y - height * 0.34,
      range
    );
  }

  update(x, y) {

    var offset = this._dom.offset();
    var baseX = offset.left;
    var baseY = offset.top;

    this.rightEye.update(x - baseX, y - baseY);
    this.leftEye.update(x - baseX, y - baseY);

    return this;
  }

  render() {
    this.rightEye.render();
    this.leftEye.render();

    return this;
  }
}

class Eye {
  constructor(selector, x, y, range) {
    this._pupil = new Pupil(selector, x, y);
    this.x = x;
    this.y = y;
    this.range = range;
  }

  get pupil() {
    return this._pupil;
  }

  update(x, y) {
    const distanceRatio = 0.3;

    var distanceX = x - this.x;
    var distanceY = y - this.y;
    var distance = Math.min(
      Math.sqrt(distanceX ** 2 + distanceY ** 2) * distanceRatio,
      this.range
    );

    var rad = Math.atan2(distanceY, distanceX);

    var newX = this.x + Math.cos(rad) * distance;
    var newY = this.y + Math.sin(rad) * distance;

    this.pupil.update(newX, newY);
    return this;
  }

  render() {
    this.pupil.render();

    return this;
  }
}

class Pupil {
  constructor(selection, x, y) {
    this._dom = $(selection);
    this.update(x, y);
  }

  update(x, y) {
    this.x = x - this._dom.width() / 2;
    this.y = y - this._dom.height() / 2;

    return this;
  }

  render() {
    this._dom.css({
      top: this.y + "px",
      left: this.x + "px"
    });

    return this;
  }
}

感想

フロントエンドの練習をして公開するならDockerとGitHubPagesは手軽で強力だと思いました。
やっぱり手を動かすと得られるものは多いと思うので、手を気持ちよく動かせる環境は大事です。

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

【LT資料】Node.jsとは?

自己紹介

松田尚也(24)
(一応)二年目エンジニア

最近やったこと・やっていること

  • 金融系のREST APIの開発
  • スマホアプリ設計 / 開発

得意・好き

  • JavaScript
  • Node.js
  • CI/CD (Azure)
  • OAuth2.0/(Open ID Connect)

  • ♡ビール♡
    (最近サッポロビールからアサヒ派になりました)


What Node.js?


What Node.js

  1. だいたい ServerSide JavaScript 実行環境
  2. だいたい イベントループ
  3. だいたい ノンブロッキングI/O と 非同期処理

だいたい ServerSide JavaScript 実行環境


ServerSide JavaScript 実行環境

  • 従来ブラウザで動いていたJavaScriptをServerでも動かせるようにした
    WebServer + JavaScript の実行環境がセットになった感じ

  • nodeコマンドを使用できる
    サーバーサイドというよりは、実行環境の提供
    node hoge.js
    これだけで対象ファイルのJavaScriptコードが実行される

  • npm(Node Package Manager)
    非常にかんたんにライブラリ管理を行える


だいたいイベントループ


イベントループ と 背景

Node.jsの開発が開始された2009年

C(クライアント)10K(1万台)問題

クライアントの同時接続数が1万件を超えると、CPU等のリソースは余裕があるにも関わらず、
同時接続のオーバーヘッドにより、速度が遅くなってしまう問題のこと


同時実行(並行処理)の限界

Web及びフロントの発展

Serverに対するリクエスト数が増える

1リクエストに対して1スレッドの処理方式だと、メモリの消費が激しい

結果
1万クライアントを超えると処理が遅くなる


イベントループ

そこで生まれたのがイベントループ
eventLoop.png

  • 一つのスレッドでイベント
  • イベントキューには Node.jsより更に下のOS等がやってくれる処理を登録
    イベントループは常にイベントキューに処理がないか監視し、ある場合は実行する

あれ。。。
遅いんじゃない???


ノンブロッキングI/O と 非同期処理


ノンブロッキングI/O と 非同期処理

同期的なイベントループ方式だと、、、
I/Oが発生するたびに処理がブロック(何もできない状態)されてしまう。


ノンブロッキングI/O と 非同期処理

そこで、ノンブロッキングI/Oな非同期
nonblocking.png

  • I/Oが発生する処理を行うと直ちに処理が返ってくる
  • でも実際には、イベントキューの中で実行待ち状態
  • 実際の処理が終わると、予め処理が終了した際に行うcall back処理が呼び出される

What Node.js

  1. ServerSide JavaScript 実行環境
  2. イベントループでシングルプロセス
  3. ノンブロッキングI/O で 非同期処理

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

【画像を】読み込みが遅い画像をエミュレートする【重くする】

ローカルディスクでは読み込みが速すぎる画像を遅くする

画像の遅延読み込みのコードをを書く際に、ローカルで確認するとき、ディスクから読み込むとあまりにも速すぎて動作の確認が難しいことがあります。そのような際に使えるサービスを見つけました。

Deelay.me

  • HTTPの応答に遅延を発生させるProxy
  • 302 Moved Temporarilyで転送
  • 遅延を指定可能(ミリ秒単位)
  • セルフホスティングも可能(npmパッケージ)
  • 別に画像じゃなくても使える

使い方

遅くする画像をhttp://deelay.me/[遅延(ミリ秒)]/[画像URL]として参照させる

サンプル(5秒)

http://deelay.me/5000/http://placehold.jp/150x150

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

【GAS×Vue.js Webアプリ】google.script.run で async/await を使う

はじめに

某デジタルメディアでGASを使ってを社内ツールを開発している情報系学部4年生です。
WebアプリケーションをVue.jsで書き、バックエンドや各種APIコールなどをGoogle Apps Scriptで処理し、データベースにスプレッドシートを使用することでインフラコスト0で運用できる。

Webアプリケーション側からGAS内のコードを走らせてその戻り値を受け取るために
google.script.run を使用すると楽に実装できるが、サーバサイドのコードを走らせているため、非同期処理となり、複数のスプレッドシートを読み込んで実装する場合はコールバック地獄が発生する。

その問題を解決できたので、簡単にメモ
参考にしたサイト

コード

フロントのVue.jsの処理を以下のように実装する。

hoge.js
let db = new Vue({
  el: '#app',
  data: {
    sheet_a: null,
    sheet_b: null,
  },
  methods: {
    loadData: async function() {
      //ここに書くと順番に実行される
      const sheet_a = await this.getSheetA();
      console.log('成功!取得したデータ: ' + JSON.stringify(sheet_a));
      this.sheet_a = sheet_a;


      const sheet_b = await this.getSheetB();
      console.log('成功!取得したデータ: ' + JSON.stringify(sheet_b));
      this.sheet_b = sheet_b;
      //全部のデータを取得した後に実行したい処理を下に記述

    },

    getSheetA: function() {
      //コード.js内のgetSheetA()を実行
      return new Promise((resolve, reject) => {
        google.script.run
          .withSuccessHandler((result) => resolve(result))
          .withFailureHandler((error) => resolve(error))
          .getSheetA();
      });
    },
    getSheetB: function() {
      //コード.js内のgetSheetB()を実行
      return new Promise((resolve, reject) => {
        google.script.run
          .withSuccessHandler((result) => resolve(result))
          .withFailureHandler((error) => resolve(error))
          .getSheetB();
      });
    }

  },
  //マウント時
  mounted: function() {
    //マウント時にシートを取得
    this.loadData();
  },
});

一応GAS側で実行されるコードも記述しておきます。
※本来はdoGet()関数内にHTMLファイルを返すためのコードを書かないといけません。
GAS×Vueで簡単なWebアプリを作る方法はこちら

コード.gs
function getSheetA() {
  var sheet = SpreadsheetApp.getActive().getSheetByName('シート1');
  var rows = sheet.getDataRange().getValues();
  var keys = rows.splice(0, 1)[0];
  return rows.map(function(row) {
    var obj = {};
    row.map(function(item, index) {
      obj[String(keys[index])] = String(item);
    });
    return obj;
  });
}

function getSheetB() {
  var sheet = SpreadsheetApp.getActive().getSheetByName('シート2');
  var rows = sheet.getDataRange().getValues();
  var keys = rows.splice(0, 1)[0];
  return rows.map(function(row) {
    var obj = {};
    row.map(function(item, index) {
      obj[String(keys[index])] = String(item);
    });
    return obj;
  });
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

レコード詳細画面でボタンを置いて特定のフィールドをmdファイルとしてダウンロード

kintoneの詳細画面とかにボタンを置いて、押すと任意のフィールドをmdファイルとしてダウンロード。
ダウンロードするファイル名はYYYY-MM-DD形式のフィールド(今回はルックアップ)の値を取得して、いい感じにトリミングして「YYYYMMDD.md」としてダウンロード

  • memo_markdownこの部分を任意のコピーしたいフィールドのフィールドコードに変更。
  • ファイル名につかう日付フィールド(今回はルックアップ)をrelease_dateというフィールドコードで配置
  • 「スペース」フィールドの配置が必要。今回はmd_link_sapceというフィールドコードで配置。
  • jQueryは読み込み必要。
sample.js
(function($) {
  'use strict';
  let events = [
    'app.record.detail.show'
  ]

  kintone.events.on(events, function(event){
    let el = kintone.app.record.getSpaceElement('md_link_sapce');
    $('<input />', {
      'type': 'button',
      'name': 'c_button',
      'value': 'Download',
      'class': 'create_button',
    }).on('click', function (resp) {
      var kin_stream = event.record.memo_markdown.value;
      var r_date = event.record.release_date.value;
      r_date = r_date.replace( /-/g , "" );
      SaveToFile(r_date + ".md",kin_stream);
    }).appendTo(el);
  });

  function SaveToFile(FileName,Stream) {
    if (window.navigator.msSaveBlob) {
      window.navigator.msSaveBlob(new Blob([Stream], { type: "text/markdown" }), FileName);
    } else {
      var a = document.createElement("a");
      a.href = URL.createObjectURL(new Blob([Stream], { type: "text/markdown" }));
      //a.target   = '_blank';
      a.download = FileName;
      document.body.appendChild(a) //  FireFox specification
      a.click();
      document.body.removeChild(a) //  FireFox specification
    }
  }

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

kintoneの特定のフォールドをmdファイルとしてダウンロード

kintoneの詳細画面とかにボタンを置いて、押すと任意のフィールドをmdファイルとしてダウンロード。
ダウンロードするファイル名はYYYY-MM-DD形式のフィールド(今回はルックアップ)の値を取得して、いい感じにトリミングして「YYYYMMDD.md」としてダウンロード

  • memo_markdownこの部分を任意のコピーしたいフィールドのフィールドコードに変更。
  • ファイル名につかう日付フィールド(今回はルックアップ)をrelease_dateというフィールドコードで配置
  • 「スペース」フィールドの配置が必要。今回はmd_link_sapceというフィールドコードで配置。
  • jQueryは読み込み必要。
sample.js
(function($) {
  'use strict';
  let events = [
    'app.record.detail.show'
  ]

  kintone.events.on(events, function(event){
    let el = kintone.app.record.getSpaceElement('md_link_sapce');
    $('<input />', {
      'type': 'button',
      'name': 'c_button',
      'value': 'Download',
      'class': 'create_button',
    }).on('click', function (resp) {
      var kin_stream = event.record.memo_markdown.value;
      var r_date = event.record.release_date.value;
      r_date = r_date.replace( /-/g , "" );
      SaveToFile(r_date + ".md",kin_stream);
    }).appendTo(el);
  });

  function SaveToFile(FileName,Stream) {
    if (window.navigator.msSaveBlob) {
      window.navigator.msSaveBlob(new Blob([Stream], { type: "text/markdown" }), FileName);
    } else {
      var a = document.createElement("a");
      a.href = URL.createObjectURL(new Blob([Stream], { type: "text/markdown" }));
      //a.target   = '_blank';
      a.download = FileName;
      document.body.appendChild(a) //  FireFox specification
      a.click();
      document.body.removeChild(a) //  FireFox specification
    }
  }

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

ドットインストールさんの動画をいくつか視聴して、オリジナルで作ってみた「ラストババ抜き」

はじめに

こんにちは。私は今夏休みなので、たくさん時間を確保できる状況にあります。
そこで、HTML、CSS、JavaScriptをしっかり勉強しようと考えました。
私が勉強する教材として利用したのがドットインストールです。

そして今回、ある程度、理解することができ、オリジナルでゲームを作ることができたので、記事にしました。

久しぶりの投稿になりますが、よろしくお願いします。

視聴したレッスン

  • 【旧版】はじめてのHTML
  • 【旧版】はじめてのCSS
  • 【旧版】JavaScript基礎文法徹底マスター
  • 【旧版】JavaScriptでおみくじを作ろう
  • 実践!ウェブサイトを作ろう
  • 実践!スマートフォンサイトを作ろう
  • 詳解HTML 基礎文法編
  • 詳解CSS 基礎文法編
  • はじめてのJavaScript
  • 詳解JavaScript DOM編
  • JavaScriptでスライドショーを作ろう
  • JavaScriptでスロットマシンを作ろう
  • JavaScriptでハイアンドローゲームを作ろう
  • JavaScriptでカウントダウンタイマーを作ろう
  • JavaScriptで5秒当てゲームを作ろう
  • JavaScriptで作る誕生日診断
  • JavaScriptで作る王様ゲーム

上記に書いてあるレッスンを視聴しました。
それぞれの文法については、2~3周ほどしました。
JavaScriptに関しては、私は他の言語も触っていましたので、スムーズに理解することができました。

環境

  • Windows 10 home
  • Google Chrome

「ラストババ抜き」の制作

このゲームはトランプの「ババ抜き」の最後の部分を少し改良して再現したゲームになります。相手のカード枚数が残り2枚で、そこから「JOKER」のカードではなく、「あたり」のカードを選ぶものです。

こちらから遊ぶことができます。→ ラストババ抜き

ソースコード

Githubにもあげてありますが、こちらにもそれぞれ載せておきます。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ラストババ抜き</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <h1>dealer</h1>
        <div class="dealer">
            <ul>
                <li>?</li>
                <li>?</li>
            </ul>
        </div>

        <div id="resetButton">もう一度!</div>
        <div id="result"></div>

        <h1>player</h1>
        <div class="player">あたりを引け!</div>
    </div>

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

</body>
</html>
styles.css
body{
    background-color:#088A4B;
}

.container{
    width:500px;
    margin:100px auto;
    font-weight:bold;
    font-size: 24px;
    text-align:center;
}

h1{
    border:5px solid #fff;
    margin:0 0 30px;
}

ul{
    display:flex;
    justify-content:space-between;
    list-style:none;
    padding:0;
    margin:0;
}

li,.player{
    background-color:skyblue;
    width:100px;
    height:160px;
    border:5px solid #fff;
    border-radius:5px;
    line-height:160px;
    user-select:none;
}

li:hover{
    opacity:0.8;
    cursor:pointer;
    user-select:none;
}

.player{
    margin:0 auto;
    font-size: 12px;
}

#result{
    margin:50px 0;
    height:70px;
    font-size: 48px;
    line-height:70px;
}

#resetButton{
    width:100px;
    height:100px;
    line-height:100px;
    font-size:18px;
    border-radius:50%;
    background-color:purple;
    border:3px solid #fff;
    float:right;
    position:relative;
    top:30px;
    right:60px;
}

#resetButton:hover{
    opacity:0.8;
    cursor:pointer;
    user-select:none;
}

.hidden{
    display:none;
}
main.js
"use strict";

{
    const d_li=document.querySelectorAll(".dealer li");
    const resetButton=document.getElementById("resetButton");
    const result=document.getElementById("result");

    window.onload=function(){
        init();
    }

    for(let i=0;i<d_li.length;i++){
        d_li[i].addEventListener("click",()=>{
            if(!resetButton.classList.contains("hidden")){
                return;
            }

            let rdNum=Math.floor(Math.random()*d_li.length)
            if(i === rdNum){
                gameClear(i);//あたり
            }else{
                gameOver(i);//はずれ
            }

            resetButton.classList.remove("hidden");
        });
    }

    resetButton.addEventListener("click",()=>{
        init();
    });

    function init(){ //初期化
        for(let i=0;i<d_li.length;i++){
            d_li[i].textContent="?";
        }
        resetButton.classList.add("hidden");
        result.textContent="";
    }

    function gameClear(i){ //ゲームクリア
        d_li[i%2].textContent="あたり";
        d_li[(i+1)%2].textContent="JOKER";
        result.textContent="あたり!"
    }

    function gameOver(i){ //ゲームオーバ―
        d_li[i%2].textContent="JOKER";
        d_li[(i+1)%2].textContent="あたり";
        result.textContent="ざんねん!"
    }

}

以上がソースコードになります。
もしなにかあればコメントをください。できる限り対応します。

終わりに

以上で「ラストババ抜き」の制作は終了です。
時間はかかりましたが、やっとオリジナルで作れたので、嬉しいです。

知識を入れていく際なのですが、ドットインストールでは手を動かしながらできるので、効率よく勉強できると思います。
個人的に、CSSの学習は大変でした。次々に新しいことが出てくるので驚きの連続です。

今後も、HTML、CSS、JavaScriptを使ってオリジナルのものを作っていく予定なので、よろしくお願いします。

ここまで読んでいただき、ありがとうございました。

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

Vue.jsによるモーダルウィンドウのサンプル (スタンダードなUIからVueに慣れる③)

Vue.jsでモーダルウィンドウを実装してみた

Vue.jsによるタブ切り替えの実装Vue.jsによるカルーセルスライダーの実装に引継ぎスタンダードなUIに
モーダルウィンドウがありますが、それもVue.jsでは簡単に実装する事ができます。
今回は「vue-js-modal」を使ってモーダルウィンドウを実装してみます。

「vue-js-modal」とは

Vue.jsで簡単にモーダルウィンドウを導入できるプラグインです。
導入は、npmコマンドで導入する方法とCDNより読み込む方法があります。
今回はCDNで簡易的に導入します。

See the Pen GRKvKME by YusukeIkeda (@YusukeIkeda) on CodePen.

npmで導入する場合は下記コマンドを実行

npm install vue-js-modal --save

CDNで導入する場合は下記JSを読み込む

https://cdn.jsdelivr.net/npm/vue-js-modal@1.3.28/dist/index.min.js

HTML

HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>vue-js-modal</title>
<style>
.modal-area-inner{
  padding: 10px;
  box-sizing: border-box;
  position: relative;
}
.modal-area-inner .hide{
  position: absolute;
  top: 10px;
  right: 10px;
  font-size: 12px;
}
</style>
</head>
<body>
<div id="app">
  <button class="button" v-on:click="show">モーダルウィンドウを表示</button>
  <modal name="modal-area">
    <div class="modal-area-inner">
    <p>モーダルウィンドウが表示されました。</p>
    <span class="hide" v-on:click="hide">閉じる[close]</span>
    </div>
 </modal>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-js-modal@1.3.28/dist/index.min.js"></script>
<script src="modal.js"></script>
</body>
</html>

JavaScript

vue-js-modalプラグインの取り込み

modal.js
Vue.use(window["vue-js-modal"].default);

メソッドの用意

modalテンプレートを呼び出すためのメソッドを用意します。

modal.js
var app = new Vue({
  el: '#app',
  methods: {
    show: function() {
      this.$modal.show('modal-area');
    },
    hide: function() {
      this.$modal.hide('modal-area');
    },
  }
});

よく使いそうなオプション

ウィンドウの幅指定

モーダルウィンドウの幅を指定したい場合は:width="300"を付与するだけでOKです。
デフォルトの幅600になっています。

モーダルウィンドウのサイズを可変にする

スライドの速度を指定する場合はは:resizable="true"を指定します。
デフォルト値はfalseです。

モーダルウィンドウを遅らせて表示させる。

モーダルウィンドウを遅延させて表示させたい場合は:delay="1000"を付与します。
ページを表示させた後一定時間後に表示させたい場合などに便利です。

オプション指定例
  <modal name="modal-area" :width="300" :height="300" :resizable="true" :delay="5000">
    <div class="modal-area-inner">
    <p>モーダルウィンドウが表示されました。</p>
    <span class="hide" v-on:click="hide">閉じる[close]</span>
    </div>
 </modal>

上記以外にも以下の表のように様々なオプションが用意されています。

name 必須 初期値
name true [String, Number]
delay false Number 0
resizable false Boolean false
adaptive false Boolean false
draggable false [Boolean, String] false
scrollable false Boolean false
reset false Boolean false
clickToClose false Boolean true
transition false String
overlayTransition false String 'overlay-fade'
classes false [String, Array] 'v--modal'
width false [String, Number] 600
height false [String, Number] 300
minWidth false Number (px) 0
minHeight false Number (px) 0
maxWidth false Number (px) Infinity
maxHeight false Number (px) Infinity
pivotX false Number (0 - 1.0) 0.5
pivotY false Number (0 - 1.0) 0.5
root false Vue instance null

まとめ

Vue.jsではモーダルウィンドウもプラグインを使うことで簡単に実装する事ができました。
標準なUIについてはほぼプラグインが用意されているので、Vue.jsそのもの仕様を理解してオリジナルで作成してみようと思います。
・参考 https://www.npmjs.com/package/vue-js-modal

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

既存のVueプロジェクトにTypeScriptを導入する

TL;DR

  • 既存のVueプロジェクトにTypeScriptを導入したい際にやったことをまとめた
  • vue-clinuxtを利用したプロジェクトではない
  • extendではなくデコレータ方式で記述する

バージョン

  • webpack: 4.33.0
  • typescript: 3.5.3
  • vue-property-decorator: 8.2.1
  • ts-loader: 6.0.4
  • @typescript-eslint/parser: 1.12.0
  • @vue/eslint-config-typescript: 4.0.0
  • vue-eslint-parser: 6.0.4
  • ts-jest: 24.0.2

実際に導入する

Vue

関連モジュール

$ npm i -D typescript vue-property-decorator

型定義ファイル(d.ts)を作成する。

index.d.ts
declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

Webpack

関連モジュールのインストール

$ npm i -D ts-loader

webpack.config.jsの設定

webpack.config.js
...
{
  test: /\.ts$/,
  loader: "ts-loader",
  exclude: "/node_modules/",
  options: {
    appendTsSuffixTo: [/\.vue$/]
  }
}
...

ESLint

関連モジュールのインストール

$ npm i -D @typescript-eslint/parser @vue/eslint-config-typescript vue-eslint-parser

.eslintrc.jsの設定

eslintrc.js
module.exports = {
  ...
  extends: [
    'plugin:vue/recommended',
    'eslint:recommended',
    '@vue/typescript'
  ],
  parser:  'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaFeatures: {
      "legacyDecotators": true
    }
  },
  ...
}

Jest

関連モジュールのインストール

$ npm i -D ts-jest

jest.config.jsの設定

jest.config.js
module.exports = {
  moduleFileExtensions: ["js", "jsx", "json", "vue", "ts"],
  transform: {
    "^.+\\.vue$": "vue-jest",
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
  transformIgnorePatterns: ["node_modules/"],
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/js/$1"
  },
  snapshotSerializers: ["jest-serializer-vue"],
  testMatch: ["**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
  testURL: "http://localhost/",
  setupFiles: [
    "<rootDir>/tests/unit/jest-env.js"
  ],
  globals: {
    "ts-jest": {
      "tsConfigFile": "tsconfig.json"
    }
  },
  watchPlugins: [
    "jest-watch-typeahead/filename",
    "jest-watch-typeahead/testname"
  ],
  "collectCoverage": true,
  "collectCoverageFrom": ["src/js/**/*.{js,vue}"]
};

型定義ファイル

使ってるライブラリの型定義ファイル(@types/〇〇)をインストールしておく。

TypeScriptの設定ファイル

tsconfig.jsonを作成する。

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "strict": false,
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "es2019",
      "esnext",
      "dom"
    ],
    "resolveJsonModule": true
  }
}

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

PWAでオフラインキャッシュ駆動しようとしたらされなかった話とGAのお話

ドキュメントは小説より奇なり

備忘録もかねて書きます。

オフラインの話

https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/offline-for-pwa?hl=ja
上記ページを見るとiOS Safariは最大50MBキャッシュ出来るよ!とありますが、
初回インストール時のキャッシュ容量はリクエスト数に紐づくっぽいです。

厳密に数値を研究出来てないので曖昧なことはかけませんが、画像のSVG化、デコードなどでリクエスト数を減らしてページの総容量をあげたところ120MBまでキャッシュ出来た場合がありました。
セーフゾーンが50MBなのでしょうね。

GAの話

https://developers.google.com/web/tools/workbox/modules/workbox-google-analytics
ChromeでとれるけどSafariで取れないよね、そうだBackground-SyncがiOS対応してないんだった。
というお話。気をつけましょう。

引用元
https://caniuse.com/#search=background-sync

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

Promiseシンプル構文

非同期処理で使われるPromise関数にはnew PromisePromise.resolveで行う方法があります。
良く紹介されている方法はnew Promiseで行う方法が多いですが、シンプルに書きたいときはPromise.resolveで行うとよいです。

よくある構文

var result = new Promise(function(resolve) {
    resolve('Hello1')
})

result
.then(function(data){
    console.log(data)
    return new Promise(function(resolve){
        resolve('Hello2')
    })
})
.then(function(data){
    console.log(data)
    return new Promise(function(resolve){
        resolve('Hello3')
    })
}).then(function(data){
    console.log(data)
})

シンプルな構文

Promise.resolve('Hello1')
.then(function(data){
    console.log(data)
    return Promise.resolve('Hello2')
})
.then(function(data){
    console.log(data)
    return Promise.resolve('Hello3')
})
.then(function(data){
    console.log(data)
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

57日目 Javascriptでファイルは読めるが書き出せなさそう。

Javascriptで作ったサイトにCSVを読み書きする機能を追加したいと思ったらどハマりしてしまいました。

まずは読み込み

function getCsv(url){
  //CSVファイルを文字列で取得。
  var txt = new XMLHttpRequest();
  txt.open('get', url, false);
  txt.send();

  //改行ごとに配列化
  var arr = txt.responseText.split('\n');

  //1次元配列を2次元配列に変換
  var res = [];
  for(var i = 0; i < arr.length; i++){
    //空白行が出てきた時点で終了
    if(arr[i] == '') break;

    //","ごとに配列化
    res[i] = arr[i].split(',');
  }
  return res;
}
getCsv("test.csv");

これでイケると思ったら全然読まない。コンソールにはこんなエラーが出ていました。

 from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

セキュリティ上の理由でローカルファイルへのアクセスをブロックしているようです。
このブロックを許可するオプション付きでChromeを起動して読ませるとできるらしい。

Start Google Chrome on Mac with command line switches

sudo /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args -allow-file-access-from-files

できました!
てすが、いつもこのオプション付きでChromeを立ち上げてくださいっていうの、あんまり現実的ではない感じです。

書き込み

これが大変でした。
Javascriptからファイルを書き出す方法がみつからず。

①ダウンロード
できそう。だけどできればバックグラウンドで動かしたい。

②Node.jsで書き込む
サーバー側で上げられればありかも。

③FilesystemAPI
任意のファイルの編集は無理。サンドボックス内のみ可能。

いろいろ調べたのですがJavascriptだけでファイルの書き込みは難しそうです。

(所要時間2時間)

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

SafariでjQueryのAjaxでファイルをUploadできない。

概要

Ajax経由でファイルをUploadしている部分が、MacのSafariブラウザでだけうまくUploadできない現象があった。
safariのバージョンは2019年8月現在の最新版で、確認に使ったバージョンは12.1.1でした。

結論

SafariでForm要素をclone()で複製した際に、Safariではファイルオブジェクトは空要素としてCloneされるため、挙動が異なります。その結果Safariではファイル要素がアップロードされているようでいて、中身が空になってしまいます。

詳細

不具合が起きたソースの概略は下記の通り。フォームでファイルを選択すると、Submitボタンを押すことなく、Ajaxで送信し、プレビューに表示するような動作となっています。アップロード中を示す動作や、サーバからのエラー処理などは省いています。

HTML部分

<img src="/imgages/default.png" id="preview" alt="アップロード画像">
<form action="/fileupload.php" id="fileupload">
    <input type="file" name="upload" id="upload">
</form>

JS部分

$('#upload').on('change', function(e){
    e.preventDefault();
    // 下記の部分でコピーされるデータがSafariと他のブラウザで異なります。
    let cloneFormData = $('#fileupload').clone(); 
    let fd = new FormData(cloneFormData.get(0));
    let url = '/uploader.php';

    $.ajax({
        url: url,
        type: 'post',
        contentType: false,
        processData: false,
        cache: false,
        data: fd
    }).done(function (e) {
        $('#preview').attr('src', e.file.uploaded_url); // プレビュー用の画像を表示
    });
});

jQueryオブジェクトをcloneしている箇所は下記の部分です。
let cloneFormData = $('#form_input_file').clone();

「ネットワーク」タブから送信しているPOSTデータを見てみると、clone()をした場合、下記のようにfilename が取れていません。
image.png
clone()しないで、フォームオブジェクトを直接POSTした場合は下記のようにfilenameが取れています。
(データ本体はSafariでは見えないようで)
image.png
あとは、HTMLプロトコルのヘッダ部分Content-lengthを見ても、何らか大きめのデータをアップしていることが確認できます。
image.png

ちなみにデバッガで送信するフォームデータを見るとcloneしなかった場合でもした場合でも、下記のように見えるので中身がよくわかりません。
image.png

ということで、Safariで ファイルオブジェクトがあるform を clone した場合、
ファイルオブジェクトは空要素としてCloneされるようです。

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

初心者でも出来るHTML,JavaScript入門5

1.事前知識

事前知識として、上記リンクの内容が必要です。

2.文字列と数値

文字列と数値
<script type="text/javascript">
    alert("文字列:Hello!");
    alert("数値:"+100);
</script>
  • JavaScript では、文字列 を扱う際「' '(シングルクォート)」または「" "(ダブルクォート)」で囲む必要がある。
  • JavaScript では、全ての 数値 は、 整数浮動小数点数 も内部的に区別されない。
  • JavaScript において、 数値 は全て 浮動小数点数 として扱われる。

3.テキストの出力

文字列と数値
<script type="text/javascript">
    document.open();//ドキュメントの出力を開始
    document.write('Hello');//ドキュメントに文字列を書き出す
    document.close();//ドキュメントの出力を終了
</script>
  • document.write() は、 ドキュメント文字列 を書き出す。
  • 文字列 として書き出すため、タグ等も書き出しが可能。

4.記述例

js1_1.html
<!DOCTYPE html>
<html>
    <head>
        <!-- 付加情報 -->

        <!-- タイトル -->
        <title>JavaScriptの説明</title>

        <!-- 文字コードの指定 -->
        <meta charset="utf-8" />
    </head>

    <body>
        <!-- コンテンツ -->

        <!-- インラインスクリプト -->
        <script type="text/javascript">
            document.open();//ドキュメントの出力を開始
            document.write('<h1>h1</h1>');//ドキュメントに文字列を書き出す
            document.write('<h2>h2</h2>');//ドキュメントに文字列を書き出す
            document.write('<h3>h3</h3>');//ドキュメントに文字列を書き出す
            document.close();//ドキュメントの出力を終了
        </script>

    </body>
</html>

中身の文をコピーして、文字コードは UTF-8 を指定し、ファイル名を js1_1.html でデスクトップに保存するとブラウザではこうなります↓↓

  • js1_1.html js1_1.png

画像のようになれば成功です。

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

ES6のimportでFirebase Admin, Cloud Storageを書く

JavaScript -> TypeScript の変換をしていく中で、なかなか見つからなかったのでメモがてら :pencil:

import * as admin from 'firebase-admin';
import * as serviceAccount from './serviceAccountKey.json';
import { Storage } from '@google-cloud/storage';

const params = {
  type: serviceAccount.type,
  projectId: serviceAccount.project_id,
  privateKeyId: serviceAccount.private_key_id,
  privateKey: serviceAccount.private_key,
  clientEmail: serviceAccount.client_email,
  clientId: serviceAccount.client_id,
  authUri: serviceAccount.auth_uri,
  tokenUri: serviceAccount.token_uri,
  authProviderX509CertUrl: serviceAccount.auth_provider_x509_cert_url,
  clientC509CertUrl: serviceAccount.client_x509_cert_url
};

// firebase admin
admin.initializeApp({ credential: admin.credential.cert(params) });

// firestore
const db = admin.firestore();
db.settings({
  timestampsInSnapshots: true
});

// cloud storage
const storage = new Storage({
  credentials: {
    client_email: serviceAccount.client_email,
    private_key: serviceAccount.private_key
  }
});

以上です :hugging:

参考

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

【Markdown】9.9秒で3x3の表を作るChrome拡張を作った。

【Markdown】9.9秒で3x3の表を作るChrome拡張を作った。

時間測定2: 9971.44091796875ms

ボルト並みの速度ですね。

Peek 2019-08-30 11-30.gif

作った表。

ちゃんとした表

時間測定: 40985.581787109375ms

40.9秒なので、400m ウェイド・バンニーキルク さんくらいの記録でしょうか。

Peek 2019-08-30 11-28.gif

緊急度(高) 緊急度(低)
重要度(高) 1 2
重要度(低) 3 4

拡張へのリンク

https://chrome.google.com/webstore/detail/table-to-markdown-creater/hhahknpikfglcfiepppleiobpgdfaank?hl=ja

ちょっとした技術解説

GitHub https://github.com/ykhirao/TableToMd

  • Vue create で作った普通のVue.jsアプリ
  • builddist/ というディレクトリにバンドルしています
  • cp:contents で、その時に必須のファイルをコピーしてます(webpackに任せる方法でやっても可だと思います)
  • zip でChrome拡張配布ようにZIPファイルに変換しています
package.json
  "scripts": {
    "serve": "vue-cli-service serve",
    "lint": "vue-cli-service lint",
    "build": "vue-cli-service build && npm run cp:contents",
    "zip": "bestzip TableToMd.zip dist/*",
    "cp:contents": "cpx src/manifest.json dist/ && cpx src/icon-128.png dist/"
  },

開発してちょっと詰まったところ

manifest.json
{
  "manifest_version": 2,
  "version": "0.1",
  "name": "Table To Markdown Creater",
  "short_name": "TableToMd",
  "description": "Chrome and Firefox exetension",
  "browser_action": {
    "default_icon": {
      "19": "icon-128.png"
    },
    "default_popup": "index.html",
    "default_title": "TableToMd"
  },
  "homepage_url": "https://github.com/ykhirao/TableToMd",
  "icons": {
    "128": "icon-128.png"
  },
  "permissions": [
    "activeTab"
  ]
}

permissionshttp://* とかにすると審査が伸びるみたいな雰囲気でした。途中で activeTab に変更して、3日で審査追えました。
manifest_version2 固定みたいですね。

ちゃんと公式を確認してから開発始めたほうがいいと思います。

Gsuite使っている人向け

Chrome拡張の開発と公開は普通5ドルの課金が必要ですが、同じGsuite内だけの配布なら課金なしで始めれるので、社内ツールとか作るのはとてもよいと思いました。

ゆる募

  • スタイルあててくれる人
  • アイコン作ってくれる人

最後に

読んでいただきありがとうございました!!

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

Vue.jsによるカルーセルスライダーのサンプル (スタンダードなUIからVueに慣れる②)

Vue.jsでカルーセルスライダー実装してみた

以前Vue.jsによるタブ切り替えの実装を紹介しましたが、Vue.jsではカルーセルスライダーも簡単に実装する事ができます。
今回は「Vue Carousel」を使ってカルーセルスライダーを実装してみます。

「Vue Carousel」とは

jQueryのslick.jsのように簡単にカルーセルスライダーを導入できるプラグインです。
導入は、npmコマンドで導入する方法とCDNより読み込む方法があります。
今回はCDNで簡易的に導入します。

See the Pen VwZWJQx by YusukeIkeda (@YusukeIkeda) on CodePen.

CSS

Vue Carouselを導入してブラウザのコンソールよりで要素を確認すると.VueCarousel-slideというクラスが生成されているのが分かります。今回はそのclassにCSSを適用します。

CSS
.VueCarousel-slide {
  font-size:12px;
  display: flex;
  align-items: center;
  justify-content: center;
  height:100px;
  background:#ccc;
  border-right:1px solid #FFF;
  box-sizing:border-box;
}

上記CSSを適用したクラスについてはコンソールにて確認する事ができます。

動的に生成されたHTML要素
<div class="VueCarousel">
  <div class="VueCarousel-wrapper">
    <div class="VueCarousel-inner" style="transform: translate(0px, 0px); transition: transform 0.5s ease 0s; flex-basis: 514.5px; visibility: visible; height: auto;">
   <!--VueCarousel-slideというクラスが動的に生成されている事が確認できる-->
      <div tabindex="-1" aria-hidden="true" role="tabpanel" class="VueCarousel-slide">Slide 1</div>
      <div tabindex="-1" aria-hidden="true" role="tabpanel" class="VueCarousel-slide">Slide 2</div><div tabindex="-1" aria-hidden="true" role="tabpanel" class="VueCarousel-slide">Slide 3</div>
    </div>
</div>
<div data-v-438fd353="" class="VueCarousel-pagination" style="">
  <div data-v-438fd353="" role="tablist" class="VueCarousel-dot-container" style="margin-top: 20px;">
      <button data-v-438fd353="" aria-hidden="false" role="tab" title="Item 0" value="Item 0" aria-label="Item 0" aria-selected="true" class="VueCarousel-dot VueCarousel-dot--active" style="margin-top: 20px; padding: 10px; width: 10px; height: 10px; background-color: rgb(0, 0, 0);">
      </button>
      <button data-v-438fd353="" aria-hidden="false" role="tab" title="Item 1" value="Item 1" aria-label="Item 1" aria-selected="false" class="VueCarousel-dot" style="margin-top: 20px; padding: 10px; width: 10px; height: 10px; background-color: rgb(239, 239, 239);">
      </button>
    </div>
  </div>
</div>

HTML

HTML
<!DOCTYPE html>
<html>
<head>
<title>vue-carousel</title>
<link rel="stylesheet" type="text/css" href="slide.css">
</head>
<body>
<div id="app">
  <carousel>
    <slide>Slide 1</slide>
    <slide>Slide 2</slide>
    <slide>Slide 3</slide>
  </carousel>
</div>

<!-- Vue.jsをCDNで読み込む -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<!-- vue-carousel.jsをCDNで読み込む -->
<script src="https://cdn.jsdelivr.net/npm/vue-carousel@0.18.0/dist/vue-carousel.min.js"></script>
<script src="slide.js"></script>
</body>
</html>

JavaScript

下記は特にオプションを指定しない場合の設定です。

JavaScript
var app=new Vue({
  el: "#app",
  components: {
    'carousel': VueCarousel.Carousel,
    'slide': VueCarousel.Slide
  },
});

よく使いそうなオプション

自動再生

カルーセルスライダーを自動再生させたい場合はautoplay="true"を付与するだけでOKです。
次のスライドに移るタイミングは:autoplayTimeout="1000"で設定します。
デフォルト値は2000となっており、値を小さくするほど次のスライドへ移る時間が短くなります。

ループ再生

カルーセルスライダーをループ再生させたい場合は:loop="true"を指定します。
デフォルト値はfalseとなっております。

スライドのスピード

スライドの速度を指定する場合はは:speed="500"を指定します。
デフォルト値は500となっており、値を小さくするほどスピードは速くなります。

オプション指定例
<carousel :autoplay="true" :autoplayTimeout="1000" :loop="true" :speed="500">
  <slide>Slide 1</slide>
  <slide>Slide 2</slide>
  <slide>Slide 3</slide>
</carousel>

まとめ

CDNを読み込んでから数十分でカルーセルスライダーを作ることができました。
Vueに移行した際もこういったアニメーション要素のあるUIは心配なさそうです。
また他にも色々なオプションがgitHubで確認することができるので、ほとんどの案件で対応できるかと思います。

・参考
https://github.com/SSENSE/vue-carousel

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

toggleメソッドで表示→非表示のイベント実装

こんばんは!今日はjs/jQueryで実装する部分があり、その中でtoggleがめちゃ便利だったので備忘録として。

toggleメソッドとは?

toggleメソッドは表示・非表示を切り替える事ができるjQueryの便利なメソッド。hideとshowが合体したメソッド。

js/jQueryでclickイベントを実装したい

実装したい事

demo
①クリックしたら画像部分が表示される
②もう一度クリックしたら画像部分が非表示になる
※ちなみgyazo gifをqittaで表示する方法は![demo](http://gyazo.com/xxxxxxxxxx/raw)でいけます

GyazoでアップロードしたGifアニメーションを簡単に表示する方法。(URLのみでOK)https://qiita.com/Kobutorina_hato/items/d28d4cc90096e058566d

準備

gem 'jquery-rails'を記述
bunde installします

application.js
//= require activestorage
//= require turbolinks
//= require_tree .
//= require jquery #追加
//= require jquery_ujs #追加

jQueryのライブラリを読み込ませましょう。JavaScriptの前に記述しないとエラーします

app/views/layouts/application.html.erb
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

これを実行する事でスクリプトファイルの読み込むためにスクリプトファイルを自動的に生成してくれます

うまくいかない

sample.html.haml
.new-member-registration-form-content__group.whatnumber-text-right
  .signup-seqcode-text
    %i.fas.fa-question-circle{ style: "color:royalblue;" }
    カード裏面の番号とは?
    .signup-seqcode-info

classのsignup-seqcode-infoをクリックしたらイベント発火させたい

sample.js
$(function() {
  let btn = document.querySelector(".signup-seqcode-text");
  let image = `<div class="signup-seqcode-info is-show">
                  カードの裏面をご残照下さい。
                  <img src="//www
                  ://表示したい画像のURL" alt="" width="240">
              </div>`

  btn.addEventListener("click", function() {
    $(".signup-seqcode-info").html(image);
  });
});

指定のクラス名.signup-seqcode-textをクリックすると変数image.signup-seqcode-info要素の下に表示されるように実装できた。しかし表示はできるがどうすれば非表示にできるか分からない。off()やremove()使ってみたが、動的に出現したhtmlを消す方法がうまくいかず。

toggleメソッドで簡単に実装できた

今日のメインテーマのtoggle登場。色々調べて書いてみて動かなくてを繰り返していたら、toggleメソッドだったら簡単にいけるのではないかと。

sample.haml
.new-member-registration-form-content__group.whatnumber-text-right
            .signup-seqcode-text
              %i.fas.fa-question-circle{ style: "color:royalblue;" }
              カード裏面の番号とは?
              .signup-seqcode-info
              .signup-seqcode-info.is-show
                カードの裏面をご参照下さい
                = image_tag "//www://表示したい画像.png?3312594182", alt: "", width: "240"

表示したい文字カードの裏面をご参照下さいと画像部分=image_tagはhamlに書いておく
.signup-seqcode-info.is-showはクリックして動的に追加したDOM要素に与えるクラス

sample.scss
.signup-seqcode-info.is-show {
  width: 300px;
  display: none;
  position: absolute;
  top: 6px;
  z-index: 1;
  padding: 16px;
  border-radius: 6px;
  background: #eee;
  text-align: center;
  color: #333;
  line-height: 1.5;
  img {
    padding-top: 10px;
  }
}

cssでdisplay:none;にしてデフォルト時は非表示にする
デフォルト時は見えないですが、クリックして出現する時にどこに表示したいかをcssで決めておきましょ。ポイントはz-index: 1;を忘れると他の要素の下に潜り込みますのでご注意を。

sample.js
$(function() {
  $('.signup-seqcode-text').click(function() {
    $('.is-show').toggle();
  })
});

指定したクラス名.signup-seqcode-textをクリックするとイベント発火
toggleはクリック1回目showイベント発火で.is-show部分が表示される
クリック2回目hideイベントで.is-show部分が非表示になる

↓↓完成形をもう一度↓↓
demo

最初は二回clickイベントを作る必要があると思っていたがtoggleメソッド使えば超短い記述で実装できた

toggleは引数も指定できる

【 対象要素.toggle( ミリ秒 ) 】
◯ミリ秒かけて表示・非表示が行われる

【 対象要素.toggle( ミリ秒, 関数 ) 】
◯ミリ秒かけて表示・非表示をした後に関数を実行する事もできます(これ実装してないので詳しくは割愛)

toggleはアニメーションも設定できる

  • 【 fast 】:素早く非表示にする
  • 【 slow 】:ゆっくり非表示にする
  • 【 swing 】:デフォルトの非表示スタイル
  • 【 linear 】:一定の速度で非表示にする

toggle兄弟いろいろ

詳しい説明は省略しますが、「fadeToggle()」,「toggleClass()」,「slideToggle()」はよく使われるいたいです

fadetoggle()はじわーっと表示・非表示にしてくれます

toggleClass()は対象となる要素のclass属性値を追加したり削除したりを繰り返すことが可能なメソッド

slideToggle()は特定の要素を縦方向にアニメーションしながら表示・非表示する事ができます

この辺りはフロント実装で使えそうなので下記サイトを参考に使ってみたいなぁと思います

30分で理解!jQueryのtoggle()と3種のメソッド活用術!
https://www.sejuku.net/blog/40705

まとめ

そういえば以前にもtoggleってなんだろ?ってちょろっと調べた事ありました。ただ実際に動かす機会がないと分かりませんね。js/jQuery苦手なんですけど(他が得意なわけではない笑)、やっぱり見た目の変化が分かりやすいので意図した動きになった時は楽しいです。

終わり

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

オブジェクトの配列から値を取り出す

準備

オブジェクトの配列に以下を用います。
この配列から"hoge1"を取り出す方法を書いていきます。

var arry = [
  { id: 0, name: "hoge0" },
  { id: 1, name: "hoge1" },
  { id: 2, name: "hoge2" },
  { id: 3, name: "hoge3" },
  { id: 4, name: "hoge4" }
];

① 配列の二番目を直接指定する

これはmapメソッドを利用するときに、使えるかと思います。

console.log(arry[1]["name"]);  // hoge1
console.log(arry[1].name);     // hoge1

② idから指定する

これは、①の発展で、特定の値から配列の中のオブジェクトを指定します。
例えば、引数の値を参照にする場合に使うかと思います。

var num = 1;
var idx = arry.findIndex(({ id }) => id === num); // idx: 1

/* 以下、①と同様 */
console.log(arry[idx].name);    // hoge1

最後に

僕は、①の値の指定の仕方が二種類あることに驚きました。配列は、数値を指定するもの!っていう余計な先入観がありました...もっと勉強しないとですね...

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

D3.jsでグリッドを作る

grid.html
<!DOCTYPE html>
<html lang="jp">
  <head>
    <meta charset="utf-8" />
    <script src="https://d3js.org/d3.v5.js"></script>
    <script src="./js/grid.js"></script>
  </head>
  <body>
    <div id="grid"></div>
    <span id="data" style="display:none;">
    [
      [
        {"x":1,"y":1,"width":100,"height":100,"selected":false},
        {"x":101,"y":1,"width":250,"height":50,"selected":false}
      ],
      [
        {"x":101,"y":51,"width":100,"height":50,"selected":false},
        {"x":201,"y":51,"width":150,"height":50,"selected":false}
      ],
      [
        {"x":1,"y":101,"width":50,"height":300,"selected":false},
        {"x":51,"y":101,"width":50,"height":150,"selected":false},
        {"x":101,"y":101,"width":100,"height":150,"selected":false},
        {"x":201,"y":101,"width":150,"height":150,"selected":false}
      ],
      [
        {"x":51,"y":251,"width":50,"height":150,"selected":false},
        {"x":101,"y":251,"width":100,"height":150,"selected":false},
        {"x":201,"y":251,"width":150,"height":150,"selected":false}
      ]
    ]
    </span>
  </body>
</html>
grid.js
document.addEventListener("DOMContentLoaded", function () {
    // html内のJson文字列をデシリアイズします。
    const gridData = JSON.parse(document.querySelector("#data").textContent);

    const grid = d3
        .select("#grid")
        .append("svg")
        .attr("width", "510px")
        .attr("height", "510px");

    const row = grid
        .selectAll()
        .data(gridData)
        .enter()
        .append("g")
        .attr("class", "row");

    const noSelectedColor = "#ffffff";
    const selectedColor = "#2C93E8";
    const column = row
        .selectAll()
        .data(d) { return d; })
        .enter()
        .append("rect")
        .attr("class", "square")
        .attr("x", function (d) { return d.x; })
        .attr("y", function (d) { return d.y; })
        .attr("width", function (d) { return d.width; })
        .attr("height", function (d) { return d.height; })
        .style("fill", noSelectedColor)
        .style("stroke", "#222")
        .on('click', function (d) {
            d.selected = !d.selected;
            let cell = d3.select(this);
            if (d.selected) {
                cell.style("fill", selectedColor);
            } else {
                cell.style("fill", noSelectedColor);
            }
        });
});

Let's Make a Grid with D3.js

解説

上から順番に解説します。

const grid = d3
    .select("#grid")
    .append("svg")
    .attr("width", "510px")
    .attr("height", "510px");
<!-- 実行後 -->
<div id="grid">
  <svg width="510px" height="510px">
</svg></div>

selectメソッドのセレクターはjsやcssなどと同じです。
取得した d3.selection オブジェクトに append メソッドで svg タグが挿入されます。append をすることでタグが実際に生成されます。append の戻り値は d3.selection オブジェクトです。
attr メソッドで属性を追加します。

const row = grid
    .selectAll()
    .data(gridData)
    .enter()
    .append("g")
    .attr("class", "row");
<div id="grid">
  <svg width="510px" height="510px">
    <g class="row"></g>
    <g class="row"></g>
    <g class="row"></g>
    <g class="row"></g>
  </svg>
</div>

data メソッドでグリッドのオブジェクトを割り当てます。サンプルでは Json 文字列からオブジェクトを生成しています。
enter メソッドで data メソッドで割り当てられたオブジェクトがd3.selectionオブジェクトにセットされます。
append("g")でオブジェクトの各配列要素に g 要素 をバインドし、タグを生成します。
(よくあるサンプルだと始めの selectAll で selectAll("row") などして、空の selection オブジェクトを取得するのを見かけます。ですが、引数は空でいいと思うのですが、わざわざ"row"などして空振りする効果がよくわかりません。)

d3.jsのセレクタとタグ操作
D3.jsで<g>要素を活用する

const noSelectedColor = "#ffffff";
const selectedColor = "#2C93E8";
const gridStrokeColor = "#222222";
const column = row
    .selectAll()
    .data(d => d)
    .enter()
    .append("rect")
    .attr("class", "square")
    .attr("x", d => d.x)
    .attr("y", d => d.y)
    .attr("width", d => d.width)
    .attr("height", d => d.height)
    .style("fill", noSelectedColor)
    .style("stroke", gridStrokeColor)
    .on('click', function (d) {
        d.selected = !d.selected;
        let cell = d3.select(this);
        if (d.selected) {
            cell.style("fill", selectedColor);
        } else {
            cell.style("fill", noSelectedColor);
        }
    });
<div id="grid">
  <svg width="510px" height="510px">
    <g class="row">
      <rect class="square" x="1" y="1" width="100" height="100" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
      <rect class="square" x="101" y="1" width="250" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
    </g>
    <g class="row">
      <rect class="square" x="101" y="51" width="100" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
      <rect class="square" x="201" y="51" width="150" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
    </g>
    <g class="row">
      <rect class="square" x="1" y="101" width="50" height="300" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
      <rect class="square" x="51" y="101" width="50" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
      <rect class="square" x="101" y="101" width="100" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
      <rect class="square" x="201" y="101" width="150" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
    </g>
    <g class="row">
      <rect class="square" x="51" y="251" width="50" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
      <rect class="square" x="101" y="251" width="100" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
      <rect class="square" x="201" y="251" width="150" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
      </rect>
    </g>
  </svg>
</div>

ここでは、先程 g 要素にバインドした配列の行オブジェクトの要素から rect を生成します。x や y、width、height、fill、stroke は SVG の属性です。
jQuery のように、on メソッドでイベントを付与できます。ここでは、クリックすると背景色を切り替えるイベントを付与しています。
(ただ、ここでもう一度 data(d => d).enter() しないといけないのがよくわからない。)

完成形

image.png

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

TypeScriptで書いてwebpackでビルドしたjsファイルのエラーをわかりやすくする

Why

webpackでバンドルしてるのでスタックトレースを見てもtsファイルでのエラー箇所がわからない・・ :tired_face:

What

node-source-map-supportをインストールして、tsconfig.json, webpack.config.jsを修正します :muscle:

node-source-map-supportのインストール

$ npm install source-map-support

tsファイルの修正

webpackのエントリポイントにしているtsファイルに以下を追加します。

app.ts
import sourceMapSupport from 'source-map-support'
sourceMapSupport.install()

設定ファイルの修正

webpack, TypeScriptの設定ファイルを修正します。

webpack.config.js
module.exports = {
    mode: 'development',
    target: 'node',
+   devtool: 'inline-source-map',
    ...
}

↑ちなみに、 devtool: 'inline-source-map'でも動きました。

tsconfig.js
{
  "compilerOptions": {
+   "sourceMap": true
   ...
  }
}

これで、webpackでビルドし直すと、エラー時にtsファイルの行数も教えてくれるようになりました :tada:

こんなかんじ :point_down: (app.tsの73行目でエラーになってることがわかる)

at Object.<anonymous> (/path/to/project/dist/webpack:/src/app.ts:73:4)

以上です :hugging:

参考

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