20200113のvue.jsに関する記事は14件です。

TypeScriptでユーザー定義のエラーを実装する

例外周りにあまり知見がなかったのでJavaScriptの標準のエラーから、TypeScriptでユーザー定義の例外を実装する方法までを調べました。

JavaScriptの標準のエラーオブジェクトについて

JavaScriptの標準のエラーオブジェクトは以下の表の通り7つあります。

エラーオブジェクト 説明
Error ランタイムエラーが発生した時に投げられます。ユーザー定義の例外の基底オブジェクトとして使用することもできます。
EvalError グローバルな eval() 関数に関連するエラーを示します。この例外はもう JavaScript から投げられませんが、EvalError オブジェクトは互換性のために残っています。
RangeError 値が配列内に存在しない、または値が許容範囲にない場合のエラーを表します。
ReferenceError 存在しない変数が参照された場合のエラーを表します。
SyntaxError 構文的に不正なコードを解釈しようとした場合のエラーを表します。
TypeError 値が期待される型でない場合のエラーを表します。
URIError グローバル URI 処理関数が誤った使い方をされたことを示すエラーです。

こんなに定義されていたのですね。恥ずかしながらRangeErrorしか知りませんでした:scream_cat:

TypeScriptでユーザー定義の例外を実装

先ほどの表ではAPIの通信エラーがあった場合、適切なエラーが見つからないのでErrorオブジェクトを使うことになります。

Errorオブジェクトは、ユーザー定義の例外の基底オブジェクトとして使用することもできます。

とのことなので、より適切なHttpRequestErrorというエラーを作ってみましょう。
まずErrorオブジェクトを継承しApplicationErrorをユーザー定義の基底オブジェクトを作成します。

ApplicationError.ts
export default class ApplicationError extends Error {

  constructor(public message: string) {
    super(message);
    this.name = "ApplicationError";
  }
}
// 最初はErrorインタフェースを実装していたのですが、
// 後ほど使うSentry.captureExceptionでErrorを継承する必要があった
// export default class ApplicationError implements Error {

ApplicationErrorを継承したHttpRequestErrorを作成。

HttpRequestError.ts
import ApplicationError from "./ApplicationError";

export default class HttpRequestError extends ApplicationError {

  constructor(public message: string, public status: number) {
    super(message);
    this.name = "HttpRequestError";
    this.message = `${status}: ${message}`
  }
}

throw new HttpRequestError("管理者に連絡してください", 500)

標準エラーとユーザー定義エラーにより異なるアクションを実行

