ツールバーに表示する検索フォームを作成しよう【Nuxt.js×TypeScript】
  • 2022.02.17に公開
  • ブログ構築TS
  • 8. NetlifyFunctionsを使った検索機能
  • No.6 / 9

今回達成すること

  • アプリ全体で使用する基本レイアウトファイル
  • ヘッダに表示するツールバーコンポーネント
  • 検索キーワードを入力するフォームコンポーネントを作成します。

2022-02-17 23-30-13

レイアウト・コンポーネントファイル設計

2022-02-17 19-33-48

検索フォームを作成するにあたって、以下のファイルを作成します。

  • 「layouts」ディレクトリ

    • app.vue => 固定ページに使用する基本のレイアウトファイル。

      固定ページとは、動的なルートを生成しない以下のページファイルを指します。

      • ホーム ... index.vue
      • プライバシーポリシー ... privacy.vue
      • 検索結果表示ページ ... search.vue
  • 「components」ディレクトリ

    以下の設計でディレクトリを作成します。

    • 「App」ディレクトリ => アプリ全体で使用するコンポーネントを格納するディレクトリ。
      • AppToolbar.vue => レイアウトファイルで使用するツールバーの部品を集約したコンポーネント。
      • AppToolbarAppBar.vue => ツールバーの部品。
    • 「Ui」ディレクトリ => アプリのUI部品コンポーネントを格納するディレクトリ。
      • UiSearchForm.vue => ツールバーに表示する検索フォーム。

検索キーワードのデータ設計

エンドユーザーが入力する検索キーワードをdata()に保管した場合、そのコンポーネントが破棄されるまで値が初期化されません。

つまりページ遷移を行なっても値が残ってしまいます。

そこでどのページファイルからでも値を初期化できるように、検索キーワードはVuexに保管します。

検索フォームコンポーネントファイルを作成する

UiSearchForm.vueは、「components/Ui」ディレクトリに作成します。

% mkdir components/Ui && touch $_/UiSearchForm.vue

検索フォームの実装は後で行うので、とりあえず仮編集でOKです。

components/Ui/UiSearchForm.vue
<template>
  <div>
    UiSearchForm.vue
  </div>
</template>

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

@Component
export default class UiSearchFormComponent extends Vue {
}
</script>

ツールバーコンポーネントを作成する

「components/App」ディレクトリに

  • AppToolbar.vue
  • AppToolbarAppBar.vue

を作成します。

% mkdir components/App && touch $_/{AppToolbar.vue,AppToolbarAppBar.vue}

AppToolbar.vueの構築

AppToolbar.vueは、ツールバーの部品を集約します。

<app-toolbar-app-bar />を呼び出しましょう。

components/App/AppToolbar.vue
<template>
  <div>
    <app-toolbar-app-bar />
  </div>
</template>

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

@Component
export default class AppToolbarComponent extends Vue {
}
</script>

AppToolbarAppBar.vueの構築

AppToolbarAppBar.vueには、Vuetifyの<v-app-bar>を使用してツールバーを構築します。

components/App/AppToolbarAppBar.vue
<template>
  <v-app-bar
    app
    tite
    elevate-on-scroll
    color="white"
  >
    <v-toolbar-title>
      <nuxt-link
        to="/"
        class="text-body-2 text-decoration-none text--primary font-weight-bold"
      >
        {{ appName }}
      </nuxt-link>
    </v-toolbar-title>

    <v-spacer />

    <ui-search-form />
  </v-app-bar>
</template>

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

@Component
export default class AppToolbarAppBarComponent extends Vue {
  appName: string = this.$config.appName
}
</script>
  • <v-app-bar>

    • app ... ツールバーをヘッダに固定する。

    • tite ... 角を角張ったツールバーに。

    • elevate-on-scroll ... スクロール時にツールバーに影を付ける。

      参考: App-bar component - Vuetify

  • <v-spacer /> ... コンテンツを両端に寄せたい時に使用するVuetifyのコンポーネント。

    参考: Grid system — Vuetify

基本レイアウトファイルを作成する

レイアウトファイルのapp.vueは、アプリの基本レイアウトファイルとなります。

特にカスタマイズが必要のないページには、このapp.vueを使用するルールとします。

% touch layouts/app.vue

