"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

BulmaをベースにしたUIフレームワーク「Buefy」で体裁を整える。/NUXT自己流チュートリアル(1)-6

今回は画面にUIフレームワーク「Buefy」を適用して、画面の体裁を整えます。

NUXT自己流チュートリアル(5)までは、Nuxt.js・Vue.jsの基本機能を試すため、簡単な画面を作り、あえてCSS等で体裁を整えることをしませんでした。

今回でちょっとだけ「マシに見える」程度にはしたいなと思います。

f:id:arakan_no_boku:20190509225706p:plain

なお、この記事は「NUXT自己流チュートリアル(1)」として続き物で書いている記事の6回目です。

1回目から続けて読んでもらえることを想定して書いていますのでご了承ください。

arakan-pgm-ai.hatenablog.com

 

事前に頭にいれといた方がいいこと

 

自己流チュートリアルのためのインストール時に選択したUIフレームワークは「Buefy」を使います。

arakan-pgm-ai.hatenablog.com

Buefyは「Bulma」のCSSをベースに、各パーツなどを「Vue.js」を適用して強化した「Vue.jsのためのUIフレームワーク」です。

ざっくり役割分担を見てみると。

  • レイアウトや基本的なコンテンツ(h1とかpとか)は「Bulma」のclass
  • Formコントロールや拡張ICONとかは「b-input」などのBuefyで拡張した機能

それぞれ利用する感じです。

もちろん、混在もできますし、Vue.jsとの親和性も問題ありません。

まさに、Buefyは「Lightweight UI components for Vue.js based on Bulma」です。

フレームワークですから使用にあたっての決め事はきちんと守る必要があります。

例えば。

<h1>とか<ul>みたいな一般的なタグにCSSの効果を出すためには、「content」classで囲っておかないといけない・・とかですね。

そのあたりのルールは、BulmaおよびBuefyの各ドキュメントのサンプルを見るのがはやいです。

buefy.org

bulma.io

 

とりあえず体裁を整える

 

今回は「NUXT自己流チュートリアル(5)」のソースがベースです。

<script>~</script>の部分は変更しません。

なので、ソースコードとしては、共通レイアウト(default.vue)以外のソースは<templete>~</templete>の部分だけを掲載しています。

 

全体のレイアウト(default.vue)

 

まず、共通レイアウトで、ヘダーとフッターを設定します。

とりあえず、こんな感じにします。

フッターには当日の日付と曜日を表示するようにしてみました。

真ん中に「<nuxt/>」と書いている部分に、pagesフォルダで定義したイメージが挿入される感じです。

なお、イメージです。

実際に<nuxt/>と表示するようにレイアウトをつくったりはしていません(笑)。

f:id:arakan_no_boku:20190522204641p:plain

layouts/default.vue

<template>
  <div>
    <section class="hero is-primary">
      <div class="hero-body">
        <div class="container  has-text-centered">
          <h1 class="title">
            BOKUのサンプル画面
          </h1>
          <h2 class="subtitle">
            Nuxt自己流チュートリアル
          </h2>
        </div>
      </div>
    </section>
    <section>
      <nuxt />
    </section>
    <footer class="footer">
      <div class="content has-text-centered">
        <p>
          <strong>今日は{{ today }}です。</strong>
        </p>
      </div>
    </footer>
  </div>
</template>

<script>
import moment from 'moment'
export default {
  computed: {
    today () {
      return moment().format('YYYY/MM/DD dddd')
    }
  }
}
</script>

ほぼ、BulmaのHeroとfooterタグをほぼサンプル通りに使っているだけです。

momentを使ってシステム日を取得してるとこと位ですかね。

違うところは。 

 

pages/index.vue

 

pagesフォルダにおく「.vue」ファイルはの内容が、全体レイアウトの<nuxt/>の部分に展開されるわけです。

まず、ルート画面にあたる「index.vue」のイメージはこちらです。

f:id:arakan_no_boku:20190522203125p:plain

ソースはこちらです。