標準エラーとユーザー定義のエラーに基づいて、エラーハンドラー内で異なるアクションを実行します。(Vueのエラーハンドラーについてはコチラ

errorHandler.ts
Vue.config.errorHandler = (error, vm, info) => {
  // ApplicationErrorの場合はユーザーにアラートを表示し
  if (error instanceof ApplicationError) {
    alert(error.message);
    if (error.stack) {
      Sentry.captureException(error);
    }
  }
  // ビルトインのエラーについてはユーザーに表示せず、Sentryなどで開発者に通知する
  if (error instanceof Error) {
    if (error.stack) {
      Sentry.captureException(error);
    }
  }
};

さいごに

JavaScriptの例外のベストプラクティスを探して見つけることができませんでした。
自分で調べた結果このような形に落ち着いたのでしばらくはこの形を使用していく予定です。

間違いやアドバイスがあれば教えていただけると嬉しいです!

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

【JAMStack】Nuxt×Contentful×Netrify�でポートフォリオサイトを一新。使用技術をまとめる

先日、JAMStackなポートフォリオサイトに一新しました。以下のリンクから確認できます。
Gurutaka Portfolio

使用した技術はこちら!

  • Nuxt
  • Contentful
  • Netrify

作り直した理由は以下のとおりです。

  • JAMStackというアーキテクチャが気になったこと
  • Worksコンテンツをローカルで編集するのが億劫
  • ヘッドレスCMS(Contentful)に興味あり

ここでは、JAMStackに初めて触れた私が、ポートフォリオサイト開発で得られた技術的知見を紹介していきます。
誤りなどありましたら、コメントでご指摘いただけると幸いです。

基本用語について

JAMStackとは?

JAMStackとは、アーキテクチャの1つで、JavaScript APIs Markup になぞった名前です。

簡単にまとめると、こんなイメージです。

  • JavaScript:クライアントサイド
  • APIs:再利用可能
  • Markup:html(静的ファイル)のみの構成

JavaScript側でフロント周りを実装し、コンテンツはAPIで取得。そして、レンダリングでhtmlをブラウザ上に描画します。

メリットとして、

  • パフォーマンス良い
  • ハイセキュリティ
  • スケーリングが容易
  • 開発体験が良い

参考:【入門】Nuxt.js + Contentful + Netlify で始める、JAMstack な CMS 構築 - Qiita

静的ファイルを事前にビルドして描画するので、描画まで早いです。
(私のサイトはまだまだサイト高速化の改善の余地ありですが、SPAだった時よりはかなり早くなっています)

特に感じたのは開発体験の素晴らしさ!コンテンツもAPIで取得するだけなので、フロントに集中できます。

参考:公式 Jamstack | JavaScript, APIs, and Markup

ヘッドレスCMSとは?

JAMStackについて調べてみると、ヘッドレスCMS(Contents Management System)というワードが出てきます。一言でいうと、コンテンツだけ管理するシステムです。ヘッドレスは、ビューがないという意味合いで使われています。

クライアント側はAPIでコンテンツを取得すれば良いので、クライアントサイドに依存することなく開発できます。

引用:ヘッドレスCMSとは?ヘッドレスCMSの概要について調べてみた - Try T.M Engineer Blog

今回はヘッドレスCMSを提供しているサービスの1つであるContentfulを使用。開発もフロントだけなので工数もかかりませんでした。

3.png
引用:Contentfulとは?ヘッドレスCMSって何?誰でもわかるように解説してみる | ぐるたかログ

参考:JAMstackってなに?実践に学ぶ高速表示を実現するアーキテクチャの構成 - エンジニアHub|若手Webエンジニアのキャリアを考える!

実装周りの技術について

アーキテクチャ

5.png

ポートフォリオサイトのアーキテクチャです。

  • クライアントサイド:Nuxt
  • ヘッドレスCMS:Contentful
  • サーバー:Netrify

Webhookを使い、Githubで管理しているソースが変更、またはContentfulの特定コンテンツが変更・追加・削除されると、Netrifyに自動で通知され、ビルド&デプロイされて最新版になるように設定しています。

Contentful

Contentfulには専門用語があり、所見だとわかりにくいので、図でまとめてみました。

以下キーワードです!
- スペース
- コンテンツモデル
- コンテンツ

4.png

名前 意味
スペース Githubでいうリポジトリのような役割。コンテンツを管理する場所
コンテンツモデル コンテンツの雛型(テンプレート)
コンテンツ コンテンツモデルから作り出された情報(コンテンツ)

簡単にまとめると
- スペースで色んなコンテンツを管理(最初に自動でスペース1つ分作られます)
- コンテンツモデルでコンテンツの雛型を作成(例えば、ブログ投稿のテンプレートなど)
- コンテンツモデルからコンテンツを作っていく(ブログでいえば、投稿記事など)

以下の記事にcontentfulの始め方や概念をまとめているので、参考になれば嬉しいです。
- Contentfulとは?ヘッドレスCMSって何?誰でもわかるように解説してみる | ぐるたかログ
- 【初心者向け】Contentfulの始め方。コンテンツモデルの作成や投稿、APIキーの場所などまとめ【使い方】 | ぐるたかログ

使用料金に関しては、個人でやる分には無料枠で十分いけます。

24個のコンテンツタイプ
5000個のレコード
10人のユーザー
1つのロール(ユーザー権限)
2つの環境(master/stagingとか)
2つのロケール(多言語対応)

引用:Contentfulの料金と使い方を整理しつつ、Nuxt.jsと組み合わせてブログを作る - Qiita

他にもコンテンツモデルのバリデーション機能も豊富で、コンテンツ同志の紐付け(reference)機能も使えます。更にブランチ機能まであるという!

わりと多機能なので、実際に使いながら色々と試してみると良いでしょう!

Nuxt

Nuxtで静的ファイルをビルド(プリレンダリング)するのは簡単です。以下のコマンドを使用すればOK

$ npm run generate 

NuxtにContentfulを導入する方法はブログに図解でまとめています。データ取得までのサンプルコードも載せているので、ご興味ある方はチェックしていただけますと嬉しいです。

NuxtにContentfulを設定する方法。導入からデータ取得までの流れをまとめてみた【スクショあり】 | ぐるたかログ

pages/index.vue
<script>
import contentfulClient from '@/plugins/contentful'

export default {
  asyncData({ env }) {
    return contentfulClient
      .getEntries()
      .then((entries) => {
        console.log(entries.items)
        return {
          ssample: entries.items
        }
      })
      .catch(console.error)
  }
}
</script>

ブログには掲載していないのですが、asyncDataでcontentfulから複数のHTTPリクエストを扱う場合は、Promise.allを使うと、スマートにかけます。

sample.vue
<template>
  <div>
    <!-- render data of the person -->
    <h1>{{ person.fields.name }}</h1>
    <!-- render blog posts -->
    <ul>
      <li v-for="post in posts">
        {{ post.fields.title }}
      </li>
    </ul>
  </div>
</template>

<script>
  import {createClient} from '~/plugins/contentful.js'

  const client = createClient()

  export default {
    // `env` is available in the context object
    asyncData ({env}) {
      return Promise.all([
        // fetch the owner of the blog
        client.getEntries({
          'sys.id': env.CTF_PERSON_ID
        }),
        // fetch all blog posts sorted by creation date
        client.getEntries({
          'content_type': env.CTF_BLOG_POST_TYPE_ID,
          order: '-sys.createdAt'
        })
      ]).then(([entries, posts]) => {
        // return data that should be available
        // in the template
        return {
          person: entries.items[0],
          posts: posts.items
        }
      }).catch(console.error)
    }
  }
</script>

引用:Integrate Contentful with Nuxt.js – Contentful

また、ビルドする前にOGPなど色々と設定しました。その際に、こちらの記事が丁寧にまとめられていて、凄く参考になりました!

余談ですが、お恥ずかしい話、OGP設定でprefix属性を忘れて、四苦八苦しました。OGP生成する方は要チェックです!
OGPにつけるprefix属性とは何か調べてみた。 - Qiita

また、ビルドした後の静的ファイルをNuxtと切り離して確認する際は、http-serverを使っています。
nuxt generateで生成した静的ファイルをhttp-serverでホスティングする #nuxtjs - ジムには乗りたい

Netrify

以下の記事に従って、進めていけば超簡単にデプロイできます!
【Netlify】Github連携でWEBサイトを公開する - Qiita

Contentful WebhookでNetrifyと連携する方法は自分のブログにまとめています!
【Contentful Webhook】コンテンツ変更を検知し、Netrifyで自動でビルドさせる方法 | ぐるたかログ

またNetrifyにはNetlify Formsがあります。せっかくなので、Netlify Formsを使って、お問い合わせフォームをポートフォリオサイトに組み込みました。JavaScriptだけで、お問い合わせフォームもできるなんてビックリです。

こちらもNetrify Formsでお問い合わせフォームを設置する方法を書いたので、参考になればと思います。
Netlify Formsを使って、お問い合わせフォームを設置する方法 | ぐるたかログ

そして最後に、詰まった点を1つ紹介。 Netrifyにビルドする際に、env周りで設定したContentful関連の環境変数をNetrifyにも必ず登録しましょう。

登録しないと、APIでコンテンツ情報を取得できなくなります!私は初心者すぎて、小一時間くらい消耗しました…。

Build___deploy___Settings.png

 最後に

JAMStackなポートフォリオサイトを開発してみて、感動しました。JavaScriptだけでここまで作れるなんて、すごい世界だなと。

そして可能性を感じました。

こちらの記事にも書かれていますが、今後ノーコード(GUI)でフロントが作れてAPIでコンテンツ取得までできれば、エンジニアでなくても、メディアサイトのようなものが作れるわけです。

今後のJAMStackの動向も楽しみです!

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

参考リンク

【入門】Nuxt.js + Contentful + Netlify で始める、JAMstack な CMS 構築 - Qiita

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

【JAMStack】Nuxt×Contentful×Netrify�でポートフォリオサイトを一新。使用技術まとめてみる

先日、JAMStackなポートフォリオサイトに一新しました。以下のリンクから確認できます。
Gurutaka Portfolio

使用した技術はこちら!

  • Nuxt
  • Contentful
  • Netrify

作り直した理由は以下のとおりです。

  • JAMStackというアーキテクチャが気になったこと
  • Worksコンテンツをローカルで編集するのが億劫
  • ヘッドレスCMS(Contentful)に興味あり

ここでは、JAMStackに初めて触れた私が、ポートフォリオサイト開発で得られた技術的知見を紹介していきます。
誤りなどありましたら、コメントでご指摘いただけると幸いです。

基本用語について

JAMStackとは?

JAMStackとは、アーキテクチャの1つで、JavaScript APIs Markup になぞった名前です。

簡単にまとめると、こんなイメージです。

  • JavaScript:クライアントサイド
  • APIs:再利用可能
  • Markup:html(静的ファイル)のみの構成

JavaScript側でフロント周りを実装し、コンテンツはAPIで取得。そして、レンダリングでhtmlをブラウザ上に描画します。

メリットとして、

  • パフォーマンス良い
  • ハイセキュリティ
  • スケーリングが容易
  • 開発体験が良い

参考:【入門】Nuxt.js + Contentful + Netlify で始める、JAMstack な CMS 構築 - Qiita

静的ファイルを事前にビルドして描画するので、描画まで早いです。
(私のサイトはまだまだサイト高速化の改善の余地ありですが、SPAだった時よりはかなり早くなっています)

特に感じたのは開発体験の素晴らしさ!コンテンツもAPIで取得するだけなので、フロントに集中できます。

参考:公式 Jamstack | JavaScript, APIs, and Markup

ヘッドレスCMSとは?

JAMStackについて調べてみると、ヘッドレスCMS(Contents Management System)というワードが出てきます。一言でいうと、コンテンツだけ管理するシステムです。ヘッドレスは、ビューがないという意味合いで使われています。

クライアント側はAPIでコンテンツを取得すれば良いので、クライアントサイドに依存することなく開発できます。

引用:ヘッドレスCMSとは?ヘッドレスCMSの概要について調べてみた - Try T.M Engineer Blog

今回はヘッドレスCMSを提供しているサービスの1つであるContentfulを使用。開発もフロントだけなので工数もかかりませんでした。

3.png
引用:Contentfulとは?ヘッドレスCMSって何?誰でもわかるように解説してみる | ぐるたかログ

参考:JAMstackってなに?実践に学ぶ高速表示を実現するアーキテクチャの構成 - エンジニアHub|若手Webエンジニアのキャリアを考える!

実装周りの技術について

アーキテクチャ

5.png

ポートフォリオサイトのアーキテクチャです。

  • クライアントサイド:Nuxt
  • ヘッドレスCMS:Contentful
  • サーバー:Netrify

Webhookを使い、Githubで管理しているソースが変更、またはContentfulの特定コンテンツが変更・追加・削除されると、Netrifyに自動で通知され、ビルド&デプロイされて最新版になるように設定しています。

Contentful

Contentfulには専門用語があり、所見だとわかりにくいので、図でまとめてみました。

以下キーワードです!
- スペース
- コンテンツモデル
- コンテンツ

4.png

名前 意味
スペース Githubでいうリポジトリのような役割。コンテンツを管理する場所
コンテンツモデル コンテンツの雛型(テンプレート)
コンテンツ コンテンツモデルから作り出された情報(コンテンツ)

簡単にまとめると
- スペースで色んなコンテンツを管理(最初に自動でスペース1つ分作られます)
- コンテンツモデルでコンテンツの雛型を作成(例えば、ブログ投稿のテンプレートなど)
- コンテンツモデルからコンテンツを作っていく(ブログでいえば、投稿記事など)

以下の記事にcontentfulの始め方や概念をまとめているので、参考になれば嬉しいです。
- Contentfulとは?ヘッドレスCMSって何?誰でもわかるように解説してみる | ぐるたかログ
- 【初心者向け】Contentfulの始め方。コンテンツモデルの作成や投稿、APIキーの場所などまとめ【使い方】 | ぐるたかログ

使用料金に関しては、個人でやる分には無料枠で十分いけます。

24個のコンテンツタイプ
5000個のレコード
10人のユーザー
1つのロール(ユーザー権限)
2つの環境(master/stagingとか)
2つのロケール(多言語対応)

引用:Contentfulの料金と使い方を整理しつつ、Nuxt.jsと組み合わせてブログを作る - Qiita

他にもコンテンツモデルのバリデーション機能も豊富で、コンテンツ同志の紐付け(reference)機能も使えます。更にブランチ機能まであるという!

わりと多機能なので、実際に使いながら色々と試してみると良いでしょう!

Nuxt

Nuxtで静的ファイルをビルド(プリレンダリング)するのは簡単です。以下のコマンドを使用すればOK

$ npm run generate 

NuxtにContentfulを導入する方法はブログに図解でまとめています。データ取得までのサンプルコードも載せているので、ご興味ある方はチェックしていただけますと嬉しいです。

NuxtにContentfulを設定する方法。導入からデータ取得までの流れをまとめてみた【スクショあり】 | ぐるたかログ

pages/index.vue
<script>
import contentfulClient from '@/plugins/contentful'

export default {
  asyncData({ env }) {
    return contentfulClient
      .getEntries()
      .then((entries) => {
        console.log(entries.items)
        return {
          ssample: entries.items
        }
      })
      .catch(console.error)
  }
}
</script>

ブログには掲載していないのですが、asyncDataでcontentfulから複数のHTTPリクエストを扱う場合は、Promise.allを使うと、スマートにかけます。

sample.vue
<template>
  <div>
    <!-- render data of the person -->
    <h1>{{ person.fields.name }}</h1>
    <!-- render blog posts -->
    <ul>
      <li v-for="post in posts">
        {{ post.fields.title }}
      </li>
    </ul>
  </div>
</template>

<script>
  import {createClient} from '~/plugins/contentful.js'

  const client = createClient()

  export default {
    // `env` is available in the context object
    asyncData ({env}) {
      return Promise.all([
        // fetch the owner of the blog
        client.getEntries({
          'sys.id': env.CTF_PERSON_ID
        }),
        // fetch all blog posts sorted by creation date
        client.getEntries({
          'content_type': env.CTF_BLOG_POST_TYPE_ID,
          order: '-sys.createdAt'
        })
      ]).then(([entries, posts]) => {
        // return data that should be available
        // in the template
        return {
          person: entries.items[0],
          posts: posts.items
        }
      }).catch(console.error)
    }
  }
</script>

引用:Integrate Contentful with Nuxt.js – Contentful

また、ビルドする前にOGPなど色々と設定しました。その際に、こちらの記事が丁寧にまとめられていて、凄く参考になりました!

余談ですが、お恥ずかしい話、OGP設定でprefix属性を忘れて、四苦八苦しました。OGP生成する方は要チェックです!
OGPにつけるprefix属性とは何か調べてみた。 - Qiita

また、ビルドした後の静的ファイルをNuxtと切り離して確認する際は、http-serverを使っています。
nuxt generateで生成した静的ファイルをhttp-serverでホスティングする #nuxtjs - ジムには乗りたい

Netrify

以下の記事に従って、進めていけば超簡単にデプロイできます!
【Netlify】Github連携でWEBサイトを公開する - Qiita

Contentful WebhookでNetrifyと連携する方法は自分のブログにまとめています!
【Contentful Webhook】コンテンツ変更を検知し、Netrifyで自動でビルドさせる方法 | ぐるたかログ

またNetrifyにはNetlify Formsがあります。せっかくなので、Netlify Formsを使って、お問い合わせフォームをポートフォリオサイトに組み込みました。JavaScriptだけで、お問い合わせフォームもできるなんてビックリです。

こちらもNetrify Formsでお問い合わせフォームを設置する方法を書いたので、参考になればと思います。
Netlify Formsを使って、お問い合わせフォームを設置する方法 | ぐるたかログ

そして最後に、詰まった点を1つ紹介。 Netrifyにビルドする際に、env周りで設定したContentful関連の環境変数をNetrifyにも必ず登録しましょう。

登録しないと、APIでコンテンツ情報を取得できなくなります!私は初心者すぎて、小一時間くらい消耗しました…。

Build___deploy___Settings.png

 最後に

JAMStackなポートフォリオサイトを開発してみて、感動しました。JavaScriptだけでここまで作れるなんて、すごい世界だなと。

そして可能性を感じました。

こちらの記事にも書かれていますが、今後ノーコード(GUI)でフロントが作れてAPIでコンテンツ取得までできれば、エンジニアでなくても、メディアサイトのようなものが作れるわけです。

今後のJAMStackの動向も楽しみです!

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

参考リンク

【入門】Nuxt.js + Contentful + Netlify で始める、JAMstack な CMS 構築 - Qiita

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

便利ページ:QRコードスキャン

久しぶりの、「便利ページ:Javascriptでちょっとした便利な機能を作ってみた」 のシリーズものです。

今回は、QRコードスキャンです。
QRコード生成はすでに実装してあったのですが、スキャンする方はありませんでした。

HTML5では、PCに接続されたカメラを扱うことができますので、ブラウザだけでスキャンできます。また、AndroidやiPhoneでのChromeでも動作するので、これでPCだけでなくスマホでも動きます。

いつもの通りGitHubにも上げてあります。
 https://github.com/poruruba/utilities

参考までに、以下にデモとしてアクセスできるようにしてあります。
 https://poruruba.github.io/utilities/

QRスキャンのためのライブラリ

以下を使わせていただきました。ありがとうございます。

cozmo/jsQR
 https://github.com/cozmo/jsQR

HTMLで以下ようにスクリプトをロードしておきます。
 <script src="dist/js/jsQR.js"></script>

ソースコード抜粋

肝心のJavascriptのソースコードです。重要部分のみ抜粋しています。

start.js
        qrcode_scan: function(){
            this.qrcode_video = $('#qrcode_camera')[0];
            this.qrcode_canvas = $('#qrcode_canvas')[0];

            if( this.qrcode_running ){
                this.qrcode_forcestop();
                return;
            }

            this.qrcode_running = true;

            this.qrcode_context = this.qrcode_canvas.getContext('2d');
            this.qrcode_timer = setTimeout(() =>{
                this.qrcode_forcestop();
            }, QRCODE_CANCEL_TIMER);

            return navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" }, audio: false })
            .then(stream =>{
                this.qrcode_video.srcObject = stream;
                this.qrcode_draw();
            })
            .catch(error =>{
                alert(error);
            });
        },
        qrcode_draw: function(){
            this.qrcode_context.drawImage(this.qrcode_video, 0, 0, this.qrcode_canvas.width, this.qrcode_canvas.height);
            const imageData = this.qrcode_context.getImageData(0, 0, this.qrcode_canvas.width, this.qrcode_canvas.height);

            const code = jsQR(imageData.data, this.qrcode_canvas.width, this.qrcode_canvas.height);
            if( code ){
                this.qrcode_scaned_data = code.data;
                console.log(code);

                this.qrcode_forcestop();

                this.qrcode_context.strokeStyle = "blue";
                this.qrcode_context.lineWidth = 3;

                var pos = code.location;
                this.qrcode_context.beginPath();
                this.qrcode_context.moveTo(pos.topLeftCorner.x, pos.topLeftCorner.y);
                this.qrcode_context.lineTo(pos.topRightCorner.x, pos.topRightCorner.y);
                this.qrcode_context.lineTo(pos.bottomRightCorner.x, pos.bottomRightCorner.y);
                this.qrcode_context.lineTo(pos.bottomLeftCorner.x, pos.bottomLeftCorner.y);
                this.qrcode_context.lineTo(pos.topLeftCorner.x, pos.topLeftCorner.y);
                this.qrcode_context.stroke();

                this.qrcode_btn = 'QRスキャン開始';
            }else{
                if( this.qrcode_running )
                    requestAnimationFrame(this.qrcode_draw);
            }
        },
        qrcode_forcestop: function(){
            if( !this.qrcode_running )
                return;

            this.qrcode_running = false;

            if( this.qrcode_timer != null ){
                clearTimeout(this.qrcode_timer);
                this.qrcode_timer = null;
            }

            this.qrcode_video.pause();
            this.qrcode_btn = 'QRスキャン開始';
        },

ご参考までに、HTMLの方も。

index.html
    <label>scaned data</label>
    <div class="input-group">
        <span class="input-group-btn">
            <button class="btn btn-default clip_btn glyphicon glyphicon-paperclip" data-clipboard-target="#qrcode_scaned_data"></button>
        </span>
        <input id="qrcode_scaned_data" type="text" class="form-control" v-model="qrcode_scaned_data" readonly>
    </div><br>
    <button class="btn btn-primary" v-on:click="qrcode_scan()">{{qrcode_btn}}</button><br>
    <div>
        <img v-show="!qrcode_running && qrcode_scaned_data==''" id="qrcode_start" src="./img/qr_start.png" v-bind:style="qrcode_size"><br>
        <video v-show="qrcode_running" id="qrcode_camera" v-bind:style="qrcode_size" autoplay></video>
        <canvas v-show="!qrcode_running && qrcode_scaned_data!=''" id="qrcode_canvas" v-bind:style="qrcode_size"></canvas>
    </div>

解説

・qrcode_scan()
ボタン押下をトリガに、QRコードスキャンを開始します。
まずは、「navigator.mediaDevices.getUserMedia」を呼び出して、ユーザに対してカメラ利用の許可を聞いた後にカメラを起動させます。
起動した後、「this.qrcode_video.srcObject = stream;」でカメラ画像をWebページに表示させ、「qrcode_draw()」の呼び出しで、カメラ画像からQRコードを探します。
setTimeout()がありますが、一定時間QRコードスキャンで見つからなかったときに、QRコードスキャンを停止するためのものです。
ちなみに、ブラウザからカメラを利用するため、HTMLはHTTPSでホスティングしている必要があります。

・qrcode_draw()
カメラ画像からQRコードをスキャンします。
いったん、別のcanvasのcontextにコピーしたのち、QRコードスキャンのライブラリを呼び出します。
QRコードがあった場合には、QRコードの部分を青色の四角形で囲ってスキャン終了です。
もしQRコードがなかった場合には、「requestAnimationFrame」を呼び出して次のカメラ画像を待ちます。

以上

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

便利ページ:JavascriptでQRコードスキャン

久しぶりの、「便利ページ:Javascriptでちょっとした便利な機能を作ってみた」 のシリーズものです。

今回は、QRコードスキャンです。
QRコード生成はすでに実装してあったのですが、スキャンする方はありませんでした。

HTML5では、PCに接続されたカメラを扱うことができますので、ブラウザだけでスキャンできます。また、AndroidやiPhoneでのChromeでも動作するので、これでPCだけでなくスマホでも動きます。

いつもの通りGitHubにも上げてあります。
 https://github.com/poruruba/utilities

参考までに、以下にデモとしてアクセスできるようにしてあります。
 https://poruruba.github.io/utilities/

QRコードスキャンのためのライブラリ

以下を使わせていただきました。ありがとうございます。

cozmo/jsQR
 https://github.com/cozmo/jsQR

HTMLで以下ようにスクリプトをロードしておきます。
 <script src="dist/js/jsQR.js"></script>

ソースコード抜粋

肝心のJavascriptのソースコードです。重要部分のみ抜粋しています。

start.js
        qrcode_scan: function(){
            this.qrcode_video = $('#qrcode_camera')[0];
            this.qrcode_canvas = $('#qrcode_canvas')[0];

            if( this.qrcode_running ){
                this.qrcode_forcestop();
                return;
            }

            this.qrcode_running = true;
            this.qrcode_btn = 'QRスキャン停止';
            this.qrcode_scaned_data = "";

            this.qrcode_context = this.qrcode_canvas.getContext('2d');
            this.qrcode_timer = setTimeout(() =>{
                this.qrcode_forcestop();
            }, QRCODE_CANCEL_TIMER);

            return navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" }, audio: false })
            .then(stream =>{
                this.qrcode_video.srcObject = stream;
                this.qrcode_draw();
            })
            .catch(error =>{
                alert(error);
            });
        },
        qrcode_draw: function(){
            this.qrcode_context.drawImage(this.qrcode_video, 0, 0, this.qrcode_canvas.width, this.qrcode_canvas.height);
            const imageData = this.qrcode_context.getImageData(0, 0, this.qrcode_canvas.width, this.qrcode_canvas.height);

            const code = jsQR(imageData.data, this.qrcode_canvas.width, this.qrcode_canvas.height);
            if( code && code.data != "" ){
                this.qrcode_scaned_data = code.data;
                console.log(code);

                this.qrcode_forcestop();

                this.qrcode_context.strokeStyle = "blue";
                this.qrcode_context.lineWidth = 3;

                var pos = code.location;
                this.qrcode_context.beginPath();
                this.qrcode_context.moveTo(pos.topLeftCorner.x, pos.topLeftCorner.y);
                this.qrcode_context.lineTo(pos.topRightCorner.x, pos.topRightCorner.y);
                this.qrcode_context.lineTo(pos.bottomRightCorner.x, pos.bottomRightCorner.y);
                this.qrcode_context.lineTo(pos.bottomLeftCorner.x, pos.bottomLeftCorner.y);
                this.qrcode_context.lineTo(pos.topLeftCorner.x, pos.topLeftCorner.y);
                this.qrcode_context.stroke();
            }else{
                if( this.qrcode_running )
                    requestAnimationFrame(this.qrcode_draw);
            }
        },
        qrcode_forcestop: function(){
            if( !this.qrcode_running )
                return;

            this.qrcode_running = false;

            if( this.qrcode_timer != null ){
                clearTimeout(this.qrcode_timer);
                this.qrcode_timer = null;
            }

            this.qrcode_video.pause();
            this.qrcode_video.srcObject = null;
            this.qrcode_btn = 'QRスキャン開始';
        },

ご参考までに、HTMLの方も。

index.html
    <label>scaned data</label>
    <div class="input-group">
        <span class="input-group-btn">
            <button class="btn btn-default clip_btn glyphicon glyphicon-paperclip" data-clipboard-target="#qrcode_scaned_data"></button>
        </span>
        <input id="qrcode_scaned_data" type="text" class="form-control" v-model="qrcode_scaned_data" readonly>
    </div><br>
    <button class="btn btn-primary" v-on:click="qrcode_scan()">{{qrcode_btn}}</button><br>
    <div>
        <img v-show="!qrcode_running && qrcode_scaned_data==''" id="qrcode_start" src="./img/qr_start.png" v-bind:style="qrcode_size"><br>
        <video v-show="qrcode_running" id="qrcode_camera" v-bind:style="qrcode_size" autoplay></video>
        <canvas v-show="!qrcode_running && qrcode_scaned_data!=''" id="qrcode_canvas" v-bind:style="qrcode_size"></canvas>
    </div>

解説

・qrcode_scan()
ボタン押下をトリガに、QRコードスキャンを開始します。
まずは、「navigator.mediaDevices.getUserMedia」を呼び出して、ユーザに対してカメラ利用の許可を聞いた後にカメラを起動させます。
起動した後、「this.qrcode_video.srcObject = stream;」でカメラ画像をWebページに表示させ、「qrcode_draw()」の呼び出しで、カメラ画像からQRコードを探します。
setTimeout()がありますが、一定時間QRコードスキャンで見つからなかったときに、QRコードスキャンを停止するためのものです。
ちなみに、ブラウザからカメラを利用するため、HTMLはHTTPSでホスティングしている必要があります。

・qrcode_draw()
カメラ画像からQRコードをスキャンします。
いったん、別のcanvasのcontextにコピーしたのち、QRコードスキャンのライブラリを呼び出します。
QRコードがあった場合には、QRコードの部分を青色の四角形で囲ってスキャン終了です。
もしQRコードがなかった場合には、「requestAnimationFrame」を呼び出して次のカメラ画像を待ちます。

以上

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

Vue slotの使い方まとめ

slot(スロットコンテンツ)とは

公式
Vue には Web Components spec draft にヒントを得たコンテンツ配信 API が実装されており、 要素をコンテンツ配信の受け渡し口として利用します。

基本編

slotタグのみ定義

タグ配下に記載したものが、スロット部分に設定される

navigationLink.vue
<template>
  <a v-bind:href="url" class="nav-link">
    <slot></slot>
  </a>
</template>

<script>
export default {
  props: ["url"]
};
</script>
App.vue
<template>
  <div>
    <navigationLink url="/profile">
      Your Profile
    </navigationLink>
  </div>
</template><a href="/profile" class="nav-link">
      Your Profile
    </a>

slotタグのみ定義と同じ意味

slotのnameにdefaultを設定する

navigationLink.vue
<template>
  <a v-bind:href="url" class="nav-link">
    <slot name="default"></slot>
  </a>
</template>

<script>
export default {
  props: ["url"]
};
</script>

slotを複数定義(name指定)

name指定することで、コンポーネント使用時にどこを置き換えるか指定できる
- 「slot name="XXX"」と「v-slot:XXX」が対応
- 「v-slot:XXX」が定義されていない場合、フォールバックし「slot name="XXX"」の内容が表示される

slotComp.vue
<template>
  <div>
    <slot name="header">Header</slot>

    <slot></slot>

    <slot name="footer">Footer</slot>
  </div>
</template>

App.vue
<template>
  <div>
    <slotComp>
      <template v-slot:header>
        <div>Title</div>
      </template>
      body
    </slotComp>
  </div>
</template><div>
      <div>Title</div>
      body Footer
    </div>

省略記法

  • 「v-slot:header」===「#header」
App.vue
<template>
  <div>
    <slotComp>
      <template #header>
        <div>Title</div>
      </template>
      body
      <template #footer>
        <div>footTitle</div>
      </template>
    </slotComp>
  </div>
</template>

応用編

slotコンポーネントのデータを利用する

  • slotにbindすることで利用側で参照可能となる ⇒ (要素にバインドされた属性は、スロットプロパティと呼ばれる)
slotComp.vue
<template>
  <div>
    <slot name="header">Header</slot>

    <slot name="main" :user="user"></slot>

    <slot name="footer">Footer</slot>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      user: {
        id: "user_id",
        name: "user_name"
      }
    };
  }
};
</script>
App.vue
<template>
    <slotComp>
      <template #header>
        <div>Title</div>
      </template>

      <template v-slot:main="{ user }">
        <div>{{ user.id }}</div>
        <div>{{ user.name }}</div>
      </template>

      <template #footer>
        <div>footTitle</div>
      </template>
    </slotComp>