app.vueから先ほど作成した<app-toolbar />を呼び出します。

layouts/app.vue
<template>
  <v-app>
    <app-toolbar />
    <v-main>
      <nuxt />
    </v-main>
  </v-app>
</template>

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

@Component
export default class AppLayout extends Vue {
}
</script>

レイアウトファイルを呼び出そう

固定ページの

  • index.vue
  • privacy.vueから

app.vueのレイアウトを使用するよう設定します。

nuxt-property-decoratorでは、layout()関数を呼び出すことで、レイアウトファイルを指定することができます。

pages/index.vue
@Component
export default class IndexPage extends Vue {
  // 追加
  layout (): string {
    return 'app'
  }
  ...
}
pages/privacy.vue
@Component
export default class PrivacyPage extends Vue {
  // 追加
  layout (): string {
    return 'app'
  }
  ...
}

いい感じに表示されました。

2022-02-17 22-22-03

Vuexに検索キーワードを用意する

store/index.tsstatesearchQueryを用意します。

ここにはユーザーが入力した検索キーワードが保存されます。

store/index.ts
export const state = () => ({
  ...
  // 追加
  // 検索クエリ
  searchQuery: '' as string
})

export const mutations = mutationTree(state, {
  ...
  // 追加
  setSearchQuery (state: RootState, payload: string): void {
    state.searchQuery = payload
  }
})

export const actions = actionTree({ state, getters, mutations }, {
  ...
  // 追加
  // 検索クエリのセット
  getSearchQuery ({ commit }, query: string) {
    commit('setSearchQuery', query)
  }
})

OK!これで全ての準備は整いました。

検索フォームを作成する

UiSearchForm.vueを完成させます。

v-modelを使って、ユーザーがフォームに入力するたびにVuexのstateに検索キーワードを保存します。

components/Ui/UiSearchForm.vue
<template>
  <div>
    <!-- test -->
    {{ query }}
    <v-form
      @submit.prevent="onSubmit"
    >
      <v-text-field
        v-model="setQuery"
        dense
        hide-details
        rounded
        clearable
        placeholder="キーワードを入力"
        :solo="isFocused"
        :filled="!isFocused"
        :style="{ minWidth: '180px' }"
        @focus="focus"
        @blur="blur"
      />
    </v-form>
  </div>
</template>

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

@Component
export default class AppSearchFormComponent extends Vue {
  isFocused: boolean = false

  get query (): string {
    return this.$accessor.searchQuery
  }

  get setQuery (): string {
    return this.query
  }

  set setQuery (newVal: string) {
    this.$accessor.getSearchQuery(newVal)
  }

  // 入力必須 && 空白以外 && 現在のQueryとの変化があった場合にtrueを返す
  get isValid (): boolean {
    return !!this.query &&
      !/^\s+$/.test(this.query) &&
      this.$route.query.q !== this.query
  }

  // フォーカス時
  focus (_event: object): void {
    this.isFocused = true
  }

  // フォーカスが離れた時
  blur (_event: object): void {
    this.isFocused = false
  }

  // submitイベント
  onSubmit () {
    if (this.isValid) {
      this.$router.push({ path: '/search', query: { q: this.query } })
    }
  }
}
</script>
  • <v-form>
    • @submit.prevent ... エンターキーでメソッドを実行するためのイベント。prevent修飾子をつけることで、ページリロードが行われず、クライアント上でページ遷移を行うことが可能になる。
  • <v-text-field>
    • dense ... フォームを細く。
    • hide-details ... フォーム下に表示される余白(バリデーションメッセージ用)を非表示にする。
    • rounded ... フォームに丸みを付ける。
    • clearable ... フォームに値が入力されたときに「×」のクリアボタンを表示する。
    • :solo="isFocused" ... フォームにフォーカスされた時にsoloのスタイルを適用。
    • :filled="!isFocused" ... フォームからフォーカスが外れた時にfilledのスタイルを適用。
    • @focus ... フォーカスされた時に発火するイベント。
    • @blur ... フォーカスが外れた時に発火するイベント。
  • get query () ... Vuexのstateに用意したsearchQueryの値を呼び出している。

Vuexのstateの値にv-modelを使用したい場合

v-modelに直接Vuexの値を渡すとエラーになります。