<template>
  <div>
    <div class="container content has-text-centered">
      <img src="~assets/00_nuxt.JPG" alt="image01">
    </div>
    <div class="container content has-text-centered">
      <NLink to="/about">
        <h3>{{ linkname1 }}</h3>
      </NLink>
    </div>
    <div class="container content has-text-centered">
      <NLink to="/cityapi">
        <h3>{{ linkname2 }}</h3>
      </NLink>
    </div>
    <div class="container content has-text-centered">
      <a :href="outurl">
        <h3>{{ linkname3 }}</h3>
      </a>
    </div>
  </div>
</template>

補足しておきます。

ごくシンプルに真ん中寄せしています。

<div class="container content has-text-centered">

ある程度、これでいけます。

ただ、画像の中央寄せなどは注意が必要です。

Bulmaのサンプルを見ると、イメージファイルは「<figure class="image is-128x128">」みたいなタグで囲ってます。

しかし、そうすると「has-text_centered」では中央寄せができなくなります。

別のやり方を画像だけ適用しないといけなくなるわけです。

でも、上記のように、シンプルに<img>タグだけでやると「has-text-centered」がきくので、今回は手抜きして、そうしてます。

あと「content」。

これが重要です。

Bulmaでは「content」で囲っていないと、h1やp,ulなどのタグのCSS定義が適用されないようになってます。

 

pages/about.vue

 

郵便番号検索の画面です。

これも単純に中央に寄せて、スタイルを適用してるだけです。

表示したイメージはこんな感じで。

f:id:arakan_no_boku:20190522215528p:plain

ソースコードはこうです。

<template>
  <div>
    <div class="container content has-text-centered">
      <NLink to="/">
        <p>ルートのページへ戻ります</p>
      </NLink>
    </div>
    <div class="container content has-text-centered">
      <img src="/post.JPG" alt="image02">
    </div>
    <div class="columns">
      <div class="column" />
      <div class="column">
        <div class="column">
          <b-field label="郵便番号">
            <b-input v-model="zip" placeholder="郵便番号を入力する" />
          </b-field>
        </div>
      </div>
      <div class="column" />
    </div>
    <div class="container  has-text-centered">
      <b-button type="is-primary" @click="getAddr({zip})">
        住所情報取得
      </b-button>
    </div>
    <hr>
    <div v-if="datas != null" class="container content has-text-centered">
      <div v-if="datas.status == 200">
        <h4>郵便番号:{{ datas.results[0].zipcode }}</h4>
        <h4>都道府県名:{{ datas.results[0].address1 }}</h4>
        <h4>市区町村名:{{ datas.results[0].address2 }}</h4>
        <h4>町域名:{{ datas.results[0].address3 }}</h4>
      </div>
      <div v-else>
        <h4>指定の郵便番号では住所情報の取得ができませんでした。</h4>
      </div>
    </div>
    <div v-else class="container content has-text-centered">
      <br>
      <p>郵便番号を入力してボタン「住所情報取得を押します。</p>
    </div>
  </div>
</template>

ポイントだけ補足します。

入力欄とボタンについては、<b-xxxx>のBuefyのタグを使ってます。

入力欄は。

<b-field label="郵便番号">
    <b-input v-model="zip" placeholder="郵便番号を入力する"></b-input>
</b-field> 

のように、<b-field>で囲います。

そうするとラベルをつけるのが楽です。

ただ、気をつけないといけないのが、位置合わせの方法です。

ボタンは「<b-button>」に変更しても、has-text-centeredでセンタリングできます。

でも、<b-input>はききません・・というか、めいっぱい横に広がってしまいます。

そのため「 <div class="columns">」のなかに「<div class="column">」を3つ配置して、真ん中に「<b-input>」を配置することで、位置合わせしてます。

 

pages/cityapi.vue

 

都道府県と市区町村の選択ページです。

これも単純に中央寄せして、スタイルを適用してるだけです。

f:id:arakan_no_boku:20190522225447p:plain


ソースコードはこうです。