</template><div>
      <div>Title</div>
      <div>user_id</div>
      <div>user_name</div>
      <div>footTitle</div>
    </div>

動的設定

  • 「v-slot:[XXX]」で動的に「slot」を指定できる
slotComp.vue
<template>
  <span>
    <slot :categories="categories"></slot>
    <slot name="new_categories" :categories="newCategories"></slot>
  </span>
</template>

<script>
export default {
  data() {
    return {
      categories: ["Dart", "Flutter", "Vue.js"],
      newCategories: ["Mobile", "Cross Platform", "Frontend Tech"]
    };
  }
};
</script>
App.vue
<template>
  <div>
    <slotComp v-slot:[collection]="{ categories }">
      <ul>
        <li v-for="category in categories" :key="category">
          {{ category }}
        </li>
      </ul>
    </slotComp>
    <button @click="changeCollection">Change</button>
  </div>
</template>

<script>
import slotComp from "./components/slotComp";

export default {
  components: { slotComp },
  data: function() {
    return {
      collection: "default"
    };
  },
  methods: {
    changeCollection() {
      this.collection =
        this.collection === "default" ? "new_categories" : "default";
    }
  }
};
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js】Validation基礎編:フォームバリデーション

前置き

validation.gif
正規表現を使ってフォームバリデーション!
今回はメールアドレス形式
hoge@hoge.hogeのみ送信可能にします?
間違った形式で送られることがないので
トラブル防止などにもなり便利ですね♪