一番スマートな書き方は、JavaScriptのゲッターとセッターを使用することです。

  • ゲッターには現在の値をreturnで返し、
  • セッターの引数で新しい値を取得できるので
  • その値をVuexのアクションメソッドに渡します。
get setQuery (): string {
  return this.query
}

set setQuery (newVal: string) {
  this.$accessor.getSearchQuery(newVal)
}

この書き方を行えば、Vuexのstateの値をv-modelに渡すことができます。

submitイベント

submitイベントでは、onSubmit()メソッドを呼び出しています。

バリデーションがtrueを返す場合に$router.push()を使用して、search.vueにページ遷移します。

$router.push()は、クライアントでページ遷移するメソッドで、<nuxt-link>と同じ挙動を行います。

ページ遷移時にはqueryプロパティにより、検索キーワードをsearch.vueに渡します。

onSubmit () {
  if (this.isValid) {
    this.$router.push({ path: '/search', query: { q: this.query } })
  }
}

このsearch.vueは次回作成するので、今はページ遷移できません。

検索フォームに入力してみよう

表示された検索フォームに値を入力すると、queryの値がリアルタイムで変化していることがわかります。

これはVuexのstateの値が、v-model に連動して変化している証拠です。

2022-02-17 23-26-35

確認が取れたらテストコードは削除してください。

components/Ui/UiSearchForm.vue
<template>
  <div>
    <!-- 削除 -->
    <!-- {{ query }} -->
    ...
</template>

今回の作業は以上です。

% git add -A
% git commit -m "Add search form & app toolbar component"

まとめと次回

今回は基本レイアウトファイルapp.vueを作成し、その中にツールバーと検索フォームを設置しました。

次回はページ遷移先であるsearch.vueを作成します。

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる。そんな方に向けた単発・短期間メンターサービスを行っています。
独学プログラマのサービス
ブログ構築TSの投稿
1
  • Nuxt.js×TypeScript開発環境構築
  • /
  • #01
Nuxt.jsをローカルPCに立ち上げよう
2
  • Nuxt.js×TypeScript開発環境構築
  • /
  • #02
Nuxt.jsプロジェクトをGitHubにPushしよう
3
  • Nuxt.js×TypeScript開発環境構築
  • /
  • #03
nuxt-property-decoratorのインストールとTypeScriptのセットアップ
1
  • Vuetifyセットアップ
  • /
  • #01
TypeScript環境のNuxt.jsにVuetifyを導入しよう
2
  • Vuetifyセットアップ
  • /
  • #02
VuetifyにカスタムCSSを追加してSASS変数を理解しよう
3
  • Vuetifyセットアップ
  • /
  • #03
VuetifyにカスタムSVGアイコンを追加しよう
1
  • NetlifyCLIを使ったNuxtデプロイ
  • /
  • #01
Netlify CLIをインストールして本番環境のサイトを作成しよう
2
  • NetlifyCLIを使ったNuxtデプロイ
  • /
  • #02
netlify.tomlを使ってNuxt.jsをNetlifyに手動デプロイしよう
1
  • Contentfulモデル構築
  • /
  • #01
Contentfulの料金とCommunityプランの無料枠を理解する
2
  • Contentfulモデル構築
  • /
  • #02
Contentfulへ新規会員登録、ロケールの変更、API Keyの発行を行う
3
  • Contentfulモデル構築
  • /
  • #03
Contentful ブログカテゴリーモデルを作成しよう
4
  • Contentfulモデル構築
  • /
  • #04
Contentful カテゴリーモデルに1対1で関連づくblogPostモデルを作成しよう
5
  • Contentfulモデル構築
  • /
  • #05
Contentful ブログ記事に1対多で関連づくplogTagモデルを作成しよう
6
  • Contentfulモデル構築
  • /
  • #06
Contentful カテゴリー・ブログ記事・タグコンテンツを作成しよう
1
  • Nuxt.js×Contentfulセットアップ
  • /
  • #01
Nuxt.js×Contentfulセットアップ。モジュールのインストールからAPI Keyの登録まで
2
  • Nuxt.js×Contentfulセットアップ
  • /
  • #02
Contentful APIリクエストの実行 Nuxt.jsにブログコンテンツを表示しよう
3
  • Nuxt.js×Contentfulセットアップ
  • /
  • #03
