20200623のvue.jsに関する記事は9件です。

bootsrtapVueの<b-navbar>で超簡単レスポンシブ対応ヘッダー!!(サンプルコードあり)

はじめに

私、完全な初学者でVue.jsを使用して簡単なポートフォリオサイトを作成しようとしていたところレスポンシブ対応がとても面倒臭く感じました。何か簡単にできないかと調べていたところ、発見した記事を参考にして作成したbootstrap4のクラスを使用しましたが、ハンバーガーメニューを押しても反応しなかっため、途方に暮れていました。しかし、根気よく調べているとbootstrapVueの<b-navbar>を使用すると簡単に、しかもちゃんと反応するものを作成できたので紹介します!最後にヘッダーのvueファイルをサンプルコードとして記載しています。

一部表現が間違っている部分があるかもしれません。ご了承ください。

対象者

vue-cliでプロジェクトを作成してvueファイルを使用している方

プロジェクトの作り方やbootstrapVueを使用できるようにすることは他の方の記事を参考にしてください。

<b-navbar>とは

ここから先の説明は基本的にはbootstrapVueの公式ドキュメント(https://bootstrap-vue.org/docs/components/navbar) に載っているものを扱います。

<b-navbar>コンポーネントは、ブランディング、ナビゲーション、その他の要素を簡潔なヘッダーに配置するラッパーです。簡単に拡張でき、<b-collapse>コンポーネントのおかげで、レスポンシブな動作を簡単に統合することができます。

以下、公式ページに載っているサンプルコード↓に沿って、ハンバーガーメニューに必要な最低限の要素について説明をしていきます。

sample
<div>
  <b-navbar toggleable="lg" type="dark" variant="info">
    <b-navbar-brand href="#">NavBar</b-navbar-brand>
    <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
    <b-collapse id="nav-collapse" is-nav>
      <b-navbar-nav>
        <b-nav-item href="#">Link</b-nav-item>
        <b-nav-item href="#" disabled>Disabled</b-nav-item>
      </b-navbar-nav>

      <!-- Right aligned nav items -->
      <b-navbar-nav class="ml-auto">
        <b-nav-form>
          <b-form-input size="sm" class="mr-sm-2" placeholder="Search"></b-form-input>
          <b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
        </b-nav-form>

        <b-nav-item-dropdown text="Lang" right>
          <b-dropdown-item href="#">EN</b-dropdown-item>
          <b-dropdown-item href="#">ES</b-dropdown-item>
          <b-dropdown-item href="#">RU</b-dropdown-item>
          <b-dropdown-item href="#">FA</b-dropdown-item>
        </b-nav-item-dropdown>

        <b-nav-item-dropdown right>
          <!-- Using 'button-content' slot -->
          <template v-slot:button-content>
            <em>User</em>
          </template>
          <b-dropdown-item href="#">Profile</b-dropdown-item>
          <b-dropdown-item href="#">Sign Out</b-dropdown-item>
        </b-nav-item-dropdown>
      </b-navbar-nav>
    </b-collapse>
  </b-navbar>
</div>

説明

<b-navbar>

これで全体を囲む

プロパティ

toggleable=“lg”ブレイクポイント(どの大きさでハンバーガーメニューに切り替えるか)決める
type=“dark"   色を決める
variant=“info" 色を決める(おそらく背景)

typeとvariantに設定する文字はbootstrapお馴染みの文字なので調べると出てきます。コードのdark,infoの部分をお好みで変更してください
toggleableに関しても指定する文字はbootstrapお馴染みのsmなどのサイズ指定の文字なので調べたら出てくると思います。お好みでlgの部分をお好みで変更してください。

<b-navbar-brand>

サイト全体のロゴを表示するコンポーネント
<a>タグみたいに<b-navbar-brand>文字</b-navbar-brand>で囲って文字を表示

プロパティ

href=“”orto=“”

vue-routerを使っている場合はhrefではなく<router-link>のプロパティ同様で指定

<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

ハンバーがメニューを押したら出てくる要素の指定(target)

プロパティ

target

適当な文字列を指定
その文字列をidプロパティで保持するタグをハンバーガーに追加

<b-navbar-nav>

ヘッダーのどこに配置するかを決めるためのコンポーネント
この小要素が主要な表示するものになる

//デフォルトで左よせ(多分)
<b-navber-nav>

</b-navbar-nav>

//ml-auto bootstrap4のクラス(margin-left:auto;の意味)を使用して右よせ
<b-navber-nav class=“ml-auto">
   
</b-navbar-nav>

<b-navbar-item>

<b-navbar-nav>が子要素として持てるタグの1つ
リンクのために使用使用法は<a>タグと一緒

<b-navbar-brand>同様urlの指定はhref=“”,router-linkの指定はto=“"で行う

以上が使用するものになります。

他にも<b-navber-nav>が子要素として持てるとして<b-nav-item-dropdown><b-nav-text><b-nav-form>などありますが、詳しく知りたい方は上記に載せたbootstrapVue公式を参照してください。

最後にヘッダーを作成するサンプルコードを載せておきます。
下の例では vue-router でルーティングしています。

また、ヘッダーを上部で固定しています。

サンプルコード

myheader.vue
<template>
  <div class="myheader">  
    <b-navbar toggleable="sm" type="dark" variant="info">
      <b-navbar-brand to=“/“>HOME</b-navbar-brand>
      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item to="/about">about</b-nav-item>
          <b-nav-item to="/game">game</b-nav-item>
          <b-nav-item to="/paint">paint</b-nav-item>
          <b-nav-item to="/scrap">scrap</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
  </div>
</template>

<style lang="scss" scoped>
.myheader {
  position: fixed;
  width: 100%;
  z-index: 1;
}
</style>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue CLI と kintone CLI の共存を探る(TypeScript 編)

Vue CLI でセットアップしたプロジェクトで kintone CLI を利用したカスタマイズ環境を作る目論見です。
どうにかこうにか形になりました。
ざっくり書いていますので読みづらいかも知れません。

こちらは TypeScript 編です。
JavaScript 編は以下をご覧ください。

Vue CLI と kintone CLI の共存を探る(JavaScript 編)

目的

Vue CLI で作られるプロジェクトの整然さと kintone CLI で行えるカスタマイズの開発やデプロイの簡易性を両立させる。
TypeScript での開発を可能とし、@kintone/rest-api-client を利用する。
kintone UI Component は諦める。(React でしか動かないので)
場合により Vuetify の利用を検討する。

フォーマッティング・lint に関しては以下の方針。
Vetur はフォーマットを実行させず、シンタックスハイライトなどの役割に専念。
eslint は静的解析のみ担当。コードフォーマットはしない。
コードフォーマットは Prettier が実行。

前提

Vue CLI および kintone CLI がグローバルインストールされている事。

npm install -g @vue/cli
npm install -g git://github.com/kintone/kintone-cli.git

プロジェクト作成手順

Vue.js プロジェクトをセットアップする

Vue CLI でプロジェクトを開始する。

vue create vue-cli-kintone-cli-ts

以下の選択肢で進める。

? Check the features needed for your project:

  • Babel
  • TypeScript
  • Vuex
  • CSS Pre-processors
  • Linter / Formatter
  • Unit Testing

? Use class-style component synytax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
? Pick a linter / formatter config: ESLint + Prettier
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

kintone CLI でアプリを作成する

作成されたプロジェクトを VS Code で開き、ターミナルを起動する。
kintone CLI でアプリをセットアップする。

kintone-cli create-template

? What type of app you want to create ? Customization
? Do you want to set authentication credentials ? Yes
? What is your kintone domain ? (サブドメイン).cybozu.com
? What is your kintone username ? (ユーザー名)
? What is your kintone password ? (パスワード)
? Do you use proxy ? No
? Do you want to use React ? No
? Do you want to use TypeScript ? Yes
? Do you want to use webpack ? Yes
? What is the entry for webpack ? main.ts
? What is the app name ? app
? Do you want to use @cybozu/eslint-config for syntax checking ? No
? What is the app ID ? (アプリ ID)
? What is the scope of customization ? ALL

app ディレクトリの下にソースが作成される。

開発サーバーをインストールする

この手順だとローカルプレビュー用の local-web-server がインストールされないため、手動で追加する。
現時点(2020 年 6 月)での最新版は 4.2 だが、このバージョンでは正しく動作しない模様。
kintone-cli でインストールされるものと同じバージョンの 2.6.1 をインストールする。

yarn add -D local-web-server@2.6.1

その他の関連ツールをインストールする

core-js が二重に入っている。
いったんアンインストールして最新版を追加。

yarn remove core-js
yarn add core-js

@kintone/rest-api-client, @kintone/dts-gen をインストールする。

yarn add @kintone/rest-api-client
yarn add -D @kintone/dts-gen

ESLint・Prettier の設定

以下を参考にする。
続・VSCode 上で vue ファイルに対して ESLint と Prettier が快適に動作する設定
VS Code で ESLint × Prettier のベストかも知れないプラクティス

yarn add -D eslint-config-prettier

プロジェクトルート直下の .eslintrc.js を修正

module.exports = {
  extends: [
    // --- 省略
    "prettier",
  ],
};

プロジェクトルート直下に .eslintignore ファイルを作成。
dist フォルダを追跡から除外する。

.eslintignore
**/dist

標準のフォーマッティングを無効化し、Prettier が全て担当するようにする。
.vscode/settings.json に設定を加える。

.vscode/settings.json
{
  "javascript.format.enable": false,
  "typescript.format.enable": false,
  "vetur.format.enable": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

.prettierrc を作る。
設定はお好みで。

.prettierrc
{
  "semi": false,
  "singleQuote": true
}

ディレクトリ構成

プロジェクトルート直下の src 以下のファイルを app/source に移動。
app/tests/unit フォルダを作成。
残った srcpublictests は不要なので削除。

app/source/main.ts をとりあえず以下のようにする

app/source/main.ts
import Vue from "vue";
import App from "./App.vue";
import store from "./store";

Vue.config.productionTip = false;

kintone.events.on("app.record.index.show", (event) => {
  console.log("Hello from kintone CLI");

  const elem: Element = kintone.app.getHeaderSpaceElement() as Element;
  new Vue({
    store,
    render: (h) => h(App),
  }).$mount(elem);

  return event;
});

tsconfig.json の設定

プロジェクトルート直下と app の下に存在する。
プロジェクトルート直下の設定に寄せる。

app の下にある記述をプロジェクトルート直下の tsconfig.json にマージする。
この際 includepathstypeRoots のパスを調整する。
最終的には以下の通りとなる。

tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "typeRoots": ["node_modules/@types", "**/source/*.d.ts"],
    "types": ["webpack-env", "jest"],
    "paths": {
      "@/*": ["~/*"]
    },
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
  },
  "include": [
    "**/source/**/*.ts",
    "**/source/**/*.tsx",
    "**/source/**/*.vue",
    "**/tests/**/*.ts",
    "**/tests/**/*.tsx"
  ],
  "exclude": ["node_modules"]
}