初期準備

今回はコンポーネントは使わず
index.vueの1ページに全て書きます✍️
まずはFormタグと送信ボタンを用意。
基本的なフォーム構成は別記事で解説済み?
◾️【Nuxt.js】v-model実践編:オリジナルフォームの簡単な作り方
https://note.com/aliz/n/n5b9bd618399e

{{ Validation.result }}
入力した値それぞれに対応したテキストを表示。
・空の場合
・バリデーションしてNGの場合
・バリデーションしてOKの場合

index.vue
<template>
 <div class="page">
   <p>{{ Validation.result }}</p>
   <form @submit.prevent>
     <label>Mail
       <input
         type="text"
         placeholder="メールアドレスを入力"
         v-model="mail"
       >
     </label>
   </form>
   <button type="submit" @click="checkForm">
     送信
   </button>
 </div>
</template>

<script>
export default {
 data () {
   return {
     mail: "",
     Validation:{
       result: "",
     },
   }
 },
}
</script>

methodsを追加(正規表現)

【基礎文法】
Vueの公式ページをご確認ください。
https://jp.vuejs.org/v2/cookbook/form-validation.html#カスタムバリデーションの利用

index.vue
methods名: function (引数名) {
  var 変数名 = 正規表現;
  return 変数名.test(引数名);
}

【コード】
引数名は分かりやすくinputdataに、
変数名は正規表現を英語にしたregexに。
正規表現についてはこちらで解説しています。
◾️【Nuxt.js】正規表現基礎編①:よく使う表現を単語分割で解説!
https://note.com/aliz/n/n898319c9042d

index.vue
<script>
export default {
 data () {
   return {
     mail: "",
     Validation:{
       result: "",
     },
   }
 },
 methods:{
   checkString (inputdata){
     var regex = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
     return regex.test(inputdata);
   }
 }
}
</script>

methodsを追加(送信可不可)

【構成】
送信ボタンを押すとcheckForm発動。
2つに分けて考えます。
これを分けるために変数mailBoolの
真偽値によってパターン分けしています?
◾️空入力&形式が間違った場合
 mailBool = false
◾️形式が正しい場合
 mailBool = true

【コード】

index.vue
<script>
export default {
 data () {
   return {
     mail: "",
     Validation:{
       result: "",
     },
   }
 },
 methods:{
   checkForm() {
     var mailBool = false
     if (!this.mail) {
       this.Validation.result="入力してください"
     }
     else if (!this.checkString(this.mail)){
       this.Validation.result="メールアドレス形式で入力してください"
     } else {
       mailBool = true
     }

     if(mailBool === true){
       this.Validation.result="送信に成功しました!"
       alert(this.mail + 'で送信しました。');
       console.log(this.mail);
       this.mail = "";
     }
   },
   checkString (mail){
     var regex = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
     return regex.test(mail);
   }
 }
}
</script>

【解説】
◾️空入力&形式が間違った場合
 mailBool = false
 ・if (!this.mail)
   data内mail=inputの入力がfalse(空)なら
   該当テキストを表示
 ・else if (!this.checkString(this.mail))
   空ではないが、
   checkString関数でthis.mailを
   バリデーションしてfalseなら
   該当テキストを表示
 ・else { mailBool = true }
   どちらでもなく正しく入力された場合
   mailBool = trueにする