ContentfulAPIをNetlifyにデプロイしよう【Nuxt FullStaticのasyncDataとfetch】
1
  • Vuex×TypeScriptセットアップ
  • /
  • #01
Vuexの型付け vuex-module-decoratorsとnuxt-typed-vuexどちらを使用するか
2
  • Vuex×TypeScriptセットアップ
  • /
  • #02
nuxt-typed-vuexのインストールとセットアップ。Vuexの型定義と呼び出し方
3
  • Vuex×TypeScriptセットアップ
  • /
  • #03
VuexにContentfulの型定義ファイルとnuxtServerInitを追加しよう
4
  • Vuex×TypeScriptセットアップ
  • /
  • #04
VuexにContentfulAPIレスポンスを保存してVueファイルに表示しよう
1
  • コンテンツページ構築
  • /
  • #01
ブログアプリのページ設計とNuxt.jsの動的ルーティングについて理解しよう
2
  • コンテンツページ構築
  • /
  • #02
カテゴリーのコンテンツページを作成しよう【Nuxt.js×Contentful】
3
  • コンテンツページ構築
  • /
  • #03
カテゴリーに関連付くブログ記事一覧を表示しよう【Nuxt.js×Contentful】
4
  • コンテンツページ構築
  • /
  • #04
injectを使用して共通エラー処理メソッドを作成しよう【Nuxt×TypeScript】
5
  • コンテンツページ構築
  • /
  • #05
NuxtChildを使用してブログ記事ページを作成しよう【Nuxt.js×TypeScript】
6
  • コンテンツページ構築
  • /
  • #06
タグ一覧ページとタグ関連づく記事一覧を表示しよう【Nuxt.js×TypeScript】
7
  • コンテンツページ構築
  • /
  • #07
プライバシーポリシーページを作成しよう【Nuxt.js×TypeScript】
8
  • コンテンツページ構築
  • /
  • #08
@nuxtjs/i18nのインストールとセットアップ。ページタイトルの翻訳化【TypeScript】
1
  • NetlifyFunctionsを使った検索機能
  • /
  • #01
Netlify Functionsを使ってクエリを返す関数を作成しよう【Nuxt.js×TypeScript】
2
  • NetlifyFunctionsを使った検索機能
  • /
  • #02
Netlify Functionsプロジェクトをデプロイしよう【Nuxt.js×TypeScript】
3
  • NetlifyFunctionsを使った検索機能
  • /
  • #03
Nuxt.js × axiosセットアップ Netlify Functionsにリクエストを行う準備をしよう
4
  • NetlifyFunctionsを使った検索機能
  • /
  • #04
オリジン•CORS•プリフライトリクエストを理解する【Nuxt.js×Netlify Functions】
5
  • NetlifyFunctionsを使った検索機能
  • /
  • #05
Netlify Functionsを使ってフォームバリデーション機能を構築しよう【Nuxt.js】
6
  • NetlifyFunctionsを使った検索機能
  • /
  • #06
ツールバーに表示する検索フォームを作成しよう【Nuxt.js×TypeScript】
7
  • NetlifyFunctionsを使った検索機能
  • /
  • #07
検索ページを作成しよう【Vue propsとTypeScriptの書き方 解説】
8
  • NetlifyFunctionsを使った検索機能
  • /
  • #08
Netlify FunctionsからContentfulAPIリクエストを送ろう【Nuxt.js】
9
  • NetlifyFunctionsを使った検索機能
  • /
  • #09
検索ページに「もっと見る」ボタンを実装しよう【Nuxt.js×TypeScript】
1
  • ブログMarkdown対応
  • /
  • #01
@nuxtjs/markdownitのインストールとセットアップ【Nuxt.js×TypeScript】
2
  • ブログMarkdown対応
  • /
  • #02
Nuxt.js×markdown-it 外部リンクを別タブで開くプラグインを追加しよう
3
  • ブログMarkdown対応
  • /
  • #03
Nuxt.js×markdown-it 内部リンクをVueRouterで高速にページ遷移しよう
4
  • ブログMarkdown対応
  • /
  • #04
Nuxt.js×markdown-it アンカーリンクとブログ目次を自動生成しよう
独学プログラマ
独学でも、ここまでできるってよ。
CONTACT
Nuxt.js制作のご依頼は下記メールアドレスまでお送りください。