app 下の tsconfig.json は削除ないしリネーム。

babel の設定

ビルドするために @babel/plugin-proposal-decorators のインストールが必要。

yarn add -D @babel/plugin-proposal-decorators

プロジェクトルート直下に babel.config.js が、 app の下に .babelrc が存在する。
プロジェクトルート直下の babel.config.jsapp/.babelrc の内容をマージする。
以下のようになる。

babel.config.js
module.exports = {
  plugins: [
    [
      "@babel/plugin-proposal-decorators",
      {
        legacy: true,
      },
    ],
    [
      "@babel/plugin-proposal-class-properties",
      {
        loose: true,
      },
    ],
    "@babel/plugin-syntax-dynamic-import",
  ],
  presets: [
    "@vue/cli-plugin-babel/preset",
    [
      "@babel/preset-env",
      {
        useBuiltIns: "usage",
        corejs: {
          version: 3,
          proposals: true,
        },
      },
    ],
  ],
};

その後 app/.babelrc は削除ないしリネーム。

Webpack の設定

.vue ファイルをビルドするために vue-loader, vue-template-compiler, ts-loader, url-loader, file-loader をインストールする。
また webpack の設定は開発ビルドとプロダクションビルドで設定を変えたいので、webpack-merge もインストールする。
加えて、プロダクションビルドではミニファイも行うので、 terser-webpack-plugin もインストールする。

yarn add -D vue-loader vue-template-compiler ts-loader url-loader file-loader webpack-merge terser-webpack-plugin

webpack の設定は app 以下のものを使用する。
app/webpack.config.jsapp/webpack.common.js にリネームし、内容を以下のようにする。

app/webpack.common.js
const path = require("path");
const VueLoaderPlugin = require("vue-loader/lib/plugin");

module.exports = {
  entry: path.resolve(__dirname, "./source/main.ts"),
  resolve: {
    extensions: [".ts", ".tsx", ".vue", ".js"],
  },
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: `${path.basename(__dirname)}.min.js`,
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: {
          loader: "vue-loader",
          options: {
            loaders: {
              js: {
                loader: "babel-loader",
              },
            },
          },
        },
      },
      {
        test: /.ts?$/,
        exclude: /node_modules/,
        use: {
          loader: "ts-loader",
          options: {
            transpileOnly: true,
            appendTsSuffixTo: ["\\.vue$"],
            happyPackMode: false,
          },
        },
      },
      {
        test: /.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(jpe?g|png|gif|svg|ico)(\?.+)?$/,
        use: {
          loader: "url-loader",
          options: {
            esModule: false,
            limit: 10000,
            fallback: {
              loader: "file-loader",
              options: {
                name: "img/[name].[ext]",
              },
            },
          },
        },
      },
    ],
  },
  performance: {
    maxEntrypointSize: 10000000,
    maxAssetSize: 10000000,
  },
  plugins: [new VueLoaderPlugin()],
};

開発ビルドの設定 app/webpack.dev.js は以下の通りにする。

app/webpack.dev.js
const merge = require("webpack-merge");
const common = require("./webpack.common.js");

module.exports = merge(common, {
  mode: "development",
});

プロダクションビルドの設定 app/webpack.prod.js は以下の通りにする。

app/webpack.prod.js
const merge = require("webpack-merge");
const common = require("./webpack.common.js");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = merge(common, {
  mode: "production",
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: { compress: { drop_console: true } },
      }),
    ],
  },
});

新たに app/webpack.config.js を作り、以下のようにする。
このファイルでビルドターゲットを判定して、適切なファイルをロードするようにする。

app/webpack.config.js
module.exports = (env, argv) => {
  const mode = argv.mode.includes("development") ? "dev" : "prod";
  return require(`./webpack.${mode}.js`);
};

Jest の設定

特にない。
必要に応じてプロジェクトルート直下の jest.config.js を編集すればよい。

package.json の設定

コマンドの入力を簡略化するため scripts を以下のようにする。
webpack の設定ファイルもここでコントロールする。