◾️形式が正しい場合
 mailBool = true
 ・this.mail = "";
   送信後はメールアドレスを
   残したままにせず空に戻す

全体コード

index.vue
<template>
 <div class="page">
   <p>{{ Validation.result }}</p>
   <form @submit.prevent>
     <label>Mail
       <input
         type="text"
         placeholder="メールアドレスを入力"
         v-model="mail"
       >
     </label>
   </form>
   <button type="submit" @click="checkForm">
     送信
   </button>
 </div>
</template>

<script>
export default {
 data () {
   return {
     mail: null,
     Validation:{
       result: "",
     },
   }
 },
 methods:{
   checkForm() {
     var mailBool = false
     if (!this.mail) {
       this.Validation.result="入力してください"
     }
     else if (!this.checkString(this.mail)){
       this.Validation.result="メールアドレス形式で入力してください"
     } else {
       mailBool = true
     }

     if(mailBool === true){
       this.Validation.result="送信に成功しました!"
       alert(this.mail + 'で送信しました。');
       console.log(this.mail);
       this.mail = "";
     }
   },
   checkString (inputdata){
     var regex = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
     return regex.test(inputdata);
   }
 }
}
</script>

<style lang="scss" scoped>
.page {
 padding: 50px 20px;
 p {
   font-size: 24px;
   position: absolute;
   top: 10px;
 }
 label {
   display: block;
   margin-top: 5px;
 }
}
</style>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者向けVue.js × Railsでのアプリ作成(ToDoリスト編)

はじめに

最近、Vue.jsとRailsでアプリを作っているのですが、Vue.jsとRailsでアプリを作る記事が少なく、勉強するのに少し不便でした。

Vue.js × Railsの記事が少ないと言っても探せばそれなりに見つかるのですが、私みたいなフロントエンドの事よくわかってない人間には、理解するのに時間がかかったりします。

ネットで記事をあさったり、そもそもJavaScriptが良くわかってないので、JavaScriptから勉強し直してみたり、Vue.js × Railsでアプリを作るだけにしては非常に遠回りしてしまいました。

この記事について

この記事は、私みたいにVue.js × Railsのアプリ作成で遠回りな勉強をしている人をなくす事を目的としています。

初心者向けにVue.js × Railsでアプリを作る記事を書いて、実装のイメージを掴んでもらえれば、私のような遠回りはなくなるはず。。。1度小さいアプリを作ってしまえば、理解度がグッと上がり、他の記事も読みやすくなるのできっと大丈夫!

また、この記事は私がRubyエンジニアなので、Rubyエンジニアから見てVue.jsをどう実装しようかという視点になってます。

この記事を読む対象のレベル

Vue.js × Rails両方ともチュートリアルをやったくらいのレベルを対象としています。
Vue.jsもRailsもだいたいこんな感じというのがわかっていれば作れると思います。

どんなものを作るか? Vue.js × Railsそれぞれの役割とは?

この記事では、Vue.js × RailsでミニマムなToDoリストアプリを作っていきます。定番ですね。

私はToDoアプリを作ろうとした時、Vue.js × Railsがそれぞれどんな役割をしているのかよくわからず、実装のイメージが掴めなかったのですが、いろんな記事を読んだ結果、RailsでAPIを作り、APIへのリクエストと返ってきたデータの表示をVue.jsで行うというのが多かったです。今回もこの役割分担でアプリを作っていきます。

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

WEB業界での経験が浅い、もしくはこれからWEB業界を目指す方はAPIのイメージがつかみにくいかもしれませんが、一言で言うとURLのリクエストを受けたら、URLに応じたデータを返すものです。

この役割のイメージが分かればRailsの部分をFirebaseに置き換えようとか、Vue.jsをReactに置き換えようとか応用が効くような気がします。

(注)私は現時点でFirebaseもReactも詳しくないので応用が効く気がするというだけ。。。詳しい方がいたら教えて下さると助かります。

完成後のイメージ

スクリーンショット 2020-01-13 14.31.32.png

  • テキストボックスにタスクを入力して追加ボタンを押すとリストに追加されて表示される。
  • チェックボックスをチェックすると取り消し線が引かれる
  • 削除ボタンを押すと削除

実際に作ってみよう

RailsでAPIを作る

では、実際にアプリを作ってきます。まずはRailsでAPIを作るところから。
以下のコマンドで、Railsアプリを作ります。--webpack=vueをするとwebpackとVue.jsのインストールが出来ます。

rails new todo_list --webpack=vue

「webpackとかまた新しい言葉出すなよ!」って方は以下のリンクを見てください!
【5分でなんとなく理解!】Webpack入門
Webpackとは、js、cssなどフロントで作るファイルをバンドリングしてくれるものです。

ToDoリストを表示するHome画面を作成

ToDoを表示するHome画面を作るため、コントローラーを作成します。

rails g controller home

作成したコントローラーにindexだけ追加しておきましょう。

/app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
  end
end

viewからVue.jsが呼び出せるか試しますために追加します。

/app/views/home/index.erb
<%= javascript_pack_tag 'hello_vue' %>

routes.rbに以下を追加。

/config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
end

rails sしてlocalhost:3000にアクセスします。
以下のような画面が表示されてればOKです。

スクリーンショット 2020-01-02 23.00.33.png

ちなみにVue.jsを変更したら

bin/webpackで更新してあげる必要があります。(重要)

APIの処理を作る

まずは、ToDoリストにタスクを追加するためにモデルを作っていきます。

rails generate model Task name:string is_done:boolean

ルーティングに以下を追加します。
表示用のhomeとデータを返すAPI用のapi::tasksを追加。

/config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'

  namespace :api, format: 'json' do
    resources :tasks, only: [:index, :create, :destroy, :update]
  end
end

リクエストを受けたらデータを返すため、Tasksのコントローラーを作ります。

rails g controller Api::Tasks

マイグレーションします。

rails db:migrate

コントローラーの中身は以下のような感じ。

/app/controllers/api/tasks_controller.rb
module Api
  class TasksController < ApplicationController
    skip_before_action :verify_authenticity_token

    def index
      @tasks = Task.order('created_at DESC')
    end

    def create
      @task = Task.new(task_params)

      if @task.save
        render json: @task, status: :created
      else
        render json: @task.errors, status: :unprocessable_entity
      end
    end

    def destroy
      Task.find(params[:id]).destroy!
    end

    def update
      Task.find(params[:id]).toggle!(:is_done)
    end

    private def task_params
      params.require(:task).permit(:name, :is_done)
    end
  end
end

APIを返す時は、htmlではなくJSONで返してあげたいので以下を追加します。
自分は実際にWEB業界に入るまでJSONに馴染みがなかったのですが、以下の形で書きます。

/app/views/api/tasks/index.json.jbuilder
json.set! :tasks do
  json.array! @tasks do |task|
    json.extract! task, :id, :name, :is_done, :created_at, :updated_at
  end
end

APIの動作確認

DBにデータを入れて確認してみましょう。
コンソールを立ち上げます。

rails c

Taskモデルにデータを追加してみましょう。

Task.create(name: 'テスト用タスク')

もう一度サーバー立ち上げ

rails s

以下のアドレスで追加したデータがJSONでデータが返ればOK。

http://localhost:3000/api/tasks.json

スクリーンショット 2020-01-02 23.40.55.png

Vue.jsでフロント作成

コンポーネント

コンポーネントとは、名前付きの再利用可能な Vue インスタンスです。
再利用出来そうなパーツごとにコンポーネントを区切って実装するのが、どうやら重要らしい。
今回、最小限の構成でアプリを構成するためコンポーネントについては省こうか迷ったのですが、重要なので組み込みます。

わかりやすいイメージで言うと、ヘッダー、フッター、サイドナビ等は色んなページで再利用するのでコンポーネントを分けて実装するといった感じでしょうか。
今回もヘッダーとToDoリストを表示するボディ部分でコンポーネントを分けて実装したいと思います。

では、まず以下のようにToDoリスト表示部分を作って下さい。

/app/views/home/index.erb
<div id="app">
  <navbar></navbar>
</div>

<%= javascript_pack_tag 'todo' %> # todoに変更する事に注意

はヘッダーのコンポーネントを表示します。
<%= javascript_pack_tag 'todo' %>でapp/javascript/packs配下のtodo.jsファイルを読み込みます。

ヘッダーの作成

まずはヘッダー部分のコンポーネントを用意します。

/app/javascript/packs/components/header.vue
<template>
  <h1>ToDoリスト</h1>
</template>

次に/app/views/home/index.erbから呼び出されているapp/javascript/packs/todo.jsにコンポーネントの設定をしていきます。
以下のように書くとapp/views/home/index.erb<navbar></navbar>/app/javascript/packs/components/header.vueをマウントして表示してくれるようです。

/app/javascript/packs/todo.js
import Vue from 'vue/dist/vue.esm.js'
import Header from './components/header.vue'

var app = new Vue({
  el: '#app',
  components: {
    'navbar': Header
  }
});

Vue.jsの読み込み設定

webpackはVue.jsの読み込み方がわからないので以下を実行します(重要)

/config/loaders/stylus.js
module.exports = {
  test: /\.styl$/,
  use: [
    'style-loader', 'css-loader', 'stylus-loader'
  ]
}
/config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const { VueLoaderPlugin } = require('vue-loader')
const vue = require('./loaders/vue')
const stylus = require('../loaders/stylus') // 作ったstylusをrequireする

environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin())
environment.loaders.prepend('vue', vue)
module.exports = environment
environment.loaders.prepend('stylus', stylus) // 作ったstylusをロード

webpackを再読み込みしてからサーバーを再起動しましょう(重要)

bin/webpack
rails s

rails sしてヘッダーが表示されて入ればOKです

スクリーンショット 2020-01-13 14.10.00.png

ToDoリストを表示するボディ部分

axiosというライブラリを使って、フロントエンドからHTTPリクエストをします。
以下のコマンドでyarnでaxiosを追加して下さい。

yarn add axios