<template>
  <div>
    <div class="container content has-text-centered">
      <NLink to="/">
        <p>ルートのページへ戻ります</p>
      </NLink>
    </div>
    <div class="container content has-text-centered">
      <img src="/jamap.JPG" alt="image03">
    </div>
    <div v-if=" prefecturesJson.message == null" class="columns">
      <div class="column" />
      <div class="column is-half">
        <b-field horizontal label="都道府県" type="is-primary">
          <b-select v-model="prefecturesSelected" expanded @input="updateMunicipalitiesJson({prefecturesSelected})">
            <option v-for="pOption in prefecturesJson.result" :key="pOption.id" :value="pOption.prefCode">
              {{ pOption.prefName }}
            </option>
          </b-select>
        </b-field>
      </div>
      <div class="column" />
    </div>
    <div v-if="municipalitiesJson != null" class="columns">
      <div class="column" />
      <div class="column is-half">
        <b-field horizontal label="市区町村" type="is-primary" :v-if="municipalitiesJson.message == null">
          <b-select v-model="municipalitiesSelected" expanded>
            <option v-for="mOption in municipalitiesJson.result" :key="mOption.id" :value="mOption.cityCode">
              {{ mOption.cityName }}
            </option>
          </b-select>
        </b-field>
      </div>
      <div class="column" />
    </div>
    <div v-else class="columns">
      <div class="column" />
      <div class="column is-half">
        <b-field horizontal label="市区町村">
          <b-select v-model="municipalitiesSelected" expanded>
            <option value="" />
          </b-select>
        </b-field>
      </div>
      <div class="column" />
    </div>
  </div>
</template>

ポイントを補足します。 

Selectを使ってます。

Selectも「has-text-centered」はきかないので、<column>でレイアウトしてます。

Selectは「<b-select v-model="municipalitiesSelected" expanded>」のようにBuefyの「b-select」を使ってます。 

Bulmaの「<select>」から、「<b-select>」に変更した時に、気をつけないといけないことがあります。

Bulmaの「<select>」の場合は「@change」で選択されたイベントをひろって、処理をキックすることができます。

でも、「<b-select>」に変更すると「@change」では発火しません。

イベントのトリガを「@input」に変更する必要があります。

 

まとめです

 

今回は、NUXT自己流チュートリアル(5)までで作った画面に、BlumaとBeufyのスタイルを適用して、体裁を整えて一区切りにしました。

多様な機能のごく一部しか使ってないですが、それでも、ヘダーとフッターをつけて、体裁を整えるだけで、かなり見た目が変わりますね

BuefyのようなUIフレームワークは、センスの良い人が考えたベストプラクティスを簡単にとりいれることができて、センスのない自分みたいな人間にはありがたいです。

 

最後にソース全体を掲載しておきます。

 

今回変更したえのvueファイルのみです。

pages/index.vue

<template>
  <div>
    <div class="container content has-text-centered">
      <img src="~assets/00_nuxt.JPG" alt="image01">
    </div>
    <div class="container content has-text-centered">
      <NLink to="/about">
        <h3>{{ linkname1 }}</h3>
      </NLink>
    </div>
    <div class="container content has-text-centered">
      <NLink to="/cityapi">
        <h3>{{ linkname2 }}</h3>
      </NLink>
    </div>
    <div class="container content has-text-centered">
      <a :href="outurl">
        <h3>{{ linkname3 }}</h3>
      </a>
    </div>
  </div>
</template>

<script>
import moment from 'moment'
export default {
  head: {
    title: 'First page'
  },
  data () {
    return {
      outurl: 'https://ja.nuxtjs.org/',
      linkname3: '(外部リンク)v-bindのサンプル',
      linkname2: '(内部リンク)市区町村サンプル',
      linkname1: '(内部リンク)郵便番号サンプル'
    }
  },
  computed: {
    today () {
      return moment().format('YYYY/MM/DD dddd')
    }
  }
}
</script>

 

pages/about.vue