package.json
  "scripts": {
    "dev": "ws --port ****",
    "serve-app": "kintone-cli dev --app-name app --watch --localhost",
    "devel-app": "webpack --mode development --config app/webpack.config.js",
    "build-app": "webpack --mode production --config app/webpack.config.js",
    "deploy-app": "kintone-cli deploy --app-name app",
    "test:unit": "jest"
  },

ws --port **** は任意のポート番号。
以下の記事で解説している。
kintone CLI をざっくり試す

.gitignore の設定

これまでの流れで app/auth.json は含まれているはず。
app/dist も弾くようにする。
その上で、今後仮に app が増えても大丈夫なようにする。

以下の通り修正。

.gitignre
*/auth.json
*/dist

.vscode/settings.json は共有したいので、.vscode の行は削除する。

ここまでで開発準備は終了。
あとはひたすらに開発に注力する。

開発フェーズ

開発・テスト

実際の開発は app/source/ 以下のファイルに対して行っていく事になる。

開発モード(ローカルサーバ)で起動

yarn serve-app

ローカルサーバへの参照が kintone アプリに適用され、テストが行えるようになる。

単体テストは以下のように実行。

yarn test:unit

テストファイルは app/tests/unit/ 以下に **.spec.ts のファイル名パターンで記述する。

ビルド

yarn build-app

app フォルダの下に dist フォルダが作成されファイルがビルドされる。
ビルドしたファイルを手動で運用環境に適用したい場合に使う。
webpack.prod.js では TerserPlugin によりミニファイと console の除去が行われる。

デプロイ

yarn deploy-app

ビルドとデプロイを同時に実行するコマンド。
ビルドが実行され、終了後に運用環境にデプロイされる。

2 つめ以降のアプリのカスタマイズを作る場合

プロジェクトが複数のアプリカスタマイズで構成される場合、いくつかアプローチがある。

kintone-cli create-template を使う

kintone CLI 的には王道パターンではある。

kintone-cli create-template --app-name (新しいアプリ名)

と実行し、カスタマイズを新規で作成する。

作成されたフォルダに、既存のアプリカスタマイズのファイルをかぶせてやる。(要微修正)

この方法だと改めて依存モジュール類を再取得しに行く動作をするため、時間が掛かる上にせっかく整えた package.json がまた汚れてしまう。
core-js が二重になってしまうなど)
従って、決して良いやり方ではないと思われるが、以下の方法の方が簡単。

既存のアプリをコピーする

既に作成済みのアプリカスタマイズの構成ファイルをコピーして整えていくパターン。
以下、作成済みのアプリカスタマイズを app、 新しいアプリカスタマイズを second-app として説明する。

フォルダ・ファイル構成

プロジェクトルート直下に新しいフォルダ second-app を作成する。
その下に、source フォルダ、 tests フォルダを作成する。
second-app/source/ 以下に必要に応じて下記フォルダを作る。

  • assets
  • components
  • css
  • js
  • store

app/ の以下のファイルを second-app/ フォルダにコピーする。

  • auth.json
  • config.json
  • webpack.common.js
  • webpack.config.js
  • webpack.dev.js
  • webpack.prod.js

app/source/ の以下のファイルを second-app/source/ フォルダにコピーする。

  • global.d.ts
  • shims-vue.d.ts
  • shims-tsx.d.ts

認証情報

新しいアプリカスタマイズの認証情報が異なる場合は second-app/auth.json を編集する。

アプリカスタマイズ情報

second-app/config.json を開き、以下の部分を編集する。

second-app/config.json
{
  "appID": **, // カスタマイズを適用するアプリID
  "appName": "second-app",
  "type": "Customization",
  "scope": "ALL",
  "uploadConfig": {
    "desktop": {
      "js": ["second-app/dist/second-app.min.js"],
      "css": []
    },
    "mobile": {
      "js": ["second-app/dist/second-app.min.js"]
    }
  }
}

基本的には編集前に app となっていたものを second-app に置き換えれば良い。

package.json の修正

package.jsonscripts にコマンドを追加する。

作成済みの serve-app, devel-app,build-app,deploy-app をコピーし、app の部分を second-app に変えてやる。
合わせると以下の通りになる。

package.json
  "scripts": {
    "dev": "ws --port 8800",
    "serve-app": "kintone-cli dev --app-name app --watch --localhost",
    "devel-app": "webpack --mode development --config app/webpack.config.js",
    "build-app": "webpack --mode production --config app/webpack.config.js",
    "deploy-app": "kintone-cli deploy --app-name app",
    "serve-second-app": "kintone-cli dev --app-name second-app --watch --localhost",
    "devel-second-app": "webpack --mode development --config second-app/webpack.config.js",
    "build-second-app": "webpack --mode production --config second-app/webpack.config.js",
    "deploy-second-app": "kintone-cli deploy --app-name second-app",
    "test:unit": "jest"
  },