ToDoアプリのメイン部分の実装です。解説は後ほど詳しく説明します。

/app/javascript/packs/components/index.vue
<template>
  <div>
    <div>
      <input v-model="newTask" placeholder="to doを追加して下さい">
      <div v-on:click="createTask">
        <i>追加</i>
      </div>
    </div>
    <ul>
      <li v-for="(task, index) in tasks">
        <input type="checkbox" v-model="task.is_done" v-on:click="update(task.id, index)">
        <span v-bind:class="{done: task.is_done}">{{ task.name }}</span>
        <button v-on:click="deleteTask(task.id, index)">削除</button>
      </li>
    </ul>
  </div>
</template>

<script>
  import axios from 'axios';

  export default {
    data: function () {
      return {
        tasks: [],
        newTask: ''
      }
    },
    mounted: function () {
      this.fetchTasks();
    },
    methods: {
      fetchTasks: function () {
        axios.get('/api/tasks').then((response) => {
          for(let i = 0; i < response.data.tasks.length; i++) {
            this.tasks.push(response.data.tasks[i]);
          }
        }, (error) => {
          console.log(error, response);
        });
      },
      createTask: function () {
        if(this.newTask == '') return;

        axios.post('/api/tasks', { task: { name: this.newTask } }).then((response) => {
          this.tasks.unshift(response.data);
          this.newTask = '';
        }, (error) => {
          console.log(error, response);
        });
      },
      deleteTask: function (task_id, index) {
        axios.delete('/api/tasks/' + task_id).then((response) => {
          this.tasks.splice(index, 1);
        }, (error) => {
          console.log(error, response);
        });
      },
      update: function (task_id) {
        axios.put('/api/tasks/' + task_id).then((response) => {
        }, (error) => {
          console.log(error);
        });
      }
    }
  }
</script>

作ったindex.vueを読み込んであげましょう。

/app/javascript/packs/todo.js
import Vue from 'vue/dist/vue.esm.js'
import Header from './components/header.vue'
import Index from './components/index.vue' // 追加

var app = new Vue({
  el: '#app',
  components: {
    'navbar': Header,
    'contents' : Index // 追加
  }
});

ヘッダーのしたにindex.vueを表示するため、<navbar></navbar>の下に<contents></contents>を追加します。

/app/views/home/index.erb
<div id="app">
  <navbar></navbar>
  <contents></contents>
</div>

<%= javascript_pack_tag 'todo' %>

チェックボックスが押されたら取り消し線を表示するためcss追加。

app/assets/stylesheets/home.scss
#app li > span.done {
  text-decoration: line-through;
}

rails sして動くか確認してみて下さい。
実際にToDoリストを追加してみましょう。

スクリーンショット 2020-01-13 16.31.25.png

ToDoアプリのコード解説メモ

学習し始めだと、Vue.jsのどの行が何をやっているのかわからない事があったのでメモ付きのコードをのせます。

まずはtemplate

<template>
  <div>
    <div>
      <input v-model="newTask" placeholder="to doを追加して下さい">
      # 追加ボタンを押すとcreateTaskを実行する
      <div v-on:click="createTask">
        <i>追加</i>
      </div>
    </div>
    <ul>
      # fetchしたタスク一覧(tasks)から一つずつtaskとindexを取り出す処理
      <li v-for="(task, index) in tasks">
        # チェックボックスが押されたらv-modelのis_doneを変更して取り消し線を引く
        # updateでAPI側のデータも更新
        <input type="checkbox" v-model="task.is_done" v-on:click="update(task.id, index)">
        # タスクの表示。v-bind:classでis_doneを参照して取り消し線が引かれるかどうか判定
        <span v-bind:class="{done: task.is_done}">{{ task.name }}</span>
        # 削除ボタンを押すとdeleteTaskを実行
        <button v-on:click="deleteTask(task.id, index)">削除</button>
      </li>
    </ul>
  </div>
</template>
<script>
  import axios from 'axios';

  export default {
    data: function () {
      return {
        tasks: [],
        newTask: ''
      }
    },
    mounted: function () {
      this.fetchTasks();
    },
    methods: {
      // APIからタスク一覧を取得
      fetchTasks: function () {
        axios.get('/api/tasks').then((response) => {
          for(let i = 0; i < response.data.tasks.length; i++) {
            this.tasks.push(response.data.tasks[i]);
          }
        }, (error) => {
          console.log(error, response);
        });
      },
      // 新しいタスク作成
      createTask: function () {
        // テキストボックスが空の場合はreturnして終了
        if(this.newTask == '') return;

        // apiへ追加リクエスト
        axios.post('/api/tasks', { task: { name: this.newTask } }).then((response) => {
          // unshiftで現在のtasksの先頭にタスクを追加
          this.tasks.unshift(response.data);
          // 追加したらテキストボックスを空にする
          this.newTask = '';
        }, (error) => {
          console.log(error, response);
        });
      },
      // タスク削除
      deleteTask: function (task_id, index) {
        // apiへ削除リクエスト
        axios.delete('/api/tasks/' + task_id).then((response) => {
          this.tasks.splice(index, 1);
        }, (error) => {
          console.log(error, response);
        });
      },
      // タスク更新。今回はis_doneのみ更新だが、タスク名とか色々更新するようカスタムしても良いと思う
      update: function (task_id) {
        // apiへ更新リクエスト
        axios.put('/api/tasks/' + task_id).then((response) => {
        }, (error) => {
          console.log(error);
        });
      }
    }
  }
</script>

まとめ

小さいアプリをとりあえず作ってみると理解度がかなり深まると思うので、今回のようなToDoアプリを作ってみると良いと思います。
かけ足で記事を書いてしまったのですが、これで私みたいな人間を救えるのか...???
今後も私のようにVue.js × Railsでアプリを作りたい人向けに記事を改訂して行きたいです。

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

rgbaの値が正しく反映しないときの確認方法

未経験からプログラミング学習をしている方に向けての記事です。
vue.jsでモーダルウインドウを作成した際、rgbaでalphaを0.5に設定しているのに、背景が透明にならない時があったため、同じように悩んでいる場合どのように解決すれば良いかまとめておきます。

確認方法

①rgbaがちゃんと適用されていることはデベロッパーツールで確認できているか?
②デベロッパツールでちゃんと適用されていない場合には、デベロッパーツール上でrgbaを指定してみて、改善されるか試してみてください
③デベロッパツールでちゃんと適用されている場合には、デベロッパーツール上でrgbaの指定を外してみて、どのように表示が変わるかを試してみましょう。

自走できるエンジニアを目指して

メンターからは

「答え」ではなく、「答えまでたどり着く方法」を学ぶ

とよく言われます。
未経験からプログラミングを学習されてる方は、時間に追われてついつい答えを求めがちですが(自分自身)、このことを念頭に質問すると良いと思います。

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

Vue.js + Typescript のディレクトリ構造一例

こんにちは。
Web・iOSエンジニアの三浦です。

今回は、最近開発している Vue.js(Typescript)について、実際に使っているディレクトリ構造を紹介します。

はじめに

初めて Vue.js で開発をしたときは、基本に則った Atomic Design を使用していました。
しかしコンポーネントが増加するにつれ、どのように分けるべきか、同レイヤー間の呼び出しを許可するかなど煩雑さが増していきました。
その後別プロジェクトを Vue.js + Typescript で開発する機会があり、前回の反省を踏まえて少し Atomic Desigin をカスタマイズしてみることにしました。

ディレクトリ構造

現状以下のディレクトリ構造を使っています。
基本的な考え方は Atomic Design ですが、こちらの「Atomic DesignをVue.jsで実現するための構成と考え方 | Biscuetでの例をもとに」をかなり参考にさせていただいています。
なおこれ以外は、 Vue CLI のデフォルトの構造から変えていません。

src
├── components      // ページ要素コンポーネント
│   ├── organisms       // 各ページに固有の要素
│   │   └── ...
│   └── pages           // 各ページ
│       └── ...
├── plugins         // グローバルインストールするプラグイン
│   └── materials      // 汎用要素
│       ├── atoms          // 最小単位の汎用要素
│       │   └── ...
│       ├── molecules      // 最小単位の要素を組み合わせた汎用要素
│       │   └── ...
│       ├── index.ts       // materialsをグローバルインストール
│       └── ...
├── constants       // 定数
│   └──  ...
├── managers        // マネージャ
│   └──  ...
├── models          // データモデル
│   └──  ...
├── usecases        // 各ページのユースケース
│   └──  ...
├── utils           // ヘルパー
│   └──  ...
│
│   // 以下はデフォルトで存在する
│
├── assets          // 画像・CSS等アセット
│   └──  ...
├── router          // ルーター
│   └──  ...
├── store           // vuexストア
│   └──  ...
├── App.vue         // 画面フレーム
└── main.ts         // 各ライブラリ等読み込み

以下では、Vue CLIには存在しないこのディレクトリ構造独自の部分を説明していきます。

components

Atomic Designにおける pagesorganisms を格納するディレクトリです。
Atomic DesignをVue.jsで実現するための構成と考え方 | Biscuetでの例をもとに」に倣っており、そちらに説明されている通り冗長になりがちな templates は排除しています。

plugins/materials

Atomic Designにおける moleculesatoms を格納しています。
こちらも「Atomic DesignをVue.jsで実現するための構成と考え方 | Biscuetでの例をもとに」に倣っており、 plugins/materials/index.ts を使ってグローバルインストールさせています。
グローバルインストールさせていることからも分かる通り、ここに格納されるコンポーネントはいわば Vuetify などのようなライブラリ的使い方を想定しており、そのためどこでも使い回せるようなコードにすることが意識付けられます。

上記の Atomic Design の分け方にしたことで、「どのようにコンポーネントを分け、どのレイヤーに入れるべきか」、また「同レイヤー間の呼び出しをどうするか」はかなりスッキリ考えられるようになったと思います。

なお、「Atomic DesignをVue.jsで実現するための構成と考え方 | Biscuetでの例をもとに」で紹介されていた plugins/materials/index.js は、Typescriptとして書き直すと以下のようになります。