<template>
  <div>
    <div class="container content has-text-centered">
      <NLink to="/">
        <p>ルートのページへ戻ります</p>
      </NLink>
    </div>
    <div class="container content has-text-centered">
      <img src="/post.JPG" alt="image02">
    </div>
    <div class="columns">
      <div class="column" />
      <div class="column">
        <div class="column">
          <b-field label="郵便番号">
            <b-input v-model="zip" placeholder="郵便番号を入力する" />
          </b-field>
        </div>
      </div>
      <div class="column" />
    </div>
    <div class="container  has-text-centered">
      <b-button type="is-primary" @click="getAddr({zip})">
        住所情報取得
      </b-button>
    </div>
    <hr>
    <div v-if="datas != null" class="container content has-text-centered">
      <div v-if="datas.status == 200">
        <h4>郵便番号:{{ datas.results[0].zipcode }}</h4>
        <h4>都道府県名:{{ datas.results[0].address1 }}</h4>
        <h4>市区町村名:{{ datas.results[0].address2 }}</h4>
        <h4>町域名:{{ datas.results[0].address3 }}</h4>
      </div>
      <div v-else>
        <h4>指定の郵便番号では住所情報の取得ができませんでした。</h4>
      </div>
    </div>
    <div v-else class="container content has-text-centered">
      <br>
      <p>郵便番号を入力してボタン「住所情報取得を押します。</p>
    </div>
  </div>
</template>
<script>
import axios from 'axios'
export default {
  data () {
    return {
      zip: '',
      datas: null
    }
  },
  asyncData () {

  },
  methods: {
    async getAddr ({ zip }) {
      const url = `http://zipcloud.ibsnet.co.jp/api/search?zipcode=${zip}&limit=1`
      await axios.get(url)
        .then((res) => {
          this.datas = res.data
        })
      return this.data
    }
  },
  head: {
    title: 'About page'
  }
}
</script>

 

pages/cityapi.vue

<template>
  <div>
    <div class="container content has-text-centered">
      <NLink to="/">
        <p>ルートのページへ戻ります</p>
      </NLink>
    </div>
    <div class="container content has-text-centered">
      <img src="/jamap.JPG" alt="image03">
    </div>
    <div v-if=" prefecturesJson.message == null" class="columns">
      <div class="column" />
      <div class="column is-half">
        <b-field horizontal label="都道府県" type="is-primary">
          <b-select v-model="prefecturesSelected" expanded @input="updateMunicipalitiesJson({prefecturesSelected})">
            <option v-for="pOption in prefecturesJson.result" :key="pOption.id" :value="pOption.prefCode">
              {{ pOption.prefName }}
            </option>
          </b-select>
        </b-field>
      </div>
      <div class="column" />
    </div>
    <div v-if="municipalitiesJson != null" class="columns">
      <div class="column" />
      <div class="column is-half">
        <b-field horizontal label="市区町村" type="is-primary" :v-if="municipalitiesJson.message == null">
          <b-select v-model="municipalitiesSelected" expanded>
            <option v-for="mOption in municipalitiesJson.result" :key="mOption.id" :value="mOption.cityCode">
              {{ mOption.cityName }}
            </option>
          </b-select>
        </b-field>
      </div>
      <div class="column" />
    </div>
    <div v-else class="columns">
      <div class="column" />
      <div class="column is-half">
        <b-field horizontal label="市区町村">
          <b-select v-model="municipalitiesSelected" expanded>
            <option value="" />
          </b-select>
        </b-field>
      </div>
      <div class="column" />
    </div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      prefecturesSelected: '',
      municipalitiesSelected: '',
      municipalitiesJson: null
    }
  },
  async asyncData (context) {
    return { prefecturesJson: await context.app.$getPrefecturesJson() }
  },
  methods: {
    async updateMunicipalitiesJson ({ prefecturesSelected }) {
      this.municipalitiesJson = await this.$getmunicipalitiesJson(prefecturesSelected)
    }
  },
  head: {
    title: 'CityApi page'
  }
}
</script>

さて、今回はこんなところで・・。 

ではでは。