あとは元のアプリカスタマイズを参考に main.tsApp.vuecomponents/**.vue 等を作り込んでいく。

それなりに作業が多くオペミスが起こりやすいので注意が必要。

Vuetify を利用する場合

Vue CLI でインストールし、その成果を app 以下に移してやると言う流れ。
※この流れでは IE11 で正しく動作しない。IE11 が要件に入らない場合のみ採用できる。

vue add vuetify

インストールが始まる。
道中でプリセットの選択を求められる。
デフォルトで良い。

? Choose a preset: Deafult (recommended)

public/index.html が存在していないと最後にエラーが出るが、インストール自体は正常に行われているので問題ない。

プロジェクトルート直下の src 以下にファイルが作成される。
src/App.vueapp/source/App.vue に上書き。
src/components/HelloWorld.vueapp/source/components/HelloWorld.vue に上書き。
src/assets/Logo.svgapp/source/assets/ に移動。
src/plugins フォルダを app/source/ に移動。
src フォルダは不要なので削除。

app/plugins/vuetify.ts を以下のように修正。

app/plugins/vuetify.ts
import "@mdi/font/css/materialdesignicons.css";
import Vue from "vue";
import Vuetify from "vuetify/lib";

Vue.use(Vuetify);

export default new Vuetify({
  icons: {
    iconfont: "mdi",
  },
});

app/source/main.ts を以下のように修正する。

app/source/main.ts
import Vue from "vue";
import App from "./App.vue";
import store from "./store";
import vuetify from "./plugins/vuetify";

Vue.config.productionTip = false;

kintone.events.on("app.record.index.show", (event) => {
  console.log("Hello from kintone CLI");

  const elem: Element = kintone.app.getHeaderSpaceElement() as Element;
  new Vue({
    store,
    vuetify,
    render: (h) => h(App),
  }).$mount(elem);

  return event;
});

app/webpack.coomon.js に以下の記述を追加。

app/source/webpack.common.js
const VuetifyLoaderPlugin = require("vuetify-loader/lib/plugin");

module.exports = {
  // 省略
  module: {
    rules: [
      // 省略
      // CSS に対するルールの次に記述する
      {
        test: /\.s(c|a)ss$/,
        use: [
          'vue-style-loader',
          'css-loader',
          {
            loader: 'sass-loader',
            options: {
              implementation: require('sass'),
              sassOptions: {
                fiber: require('fibers'),
                indentedSyntax: true // optional
              }
            }
          }
        ]
      },
      // 省略
      // いちばん下に追加する
      {
        test: /\.(woff|woff2|eot|ttf)(\?.+)?$/,
        use: {
          loader: 'url-loader',
          options: {
            esModule: false,
            limit: 1000000
          }
        }
      }
    ]
  }
  plugins: [
    new VueLoaderPlugin(), // 既に記述済み
    new VuetifyLoaderPlugin(),
  ],
};

追加のライブラリをインストールする。

yarn add -D vue-style-loader fibers deepmerge @mdi/font

これで Vuetify が表示できるようになる。

雑感

Vue CLIkintone CLI を良い感じに共存させようと言う考えから始めたが、あとでライブラリを追加したり多くの設定ファイルに手を入れたりファイルを移動させたりと、結局のところ両者の良さを良い感じに活かすと言うよりはどうにかこうにか妥協点を見出したと言う形になったように思う。
とは言え、Vue.js の環境をゼロから(Vue CLI を使わずに)作成するのは今となってはもう面倒この上ないし、kintone CLI のコマンド一発でデプロイできる便利さは開発工数の削減に効果があるのは間違いなく、無理っくり感が否めない構成とは言えテンプレート化できたのは十分なメリットをもたらすのではなかろうか。

※今回の成果は近日中に GitHub にアップする予定です。

参考

VS Code に Prettier・ESLint・Stylelint を導入してファイル保存時にコードを自動整形させる方法
続・VSCode 上で vue ファイルに対して ESLint と Prettier が快適に動作する設定
VS Code で ESLint × Prettier のベストかも知れないプラクティス
kintone CLI をざっくり試す

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

Vue CLI と kintone CLI の共存を探る(JavaScript 編)

Vue CLI でセットアップしたプロジェクトで kintone CLI を利用したカスタマイズ環境を作る目論見です。
どうにかこうにか形になりました。
ざっくり書いていますので読みづらいかも知れません。

こちらは JavaScript 編です。
TypeScript 編は以下をご覧ください。

Vue CLI と kintone CLI の共存を探る(TypeScript 編)

目的

Vue CLI で作られるプロジェクトの整然さと kintone CLI で行えるカスタマイズの開発やデプロイの簡易性を両立させる。
@kintone/rest-api-client を利用する。
kintone UI Component は諦める。(React でしか動かないので)
場合により Vuetify の利用を検討する。

フォーマッティング・lint に関しては以下の方針。
Vetur はフォーマットを実行させず、シンタックスハイライトなどの役割に専念。
eslint は静的解析のみ担当。コードフォーマットはしない。
コードフォーマットは Prettier が実行。

前提

Vue CLI および kintone CLI がグローバルインストールされている事。

npm install -g @vue/cli
npm install -g git://github.com/kintone/kintone-cli.git

プロジェクト作成手順

Vue.js プロジェクトをセットアップする

Vue CLI でプロジェクトを開始する。

vue create vue-cli-kintone-cli-js

以下の選択肢で進める。

? Check the features needed for your project:

  • Babel
  • Vuex
  • CSS Pre-processors
  • Linter / Formatter
  • Unit Testing

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
? Pick a linter / formatter config: ESLint + Prettier
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

kintone CLI でアプリを作成する

作成されたプロジェクトを VS Code で開き、ターミナルを起動する。
kintone CLI でアプリをセットアップする。

kintone-cli create-template

? What type of app you want to create ? Customization
? Do you want to set authentication credentials ? Yes
? What is your kintone domain ? (サブドメイン).cybozu.com
? What is your kintone username ? (ユーザー名)
? What is your kintone password ? (パスワード)
? Do you use proxy ? No
? Do you want to use React ? No
? Do you want to use TypeScript ? No
? Do you want to use webpack ? Yes
? What is the entry for webpack ? main.js
? What is the app name ? app
? Do you want to use @cybozu/eslint-config for syntax checking ? No
? What is the app ID ? (アプリ ID)
? What is the scope of customization ? ALL

app ディレクトリの下にソースが作成される。

開発サーバーをインストールする

この手順だとローカルプレビュー用の local-web-server がインストールされないため、手動で追加する。
現時点(2020 年 6 月)での最新版は 4.2 だが、このバージョンでは正しく動作しない模様。
kintone-cli でインストールされるものと同じバージョンの 2.6.1 をインストールする。

yarn add -D local-web-server@2.6.1

その他の関連ツールをインストールする

core-js が二重に入っている。
いったんアンインストールして最新版を追加。

yarn remove core-js
yarn add core-js

@kintone/rest-api-client をインストールする。

yarn add @kintone/rest-api-client

ESLint・Prettier の設定

以下を参考にする。
続・VSCode 上で vue ファイルに対して ESLint と Prettier が快適に動作する設定
VS Code で ESLint × Prettier のベストかも知れないプラクティス

yarn add -D eslint-config-prettier

プロジェクトルート直下の .eslintrc.js を修正

module.exports = {
  extends: [
    // --- 省略
    "prettier",
  ],
  // --- 省略
  globals: {
    kintone: true,
    event: true,
  },
};

プロジェクトルート直下に .eslintignore ファイルを作成。
dist フォルダを追跡から除外する。

.eslintignore
**/dist

標準のフォーマッティングを無効化し、Prettier が全て担当するようにする。
.vscode/settings.json に設定を加える。

.vscode/settings.json
{
  "javascript.format.enable": false,
  "typescript.format.enable": false,
  "vetur.format.enable": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

.prettierrc を作る。
設定はお好みで。

.prettierrc
{
  "semi": false,
  "singleQuote": true
}

ディレクトリ構成

プロジェクトルート直下の src 以下のファイルを app/source に移動。
app/tests/unit フォルダを作成。
残った srcpublictests は不要なので削除。

app/source/main.js をとりあえず以下のようにする

app/source/main.js
import Vue from "vue";
import App from "./App.vue";
import store from "./store";

Vue.config.productionTip = false;

kintone.events.on("app.record.index.show", (event) => {
  console.log("Hello from kintone CLI");

  const elem = kintone.app.getHeaderSpaceElement();
  new Vue({
    store,
    render: (h) => h(App),
  }).$mount(elem);

  return event;
});

babel の設定

プロジェクトルート直下に babel.config.js が、 app の下に .babelrc が存在する。
プロジェクトルート直下の babel.config.jsapp/.babelrc の内容をマージする。
以下のようになる。

babel.config.js
{
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-syntax-dynamic-import"
  ],
  "presets": [
    "@vue/cli-plugin-babel/preset",
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": {
          "version": 3,
          "proposals": true
        }
      }
    ]
  ]
}

その後 app/.babelrc は削除ないしリネーム。

webpack の設定

.vue ファイルをビルドするために vue-loader, vue-template-compiler, url-loader, file-loader をインストールする。
また webpack の設定は開発ビルドとプロダクションビルドで設定を変えたいので、webpack-merge もインストールする。
加えて、プロダクションビルドではミニファイも行うので、 terser-webpack-plugin もインストールする。

yarn add -D vue-loader vue-template-compiler url-loader file-loader webpack-merge terser-webpack-plugin

webpack の設定は app 以下のものを使用する。
app/webpack.config.jsapp/webpack.common.js にリネームし、内容を以下のようにする。

app/webpack.common.js
const path = require("path");
const VueLoaderPlugin = require("vue-loader/lib/plugin");