import { VueConstructor } from "vue/types/umd";

// @/plugins/materials 配下の全vueファイルを取得
const context = require.context(".", true, /.vue$/);
const components: { [key: string]: any } = {};

// 取得したコンポーネント一覧を、ファイル名がキーのオブジェクトに変換
context.keys().forEach(contextKey => {
  const keys = contextKey.match(/.+\/(.+)\.vue/);
  if (keys) {
    const key = keys[1];
    components[key] = context(contextKey).default;
  }
});

// ファイル名をキーとしたコンポーネントとしてグローバル登録
export default {
  install(Vue: VueConstructor) {
    Object.keys(components).forEach(key => {
      Vue.component(key, components[key]);
    });
  }
};

ここからは、各コンポーネントで使用するデータや処理を、どのようにコンポーネント外で定義するかになります。

constants

定数を定義します。
色々なところで使い回すようなものや、データサイズが大きいものなどはここに入れるようにしています。

例:画面幅の定数

const DISPLAY: {
  sm: {
    start: number;
  };
  md: {
    start: number;
  };
  lg: {
    start: number;
  };
  xl: {
    start: number;
  };
} = {
  sm: {
    start: 600
  },
  md: {
    start: 960
  },
  lg: {
    start: 1264
  },
  xl: {
    start: 1904
  }
};

export default DISPLAY;

managers

例えばAPIへの接続処理など、ある程度複雑で、かつ色々なところで使い回すような処理はここに入れています。

例:データを受け取ってAPIへ投げ、返り値を指定のクラスに変換して返すマネージャ

import axios, { AxiosResponse, AxiosRequestConfig } from "axios";
import { Request, RequestMethodType } from "@/models/request/baseRequest";
import Decoder from "@/models/request/baseDecoder";

const APIManager: {
  request: <P, B, R>(request: Request<P>, decoder: Decoder<B, R>) => Promise<R>;
} = {
  /**
   * APIリクエスト
   *
   * @template P APIリクエスト時に投げるパラメータ
   * @template B APIレスポンスの型
   * @template R レスポンスを変換する型
   * @param {Request<P>} request リクエスト型
   * @param {Decoder<B, R>} decoder デコーダ
   * @returns {Promise<R>} レスポンスを型変換した値
   */
  request: async <P, B, R>(
    request: Request<P>,
    decoder: Decoder<B, R>
  ): Promise<R> => {
    let method: AxiosRequestConfig["method"];

    switch (request.method) {
      case RequestMethodType.post:
        method = "post";
        break;
      case RequestMethodType.get:
      default:
        method = "get";
        break;
    }

    let response: AxiosResponse<any> = await axios({
      method: method,
      baseURL: request.baseURL,
      url: request.path,
      data: request.parameters
    });

    if (typeof response.data === "undefined") {
      throw new Error("data could not be gotten.");
    }

    return decoder.decode(JSON.parse(response.data));
  }
};

export default APIManager;

models

APIのレスポンスの型など、一つのデータとして固めておきたい塊を入れるモデルクラスを定義します。

例:APIへのリクエストモデル

import { Request, RequestMethodType } from "@/models/request/baseRequest";

export type TextParameterType = { text: string };

export class TextRequest implements Request<TextParameterType> {
  private _method: RequestMethodType = RequestMethodType.post;
  private _baseURL: string = "http://localhost:8080";
  private _path: string = "/text";
  private _parameters: TextParameterType;

  constructor(text: string) {
    this._parameters = { text: text };
  }

  get method(): RequestMethodType {
    return this._method;
  }

  get baseURL(): string {
    return this._baseURL;
  }

  get path(): string {
    return this._path;
  }

  get parameters(): TextParameterType {
    return this._parameters;
  }
}

usecases

一定以上の複雑なページを作る時、 pagesorganisms にすべての処理を書くと、ファイルが長くなりすぎて可読性に欠ける場合があります。
そのため、コンポーネント側にはviewとの接続を主に担当させ、具体的なロジックをユースケース側に分離して書くようにすることで可読性を向上させることができます。

コンポーネント側

@Watch("text")
onChangeText() {
  this.submitEnabled = TextUseCase.submitEnabled(
    this.textString,
    this.maxLength
  );
}

ユースケース側

submitEnabled: (text: string, maxLength: number): boolean =>
  0 < text.length && text.length < maxLength

utils

全体的に使う、ちょっとしたヘルパー関数を入れています。

例:スリープ処理

const sleep: (msec: number) => Promise<any> = msec =>
  new Promise(resolve => setTimeout(resolve, msec));

export default sleep;

おわりに

いかがだったでしょうか。
もちろんこれが必ずしも正解とは言えないですし、これ自体に関してもまだ完成ではなく、例えば managersutils の境はどの程度の複雑性になるのか、であるとか、DIをしたい場合はさらに別途ディレクトリ構成が必要になったりはあると思いますが、現状のプロジェクトの規模だとこれで良い感じに回っている印象です。
参考になれば幸いです!

参考文献

以下のサイトを参考にさせていただきました。
ありがとうございました!

Atomic DesignをVue.jsで実現するための構成と考え方 | Biscuetでの例をもとに

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

エラー Vue/Vuetify - Unknown custom element: <v-app> - did you register the component correctly?

NG

new Vue({
    el: "#app",
    components: { App },
    template: "<App/>"
});

Vue.use(Vuetify);

OK
new Vue()の前にVue.use(Vuetify)しないとダメらしい

Vue.use(Vuetify);

new Vue({
    el: "#app",
    components: { App },
    template: "<App/>"
});

pluginsフォルダを読み込む場合
こんな感じで冗長になる
main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
import VueSession from 'vue-session'

import vuetify from './plugins/vuetify'
// import Vuetify from "vuetify/lib"
Vue.use(Vuetify)
Vue.use(VueSession)

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  router,
  store,
  vuetify,
  render: h => h(App)
}).$mount('#app')

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

Vue.js のプログラミングを外部サービスでできるようにする方法

vuejslogo.png

はじめに

現在、Vue.js で簡単なプロダクトを作成しようと Vue.js について学習しており、外部公開できるようなサービスがないか探したところ、Glitch で実現できるようなので備忘のため投稿しました。

プロジェクトの作り方

以下の通りです。

1. Glitch の Web サイト にアクセスします。

2. 画面右上の検索バーで vue.js をキーワードに検索します。
1.png

3. 「Gal Pasternak」という方の「vue-js」をクリックします。
2.png

Vue.js のテンプレートプロジェクトを公開されている方は他にもいますので、適宜お選びください。

4. 「Remix Your Own」をクリックして、自分のプロジェクトとしてコピーします。
3.png

そして、コピー後に、以下のようにしてプロジェクト名を変更します。
(Glitch 全体を通して同じプロジェクト名をつけることはできませんので、ご注意ください。)
4.png

次の画面キャプチャの「legedary-kilogram」の部分を任意のプロジェクト名に変更します。
5.png

5. Vue.js のコードを書きます。

6. ページを表示します。
「Show」をクリックして、「In a New Window」または「Next to The Code」のどちらかを選択して、結果のページを表示します。
6.png

最後に

Vue.js の基本学習とプロダクト作成を進めていきたいと思います。

なお、Node.js のプロジェクトも作成できるようなので、ご興味がある方はやってみてはいかがでしょうか。

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

【Firebase, Nuxt】リアルタイムなスライド共有サービスを作ってハッカソンで優勝した話

昨年末にFirebaseのアイデアソン/ハッカソンに参加しました。
その場で出会った3名で即席チームを結成して、約1ヶ月でFirebaseを使ったサービスを開発しました。
その結果、最優秀賞を獲得し、更に1ヶ月で機能を追加して、サービスを正式リリースしました!

自分なりに大きな経験になったので、その経緯をサービス紹介を含めて公開します。
個人開発したいと思っているエンジニアで、参考にしてくれる人がいたら幸いです。

どんなサービスか

"SlideLive(スライドライブ)"といいます。
勉強会やセミナーのライブ感を飛躍的に高めるリアルタイムスライド共有サービス
です。
top.png

SlideLiveのコンセプト

勉強会をライブに

私はプレゼンが苦手です。
「アイスブレイク」ってどうやったらいいのでしょうか?
「勉強会でプレゼンしている時にリアクションが無く緊張する」ことってありませんか?
そんな課題認識からサービスを発想しました。

SlideLiveは、参加者からのリアクションを最大限引き出して、発表者と参加者の距離を近くします。
発表者からの一方通行ではなく、参加者からフィードバックして、双方向で一緒に盛り上がれる、ライブのような「理想の勉強会」を演出します。
また、そのリアクションがアーカイブされて、勉強会の後でも楽しむことができます。

スクリーンショット 2020-01-11 21.57.49.png

勉強会のライブ感を高める機能

発表者ページのリアルタイム同期

今時の参加者は、PCやスマホを見ながら参加している人が多いので、手元の画面に発表者のページをライブで同期させています。
発表者のPC画面(左)をページ遷移すると、参加者のスマホ画面(右)が同期して切り替わります。
slide1.gif

コメント&いいね機能

各ページに思いついたコメントを参加者が書き込こみ、そのコメント(右)がすぐに発表者画面(左)で共有されます。
参加者のコメントに"いいね"がついて、コメント発生が促されます。
発表者がコメントを受けて説明を追加するなど、双方向のコミュニケーションが生まれます。
comment1.gif

スライドQ&A機能

発表者からアンケートやクイズを出題して、参加者が自分のスマホやPCから回答を入力できます(某クイズ番組方式)。
勉強会だけでなく、学校の授業や、結婚式の余興などでも活用できる機能です。
qa1.gif

音声認識文字起こし機能

発表者の音声を自動的に認識してテキストとしてアーカイブします。
発表内容の文字起こしに使えます。
onsei1.png

その他の機能

勉強会で活用できる機能が一通り揃っています。

  • マイページ
  • 非公開設定
  • QRコードでイベント・スライドを共有
  • イベント・スライド・Q&A編集機能
  • フルスクリーン表示