module.exports = {
  entry: path.resolve(__dirname, "./source/main.js"),
  resolve: {
    extensions: [".ts", ".tsx", ".vue", ".js"],
  },
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: `${path.basename(__dirname)}.min.js`,
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: {
          loader: "vue-loader",
          options: {
            loaders: {
              js: {
                loader: "babel-loader",
              },
            },
          },
        },
      },
      {
        test: /.js?$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
      {
        test: /.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(jpe?g|png|gif|svg|ico)(\?.+)?$/,
        use: {
          loader: "url-loader",
          options: {
            esModule: false,
            limit: 10000,
            fallback: {
              loader: "file-loader",
              options: {
                name: "img/[name].[ext]",
              },
            },
          },
        },
      },
    ],
  },
  performance: {
    maxEntrypointSize: 10000000,
    maxAssetSize: 10000000,
  },
  plugins: [new VueLoaderPlugin()],
};

開発ビルドの設定 app/webpack.dev.js は以下の通りにする。

app/webpack.dev.js
const merge = require("webpack-merge");
const common = require("./webpack.common.js");

module.exports = merge(common, {
  mode: "development",
});

プロダクションビルドの設定 app/webpack.prod.js は以下の通りにする。

app/webpack.prod.js
const merge = require("webpack-merge");
const common = require("./webpack.common.js");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = merge(common, {
  mode: "production",
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: { compress: { drop_console: true } },
      }),
    ],
  },
});

新たに app/webpack.config.js を作り、以下のようにする。
このファイルでビルドターゲットを判定して、適切なファイルをロードするようにする。

app/webpack.config.js
module.exports = (env, argv) => {
  const mode = argv.mode.includes("development") ? "dev" : "prod";
  return require(`./webpack.${mode}.js`);
};

Jest の設定

特にない。
必要に応じてプロジェクトルート直下の jest.config.js を編集すればよい。

package.json の設定

コマンドの入力を簡略化するため scripts を以下のようにする。
webpack の設定ファイルもここでコントロールする。

package.json
  "scripts": {
    "dev": "ws --port ****",
    "serve-app": "kintone-cli dev --app-name app --watch --localhost",
    "devel-app": "webpack --mode development --config app/webpack.config.js",
    "build-app": "webpack --mode production --config app/webpack.config.js",
    "deploy-app": "kintone-cli deploy --app-name app",
    "test:unit": "jest"
  },

ws --port **** は任意のポート番号。
以下の記事で解説している。
kintone CLI をざっくり試す

.gitignore の設定

これまでの流れで app/auth.json は含まれているはず。
app/dist も弾くようにする。
その上で、今後仮に app が増えても大丈夫なようにする。

以下の通り修正。

.gitignre
*/auth.json
*/dist

.vscode/settings.json は共有したいので、.vscode の行は削除する。

ここまでで開発準備は終了。
あとはひたすらに開発に注力する。

開発フェーズ

開発・テスト

実際の開発は app/source/ 以下のファイルに対して行っていく事になる。

開発モード(ローカルサーバ)で起動

yarn serve-app

ローカルサーバへの参照が kintone アプリに適用され、テストが行えるようになる。

単体テストは以下のように実行。

yarn test:unit

テストファイルは app/tests/unit/ 以下に **.spec.js のファイル名パターンで記述する。

ビルド

yarn build-app

app フォルダの下に dist フォルダが作成されファイルがビルドされる。
ビルドしたファイルを手動で運用環境に適用したい場合に使う。
webpack.prod.js では TerserPlugin によりミニファイと console の除去が行われる。

デプロイ

yarn deploy-app

ビルドとデプロイを同時に実行するコマンド。
ビルドが実行され、終了後に運用環境にデプロイされる。

2 つめ以降のアプリのカスタマイズを作る場合

プロジェクトが複数のアプリカスタマイズで構成される場合、いくつかアプローチがある。

kintone-cli create-template を使う

kintone CLI 的には王道パターンではある。

kintone-cli create-template --app-name (新しいアプリ名)

と実行し、カスタマイズを新規で作成する。

作成されたフォルダに、既存のアプリカスタマイズのファイルをかぶせてやる。(要微修正)

この方法だと改めて依存モジュール類を再取得しに行く動作をするため、時間が掛かる上にせっかく整えた package.json がまた汚れてしまう。
core-js が二重になってしまうなど)
従って、決して良いやり方ではないと思われるが、以下の方法の方が簡単。

既存のアプリをコピーする

既に作成済みのアプリカスタマイズの構成ファイルをコピーして整えていくパターン。
以下、作成済みのアプリカスタマイズを app、 新しいアプリカスタマイズを second-app として説明する。

フォルダ・ファイル構成

プロジェクトルート直下に新しいフォルダ second-app を作成する。
その下に、source フォルダ、 tests フォルダを作成する。
second-app/source/ 以下に必要に応じて下記フォルダを作る。

  • assets
  • components
  • css
  • js
  • store

app/ の以下のファイルを second-app/ フォルダにコピーする。

  • auth.json
  • config.json
  • webpack.common.js
  • webpack.config.js
  • webpack.dev.js
  • webpack.prod.js

認証情報

新しいアプリカスタマイズの認証情報が異なる場合は second-app/auth.json を編集する。

アプリカスタマイズ情報

second-app/config.json を開き、以下の部分を編集する。

second-app/config.json
{
  "appID": **, // カスタマイズを適用するアプリID
  "appName": "second-app",
  "type": "Customization",
  "scope": "ALL",
  "uploadConfig": {
    "desktop": {
      "js": ["second-app/dist/second-app.min.js"],
      "css": []
    },
    "mobile": {
      "js": ["second-app/dist/second-app.min.js"]
    }
  }
}

基本的には編集前に app となっていたものを second-app に置き換えれば良い。

package.json の修正

package.jsonscripts にコマンドを追加する。

作成済みの serve-app, devel-app,build-app,deploy-app をコピーし、app の部分を second-app に変えてやる。
合わせると以下の通りになる。

package.json
  "scripts": {
    "dev": "ws --port 8800",
    "serve-app": "kintone-cli dev --app-name app --watch --localhost",
    "devel-app": "webpack --mode development --config app/webpack.config.js",
    "build-app": "webpack --mode production --config app/webpack.config.js",
    "deploy-app": "kintone-cli deploy --app-name app",
    "serve-second-app": "kintone-cli dev --app-name second-app --watch --localhost",
    "devel-second-app": "webpack --mode development --config second-app/webpack.config.js",
    "build-second-app": "webpack --mode production --config second-app/webpack.config.js",
    "deploy-second-app": "kintone-cli deploy --app-name second-app",
    "test:unit": "jest"
  },