どうやって作ったか

既存サービスをフレームワークに

10月末にアイデアソンをやった時点でコンセプトは固まりました。
アイデアソンからハッカソンまでは後1ヶ月で、メンバ3名も本業があるので手はかけられない、どうやって作ろうか。
そこで、メンバの@Yuta_Fujiwaraが、既存サービスのフレームワークを男前にも惜しげもなく提供してくれました。
コメントやいいね、認証まで揃っていたので、その時点で基本機能はほぼできていました、、素晴らしい。
彼のおかげで、サービスを短期に形にできました。頭が上がりません。

アーキテクチャ

system4.jpg

バックエンドはFirebase、フロントエンドはNuxt + SemanticUIです。
この組み合わせを使ってみた感想として「めっちゃ相性いい」と思ってます。
余計な手間が少なく、サービスのロジック開発に集中できるし、思い通りに作れるので開発していて楽しい!
トラブルにハマって1日問題解決にかかってしまった、、ということが起こり難い気がします。
また、現状はFirebaseの無料枠に収まっているので、完全無料で開発ができているのも嬉しいです。

バックエンド

Cloud Firestoreでリアルタイム性を追求

本サービスの肝は、発表者と参加者のリアルタイムなコミュニケーションです。
発表者のページを同期、参加者のコメントの同期、Q&Aの回答集計といったUXがリアルタイムに変化することで、ライブ感が生まれてくると考えているので、見せ方にこだわっています。
そのために、FirestoreのonSnapshotメソッドを多用しています。
自前のWebSocketで実装したら相当大変なことを、短いコードで実現できています。

ただFirestoreは使っていく中で「collectionを全件READして読み取り回数が膨大になる問題」が発生したり、「セキュリティルールどうする」「RealtimeDatabaseとの違いって何」などの議論が必要だったりして、まだ使いこなしを模索しています。

FJUGのnoriさんの記事などを参考にさせてもらいながら、勉強中です。

Cloud Functions でバックグラウンド処理実行

Firestoreのデータ更新のイベントをトリガーにするバックグラウンド関数として、コメント数の集計などで使っています。
今後は、データの登録をトリガに検索のインデックスを作る等でもっと活用したいと思っています。

認証・ストレージ・ホスティング・分析もFirebaseでOK

  • 認証はFirebase AuthenticationでGoogle認証とTwitter認証を利用してます。 既存サービスのものを利用したので開発レスでした。超便利です。
  • サイトの公開はFirebase Hostingで。
  • Firebase Storageに発表資料や画像のバイナリを保存。
  • Google Analyticsをアクセス数の解析などのために導入。これから活躍してくれるはずです。

フロントエンド

Nuxt.js・Vue.js

各メンバが利用経験が有り、コンポーネントを分けた並行開発がし易いNuxtをSPAモードで使用しています。
Vuexも使いやすいです(私はRedux苦手です)。
ライブラリを使った機能追加も容易なので助けられました。
本サービスでは以下を使っています。

  • vue-pdf & swiper.js : スライドが中心のサービスだけに、スムーズな画面遷移&ページ同期が必須です。 vue-pdfを使って発表資料を表示、swiperでページを送れるようにしています。 この仕組みを@tammaxが動かしてくれたので、行ける!となりました。
  • vue-qriously:スライド・イベント共有のQRコード生成に使ってます。生成が速くて良いです。
  • vue-chart: アンケートの集計に利用。こんな感じで使ってます。

chart.png

SemanticUI

「既存サービスで使っていていいよ」とのメンバのリードで採用。
使ってみるとdivタグのclass属性にUI要素を並べるだけでいい感じのUIができるし、iconも種類が豊富でかわいい。
個人的にはVue+Vuetifyより、Vue+SemanticUIの方が好みです。
SemanticUIについてはこの記事が詳しいです。

チーム開発のためのツール

メンバ3名は全員本業で結構忙しくしているので、開発するのは平日の夜や休日になります。
使っているツール群は以下のものです。
一般的ですが、自分に取ってはバリバリ使うのはどれも初めてだったので新鮮でした。
今では、このツール群とMacとVSCodeが有ればどこでも開発できる、と思うようになりました。

Slackでチャット

対面で会えないので基本連絡はSlackで。
GitHubにコミットしたら通知するように設定してます。

GitHubでソース管理

Privateリポジトリで開発しています。
最初はmasterブランチ一本で開発していましたが、後に間違って上書きするのが怖くなってきて、developブランチでテストしてからmasterに上げる運用にシフトしました。

Trelloでタスク管理

タスクを看板で見える化してます。今後の機能要望もここに蓄積しています。
trello.png

ハッカソン当日

風邪でダウン

そんな大事な日に、私は風邪でダウンしていました。
その週に会社を3日も休み、病み上がり状態だったのです。
また、休日にノコノコでかけて、風邪がぶり返すことも許されませんでした。

でもSlideLiveが有ったので大丈夫でした(+Zoomも)。
自宅から、Zoomの画面を見て、SlideLiveの発表資料を見つつ、コメントを投入していました。
そして、一人でこれは行けると盛り上がっていました。

ハッカソンをハックする

私達は、ハッカソンの参加者でありながら、各発表者にもSlideLiveを使って発表してもらう仕込みをしていました。
この作戦は大成功でした。
自分たち以外の発表でも常にSlideLiveがアピールされている状況を作り出すことができたのです。

ちょっとズルい作戦だったので、他の発表者の方には少し申し訳なかったかなと思いましたが、、(当日の発表者の方、すいません。)
狙ってやったというより、サービスの特性上、たまたまハマった作戦でした。

好評価

「SlackやTwitterで実況するより一体感がある」
「ページ遷移が連動するのいいね。ログイン不要なのも良い」
「Slidoと違ってスライドを確認できるのが良い」
といったコメントをいただきました。
スライドとコメントを統合して、一貫して見られるようにした所が良かったのかも。

そして審査結果発表、、

最優秀賞を獲得できました!
私は画面の中で表彰されるメンバ2名を見て、誇らしい思いでした。

審査員の中島聡さんは私が本を愛読している方で、特にエンジニアとしての生き方という本が大好きで、今まで読んだ中でも5本の指に入る本です。
その中島さんに評価されたなんて、信じられない思いでした。

そして何より嬉しかったのがSlideLiveを使って勉強会が盛り上がったことです。
自分たちの狙い通りに喜んでもらえたことは最高のご褒美でした。

当日の様子は以下から参照できますので、参考にリンクしておきます。
SlideLive Firebase Startup #2 ー Pitch & Demo Day
noteのレポート 前編後編

現場で祝杯を上げれなかったのは残念だったので、その後3人で打ち上げで祝杯を上げ、当日の話を肴に美味しいお酒を飲みました。

チーム開発をやって思うこと

メンバへの感謝

私自身は、普段は開発マネージャーとしてマネジメントを担当していて、「ここ数年コーディングしてないな」「最近どんな技術が流行っているのかな」という思いから、勉強会に参加し始めました。
半年ぐらい経って、一緒にサービスを開発する仲間ができて、開発したサービスをほめてもらえて、スキルアップもできて、「最高だな〜」と思っています。

今回、たまたま良い仲間と出会えましたが、それまでは悶々と勉強会に一人で参加して、懇親会であまり交流もできずに寂しく帰ることも多かったです。
ここでは、そんな内弁慶エンジニアの私が一歩外に踏み出せた経験は自信になりました。
僕と同じように社内以外のエンジニアとつながりがない、、?と思っているエンジニアの方がいたら、是非後押ししたいです。

チーム開発のメリットは大きい

それまでは個人でアプリ作ったりもしましたが、物になりませんでした。
チーム開発は以下のようなメリットがあると感じます。本当に有り難い。

  • 開発したもののフィードバックがもらえて、改善サイクルが回る
  • 励まし合ったり、あの人がここまでやったから自分もここまでやらなきゃ、と思って開発のモチベーションが持続し易い
  • 人のコードを見るのが参考になる
  • 詰まった時に相談できる
  • 飲み会ができる

SlideLiveの今後

「勉強会をライブに」を実現したい

せっかく頑張って準備した発表資料・プレゼンはその場限りではもったいない、とても良いコンテンツだと思いっています。
もっと多くの人の目に触れて、フィードバックを沢山もらえて、アーカイブが残る、勉強会コンテンツの価値を最大化するサービスにしたいです。

音楽のライブであれば、ファン同士が盛り上がったり、出演者と対話したり、DVDが出たりします。
勉強会もそんなポテンシャルがあると信じています。

そのために、twitterやSlackと連携で参加者を増やしたり、音声認識等のアーカイブ機能も強化します。
「勉強会ならSlideLive」が定着するような、デファクトのサービスになることを本気で目指して行きたいと思います。

最後に

FJUG、シンギュラリティ・ソサエティの方々にはこんないいイベントを企画していただいて、とても感謝しています。
その中で、SlideLiveのサービスに期待をいただいているので、それに応えてサービスを育てていきたいと思います。

長文読んでいただいて有難うございました。
是非使ってもらって、フィードバックいただけると大変有り難いです。

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

Vue.jsのおすすめチュートリアルのチャットアプリ作成をやってみる(更新中)

Vue.jsを始めたいと思っていたので、下記サイトに書いてあるおすすめのチュートリアルをやりましたので、その時のメモです。
Vue.jsは完全の素人で、SPAはBackbone.jsで構築したことがあります。

(進めるごとに更新しようと思います)

2020年のフロントエンドマスターになりたければこの9プロジェクトを作れ

はじめに

下記サイトにチュートリアルがあります。
Build a Real-time Chat App with Pusher and Vue.js

学べる事

(編集中)

  • Vue.jsの基礎
  • vue-cliの使い方
  • Vuexの基礎
  • CSS フレームワークの使い方
  • Pusher(CHATKIT)の使い方

Vuexとは

公式サイトより引用

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します

Pusherとは

下記記事で概要をつかみました
Pusherを触ってみる

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