あとは元のアプリカスタマイズを参考に main.jsApp.vuecomponents/**.vue 等を作り込んでいく。

それなりに作業が多くオペミスが起こりやすいので注意が必要。

Vuetify を利用する場合

Vue CLI でインストールし、その成果を app 以下に移してやると言う流れ。
※この流れでは IE11 で正しく動作しない。IE11 が要件に入らない場合のみ採用できる。

vue add vuetify

インストールが始まる。
道中でプリセットの選択を求められる。
デフォルトで良い。

? Choose a preset: Deafult (recommended)

public/index.html が存在していないと最後にエラーが出るが、インストール自体は正常に行われているので問題ない。

プロジェクトルート直下の src 以下にファイルが作成される。
src/App.vueapp/source/App.vue に上書き。
src/components/HelloWorld.vueapp/source/components/HelloWorld.vue に上書き。
src/assets/Logo.svgapp/source/assets/ に移動。
src/plugins フォルダを app/source/ に移動。
src フォルダは不要なので削除。

app/plugins/vuetify.js を以下のように修正。

app/plugins/vuetify.js
import "@mdi/font/css/materialdesignicons.css";
import Vue from "vue";
import Vuetify from "vuetify/lib";

Vue.use(Vuetify);

export default new Vuetify({
  icons: {
    iconfont: "mdi",
  },
});

app/source/main.js を以下のように修正する。

app/source/main.js
import Vue from "vue";
import App from "./App.vue";
import store from "./store";
import vuetify from "./plugins/vuetify";

Vue.config.productionTip = false;

kintone.events.on("app.record.index.show", (event) => {
  console.log("Hello from kintone CLI");

  const elem = kintone.app.getHeaderSpaceElement();
  new Vue({
    store,
    vuetify,
    render: (h) => h(App),
  }).$mount(elem);

  return event;
});

app/webpack.coomon.js に以下の記述を追加。

app/source/webpack.common.js
const VuetifyLoaderPlugin = require("vuetify-loader/lib/plugin");

module.exports = {
  // 省略
  module: {
    rules: [
      // 省略
      // CSS に対するルールの次に記述する
      {
        test: /\.s(c|a)ss$/,
        use: [
          'vue-style-loader',
          'css-loader',
          {
            loader: 'sass-loader',
            options: {
              implementation: require('sass'),
              sassOptions: {
                fiber: require('fibers'),
                indentedSyntax: true // optional
              }
            }
          }
        ]
      },
      // 省略
      // いちばん下に追加する
      {
        test: /\.(woff|woff2|eot|ttf)(\?.+)?$/,
        use: {
          loader: 'url-loader',
          options: {
            esModule: false,
            limit: 1000000
          }
        }
      }
    ]
  }
  plugins: [
    new VueLoaderPlugin(), // 既に記述済み
    new VuetifyLoaderPlugin(),
  ],
};

追加のライブラリをインストールする。

yarn add -D vue-style-loader fibers deepmerge @mdi/font

これで Vuetify が表示できるようになる。

雑感

Vue CLIkintone CLI を良い感じに共存させようと言う考えから始めたが、あとでライブラリを追加したり多くの設定ファイルに手を入れたりファイルを移動させたりと、結局のところ両者の良さを良い感じに活かすと言うよりはどうにかこうにか妥協点を見出したと言う形になったように思う。
とは言え、Vue.js の環境をゼロから(Vue CLI を使わずに)作成するのは今となってはもう面倒この上ないし、kintone CLI のコマンド一発でデプロイできる便利さは開発工数の削減に効果があるのは間違いなく、無理っくり感が否めない構成とは言えテンプレート化できたのは十分なメリットをもたらすのではなかろうか。

※今回の成果は近日中に GitHub にアップする予定です。

参考

VS Code に Prettier・ESLint・Stylelint を導入してファイル保存時にコードを自動整形させる方法
続・VSCode 上で vue ファイルに対して ESLint と Prettier が快適に動作する設定
VS Code で ESLint × Prettier のベストかも知れないプラクティス
kintone CLI をざっくり試す

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

Vuejs 子コンポーネントのplaceholder属性の初期値を親コンポーネント側で入力した値にするには

スクリーンショット 2020-05-23 11.30.00.png

上の画像のようなTodolistを作成中です。編集ボタンを押すと内容が編集できるといった仕組みになっています。

スクリーンショット 2020-05-23 11.45.11.png

上の画像が編集画面です。初期値ですでに入力されていた内容と設定されていた日付が表示されてます。

親コンポーネント(TodoList.vue)
<div>
  <b-container fluid class="bv-example-row">
    <b-row>
      <b-card
        border-variant="secondary"
        footer-border-variant="secondary"
        align="center"
        v-for="todo in todos" 
        :key="todo.id" 
        :content="todo.content"
        :timelimit="todo.timelimit"
      >
      <template v-slot:header>
        <b-button variant="outlineinfo" @click="testOpenModal(todo)">
          編集
        </b-button>
        <b-button variant="outline-danger">
          削除
        </b-button>
        <b-button variant="outline-success">
          完了
        </b-button>
      </template>
      <b-card-text>{{ todo.content }}</b-card-text>
        <template v-slot:footer>
          期限:{{ todo.timelimit }}
        </template>
      </b-card>
    </b-row>
    <transition name="modal">
      <show-modal 
        :id="todoId"
        :timelimit="todoTimelimit"
        :content="todoContent"
        v-if="showContent" 
        @close="modalClose"
      />
    </transition>
  </b-container>
</div>

<script>
import ShowModal from './ShowModal'
export default {
  name: 'todo-list',
  components: {
    ShowModal,
  },
  data () {
    return {
      showModal: false,
      editUserContent: '',
      showContent: false,
      todoItem: ''
    }
  },
    computed: {
    todos () {
      return this.$store.state.todos
      }
    },
      methods: {
        testOpenModal (todo) {
           this.showContent = true
           this.todoId = todo.id
           this.todoTimelimit = todo.timelimit
           this.todoContent = todo.content
       },
      modalClose () {
         this.showContent = false
       }
   }
}
</script>

上記のような配列で回して表示しているTodoListです。show-modalが子コンポーネントでモーダルウィンドウを表示させるコンポーネントです。

<b-button variant="outlineinfo" @click="testOpenModal(todo)">
  編集
</b-button>

上のコードでモーダル画面を発火させる文です。引数にtodoを渡しています。
todoの中には内容と日付などが入っています。

methods: {
  testOpenModal (todo) {
    // モーダルをウィンドウを表示させる
    this.showContent = true
    // todoIdにtodoのなかのidを代入
    this.todoId = todo.id
    this.todoTimelimit = todo.timelimit
    this.todoContent = todo.content
  },

上のコードでモーダルを表示させthis.xxxx = todo.xxでtodoのなかの値を代入しています。

<show-modal 
  :id="todoId"
  :timelimit="todoTimelimit"
  :content="todoContent"
  v-if="showContent" 
  @close="modalClose"
/>

v-bindで先ほど代入してあげた値をshow-modal側に渡しています。

<template>
<div>
    <div class="modal-mask">
        <div class="modal-wrapper" @click.self="$emit('close')">
            <div class="modal-container">

                <div class="modal-header">
                    {{ id }}:編集
                    {{ timelimit }}
                </div>
// v-model="content"でも初期値が入るし:value="content"でも入る
                <div class="modal-form">
                    <b-input-group class="mb-2">
                    <b-form-input 
                        type="text" 
                        placeholder="やりたいことを入力してください"
                        :value="content"
                        @input="onInput"
                    >
                    </b-form-input>
                    <b-form-datepicker 
                        class="mb-2" 
                        placeholder="何日までに行いますか?"
                        :value="timelimit"
                    >
                    </b-form-datepicker>
                </b-input-group>
                </div>

                <div class="modal-footer">
                    <button @click="editTodo">OK</button>
                    <button @click="$emit('close')">Close</button>
                </div>
            </div>
        </div>
    </div>
</div>

</template>
<script>
export default {
    name: 'show-modal',
    props: ['id', 'timelimit', 'content'],
  deta () {
        return {
            closeModal: false,
            timelimit: '',
            editContent: '',
        }
    },
    methods: {
        editTodo() {
            this.$store.commit ('editTodo',{
                editContent: this.editContent
            })
        },
    onInput(e) {
            this.content = e.target.value
            this.$emit('content', this.content)
        }
    },
    computed: {
        todos () {
            return this.$store.state.todos
        }
    }
}
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vuexでcomponentなform、親子相互bind

git
https://github.com/kujiy/vuex-study/tree/a2049726e7e6a2c62c1a1ba1d0cef7940473c6b9

やりたいこと

親子相互にbindしたい。

どのinput boxに入れても全部リアクティブに動く状態。

image.png

vuexのcomputedでやる場合はget/setが必要

vuex の modulesを使っています

main.js

import Vue from 'vue'
import Vuex from 'vuex'
import App from './App.vue'
Vue.use(Vuex)

// import store from './store.js'
import store from './store_module.js'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  store,
}).$mount('#app')

store.js
他の勉強もしてたのでゴミだらけ

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
      moduleA: {
          namespaced: true,
          state: {
              count: 0,
              mymsgDict: "mAAAAAAAAAAAAAAAAAAA ymsgDict init",
              mymsg: " AAAAAAAAAAAAAAAAAAAAAAA mymsg init"
          },
          mutations: {
              increment(state) {
                  state.count++
              },
              setMsgDict(state, payload) {
                  state.mymsgDict = payload.message;
              },
              setMsg(state, payload) {
                  state.mymsg = payload;
              },
              updateMessage(state, message) {
                state.message = message;
              }
          }
      },
      moduleB: {
          namespaced: true,
          state: {
              count: 0,
          },
          mutations: {
              increment(state) {
                  state.count++
              },
          }
      }
  }
})
export default store

app.vue
copmonentでhelloworldを呼んでる

<template>
  <div id="app">
      Page app:
    <form>
        <input  v-model="compTitleVuex" >
        <input  v-model="compTitleVuex" >
        <HelloWorld/>
    </form>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  computed: {
    compTitleVuex: {
      get() {
        return this.$store.state.moduleA.mymsg
      },
      set(value) {
        console.log(value);
        this.$store.commit('moduleA/setMsg', value)
      }
    }
  }
}
</script>

呼び出されるcomponent側
HelloWorld.vue ひどい名前・・

<template>
    <div class="hello">
        <hr>
        Inside component
        <input v-model="compTitleVuex">
    </div>
</template>

<script>

    export default {
        name: 'insideComponent',
        computed: {
            compTitleVuex: {
                get() {
                    return this.$store.state.moduleA.mymsg
                },
                set(value) {
                    console.log(value);
                    this.$store.commit('moduleA/setMsg', value)
                }
            }
        }
    }
</script>


以下 mapStateは動かなかった

書き方違うか、対応してないか。

export default {
  name: 'HelloWorld',
  computed: {
    // mix this into the outer object with the object spread operator
    ...mapState({
      message: state => state.moduleA.message;
  })
}}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js】Nuxt文法編:@click

前置き

「すごく勉強になる!?」
とコメントいただけた
人気の文法編です?
今回は@click

❓どんな時に使うか
クリックでJSを実行したい時に使います?
使用率は高いのでぜひマスターしてください?

TODOリストをクリックで削除したり
https://note.com/aliz/n/nda7438249ca8
クリックでページ遷移したり
https://note.com/aliz/n/nd9f344e4686f
modalをクリックで開閉したり…!
https://note.com/aliz/n/nd6e771724cd7

@click

@click
v-on:click

どちらでも同じ意味です。
@はv-on:の省略です?

v-on:hoge
hogeイベントが起きた時

v-on:
DOMイベントの発火時に
JavaScriptを実行?

v-on:click
クリックイベントが起きた時に
JavaScriptを実行?

実行式を直接記載

@click="実行式"
実行式を直接書きます✍️
ただ通常は次のmethodsを呼び出すのが一般的です!
それが何故かを理解するために、把握する程度でOK!

ここでdataのcounterを使う場合は
thisが不要です!
methods内で呼び出す場合は
必要になります?

index.vue
<template>
 <div class="page">
   <p>{{ counter }}</p>
   <button @click="counter += 1">
     click!
   </button>
 </div>
</template>

<script>
export default {
 data () {
   return {
     counter: 0,
   }
 },
}
</script>

?+=
 演算子です。
 変数の値(counter: 0)と
 式の値(1)を加算して
 その和を変数(counter)に代入します♠️
 counterの0に1を足して
 counter自体が1になります。
 そしてまた1を足すとcounterが2, 次は3…
 と続いていきます?

結果
@click.gif

メソッドイベントハンドラ

@click="methods名"
実行式が長くなることが多いため
methods名を呼び出せます?‍♀️

?パターン1?

index.vue
<template>
 <div class="page">
   <p>{{ counter }}</p>
   <button @click="countUp">
     click!
   </button>
 </div>
</template>

<script>
export default {
 data () {
   return {
     counter: 0,
   }
 },
 methods: {
   countUp () {
     this.counter += 1
   },
 },
}
</script>

結果
先ほどと同じです?‍♀️

?パターン2?

index.vue
<template>
 <div class="page">
   <button @click="alertMessage">
     alertMessage
   </button>
   <button @click="alertEvent">
     alertEvent
   </button>
 </div>
</template>

<script>
export default {
 data () {
   return {
     name: 'Nuxt.js'
   }
 },
 methods: {
   alertMessage () {
     alert (`Hello ${this.name}!`)
   },
   alertEvent (event) {
     alert (event.target.tagName)
   },
 },
}
</script>

?テキスト${変数名}
 methodsのalertMessageで使用。
 テンプレートリテラルです。
 通常は文字列を''で囲み
 +で変数を連結しますが…
 いちいち''で囲むのは面倒?
 'テキスト' + 変数名 + 'テキスト'

 テンプレートリテラルを使えば
 いちいち文字を''で区切らず
 ``(バッククォート)で全体を囲み
 変数の部分だけ${変数名}にすれば
 簡単に連結できます?

?event.target.tagName
 methodsのalertEventで使用。
 event.target
 クリックイベントが起きている物
 [object HTMLButtonElement]
 event.target.tagName
 buttonタグの名前
 buttonが表示されます?
 大文字で返されます。
 https://developer.mozilla.org/ja/docs/Web/API/Element/tagName

結果
@click2.gif

インラインメソッドハンドラ

@click="methods名(引数に代入する物)"
インラインでJavaScript実行式を書けます!
文字列'hi'を表示させてます?

index.vue
<template>
 <div class="page">
   <button @click="say('hi')">
     Say hi
   </button>
 </div>
</template>

<script>
export default {
 methods: {
   say (message) {
     alert (message)
   },
 },
}
</script>

結果
@click3.gif

?say(変数)
 文字列ではなく変数でも⭕️
 say(name)にした場合は
 dataのnameが呼び出され
 表示は文字列'myName!'になります?
 data () {
  return {
   name: 'myName!'
  }
 },

?ちなみにsayが変数、'hi'が変数名です!

インラインステートメントハンドラ

@click="methods名($event)"
$event変数でDOMイベントの参照ができます!

❓DOMイベントとは
 DOM:文字の色を変えたり、
    プログラムからhtmlなどを操作できる仕組みのこと
 DOMイベント:操作するためのイベント

【イベントの種類】
 いくつか記載しましたが
 他にもまだまだ沢山あります!
 気になる方は調べてみてください?

イベントハンドラ
JavaScriptイベントハンドラ/
--| mouseイベント(DOMイベント)/
-----| @click(type)
-----| @mousedown(type)
-----| @mouseup(type)
--| keyboardベント/
-----| @click
-----| @mousedown
-----| @mouseup
--| inputイベント/
-----| @input
-----| @change

【解説/index.vue】
・textChange (event)

 イベントを受け取る際は$が不要です?
・event.target.value
 今回のMouseEventが起きている
 target(button)のvalue(りんご、みかん)

index.vue
<template>
 <div class="page">
   <p>{{ text }} が好き</p>
   <button
     value="りんご"
     @click="textChange($event)"
   >
     りんご
   </button>
   <button
     value="みかん"
     @click="textChange($event)"
   >
     みかん
   </button>
 </div>
</template>

<script>
export default {
 data () {
   return {
     text: 'りんご or みかん',
   }
 },
 methods: {
   textChange (event) {
     console.log(event)
     this.text = event.target.value
   },
 },
}
</script>

?$eventは省略可能
 @click="methods名"だけでもOKです!

?@click="textChange($event.target.value)"
 もちろんここで直接値を渡してもOKです!
 consoleの表示が変わるので見てみてください?
 methodsの変更もお忘れなく〜?
 textChange (event) {
  console.log(event)
  this.text = event
 },

結果
@click4.gif

?console.log(event)
 今回はMouseEventが表示されています?
 中身を見るとtype: "click"が出てきますよ?

イベント修飾子

@click.修飾子="methods名"
様々な修飾子(event modifiers)が使えます!

・stop
 ネストされた子要素のmethodsが
 親要素に伝搬するのを防ぎます

【index.vue】
親要素、子要素共に同じmethodsを使用してます。
stopなし:親と子の2回のalertが表示される
stopあり:子の1回のみalertが表示される

index.vue
<template>
 <div @click="handler">
   <button @click.stop="handler">
     イベント実行ボタン
   </button>
 </div>
</template>

<script>
export default {
 methods: {
   handler (element) {
     alert(element)
   },
 },
}
</script>

他にも色々あります!
・prevent
 Event.preventDefault()と同じです。
 @submit.preventでよく使います。   https://note.com/aliz/n/n5b9bd618399e

・capture
・self
・once
・passive

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

Vuejsでリアルタイム検索

初めに

割と検索に苦手意識を持ってきたので実装を避けてきたのですが思ったより案外簡単にできたのでおいておきます。
環境

  • Vuejs
  • Bootstrap4
  • Axios

HTML

<input type="text" placeholder="検索..." v-model="keyword">
<p v-for="user in filterUsers">
 {{ user.name }}
</p>

Vuejs


export default {
 data() {
  return {
   keyword: '',
  }
 },
 created() {
  this.getItems()
 },
 computed: {
  filterData() {
   var users = [];
   for (var i in this.users) {
    var user = this.users[i]
    // 配列とkeywordを比較して一致したら...
    if(user.name.indexOf(this.keyword) !== -1) {
     //配列にpushする。
     users.push(user)
    }
   }
   return users;
  }
 },
 methods: {
  getItems: function () {
   axios.get('/api/users').then(res => {
     this.users = res.data
   })
  }
 }
}

終わり

これで完成となっています。
ね?簡単でしょ?
リアルタイム検索なので前方、後方、部分、完全一致関係なく検索してくれます。
便利な世の中になった。

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

Docker+Rails+Vueの環境を迷わず作成するステップ

最近勉強したDockerを使って、railsとVueの環境構築をしてみました。
いろいろ検索してやってみたものの、多くのエラーと向き合う日々を迎えることに...(それでもだいぶ理解は深まった?)

この記事では

  • とにかく開発環境だけ欲しい
  • 自分の忘備録

を主な対象として、最速でDokcer+Rails+Vueの環境を作成するステップをご紹介します?!
(多分エラーは出ないはず。。。)

STEP1. 4つのファイルを作成しよう

まずは作業するフォルダ(ディレクトリ)に

  • Dockerfile
  • docker-compose.yml
  • Gemfile
  • Gemfile.lock

の4つのファイルを作成します。内容はそれぞれ以下のようにします!

Dockerfile
FROM ruby:2.5.3

RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get update && \
    apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev

RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y yarn

RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - 
RUN apt-get install -y nodejs npm && npm install n -g && n 10.17.0

RUN yarn add node-sass

RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app

docker-compose.yml
version: '3'
services:
    web:
        build: .
        command: bundle exec rails s -p 3000 -b '0.0.0.0'
        volumes:
            - .:/app
        ports:
            - 3000:3000
        depends_on:
            - db
        tty: true
        stdin_open: true
    db:
        image: mysql:5.7
        volumes:
            - db-volume:/var/lib/mysql
        environment:
            MYSQL_ROOT_PASSWORD: password
volumes:
    db-volume:

Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.3'

作業しているディレクトリで
touch Gemfile.lock
を実行すると、からのGemfile.lockが作成されます。

?バージョンやdbのパスワードなどはよしなにご変更ください〜。

STEP2. rails newをする

今回はdockerコンテナ上でrailsアプリを作成するので、コマンドラインにそのまま
rails new
を打つのは正しくありません。
(僕のPCはローカルにrailsを入れていないため、rails newを打っても「そのコマンド知りませんけど?」って怒られます。)

dockerコンテナ上でコマンドを実行したい場合は
docker-compose run
コマンドをつかえばOKです?。

下記コマンドを実行します。
docker-compose run web rails new . --force --database=mysql --webpack=vue --skip-coffee

このコマンドにより、

  • dbはMySQLを明示的に指名
  • Vueを後入れしなくて済む(後入れでエラーが結構出た記憶)
  • coffee使わないので、coffee関連のファイルを作成しない

という付加価値をつけた状態で、rails new しています。

?あと2ステップ!

STEP3. database.ymlを作成する

STEP2の読み込みが完了したら、config/database.ymlが作成されているはず。
このファイル内にある「default」の内容を少し書き換えます。

config/databese.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password   ←docker-compose.ymlで指定したdbのパスワード
  host: db         ←docker-compose.ymlのservicesで指定したdbの名前

STEP4. docker-compose up --build

ここまでできたら、
docker-compose up --build
を実行します!

少し時間がかかりますが、読み込みが終わったあとにローカルホストにアクセスしてみると、「Yay! You’re on Rails!」の画面が表示されましたか?
もしそうなら成功です!

だがしかしBut。。。?
僕の場合は、「ERROR -- : Unknown database 'app_development' (ActiveRecord::NoDatabaseError)」が表示されました。

僕と同じという方は、
docker-compose exec web rails db:create
をしてあげればOK!

docker-compose down
をした後に、再度
docker-compose up --build
をすれば立ち上がるはずだぞ!お疲れ様でした?

最後に

コロナ禍でWebエンジニア転職を頑張っていますが、railsポートフォリオだけだとお祈りしかされません。
AWSの冗長構成でインフラ構築しててもお祈りです。

そこでRails+VueのRESTfulAPIを使ったアプリ(いわゆるSPA)を追加で作ったところ最終面接までは行けるようになりましたが、それでもやっぱり大変です。

もしWebエンジニア転職が大変だぁという方が読んでくれていたら、これだけは言いたいです。
諦めずに頑張ろうね!!!!

以上、お粗末様でした。

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

TypeScript+Vue.jsの環境でnullableなpropsを指定する

TypeScript+Vue.jsの環境でVue.jsのコンポーネントでpropsを使っているときに、nullableなtype指定をどうやってやるかを説明します。

結論

Typescriptの方の型指定だけnullableであることを明示します。

title.vue
<template>
<h1>{{ title }}</h1>
</template>

<script lang="ts">
import { createComponent, SetupContext } from '@vue/composition-api'

export default createComponent({
    props: {
        title: {
            type: String as () => string | null,
            default: null
        }
    }
})
</script>

そもそもなのですが、Vue.jsのpropsはすべてnullableなので、nullableにするときに[String, null]というようにnullをpropsのtypeに書く必要はありません。

よって、TypeScript側にだけnullableだということを教えればよいです。

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