データベースのテーブルのスキーマ情報をもとに、エンティティクラスのソースコードを自動生成するジェネレーターを作成する際に、ソースコードの行数に応じて高さを自動で調整するテキストエリア (textarea) 要素が必要になったので、その作成例を記載します。
今回は Vue CLI を使用したプロジェクトをローカルにインストールして作成します。
目次
環境設定
Vue CLI を使用したプロジェクトで作成するので、以下のコマンドを実行してプロジェクトを作成します。
Vue CLI をインストールするディレクトリに移動する
1 |
cd C:\vue-cli\projects |
上記の例ではCドライブに「vue-cli」フォルダを作成し、その中に「projects」フォルダを作成して、そこにVue CLIをインストールします。
Vue CLI をインストールする
Vue CLI をインストールする前に package.json を作成します。
1 |
npm init -y |
次に Vue CLI をインストールします。
1 |
npm install --save-dev @vue/cli |
ここまでのコマンドはローカルに Vue CLI をインストールするための操作になりますので、既に Vue CLI をグローバルインストールしている場合は不要になります。
プロジェクトを作成する。
Vue CLIをインストールしたディレクトリ (プロジェクトを作成するディレクトリ) に移動してプロジェクトを作成します。
1 |
npx vue create sample-project |
上記の例では「C:\vue-cli\projects」フォルダーに「sample-project」という名前でプロジェクトを作成しています。
プロジェクトの階層
1 2 3 4 |
C: vue-cli projects sample-project |
プロジェクトの設定
createコマンドを実行すると、プロジェクトの作成方法を「デフォルト (default)」で行うか「手動 (Manually)」で行うかの選択が表示されます。
「デフォルト」を選択すると、babel, eslintがインストールされます。
1 2 3 |
? Please pick a preset: (Use arrow keys) > default (babel, eslint) Manually select features |
今回は「手動」を選択します。
「手動」を選択すると、必要とする機能の選択肢が表示されます。
1 2 3 4 5 6 7 8 9 10 |
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection) >(*) Babel ( ) TypeScript ( ) Progressive Web App (PWA) Support ( ) Router ( ) Vuex ( ) CSS Pre-processors (*) Linter / Formatter ( ) Unit Testing ( ) E2E Testing |
TypeScriptを使用したいので選択して次へ進みます。
1 2 3 4 5 6 7 8 9 |
(*) Babel >(*) TypeScript ( ) Progressive Web App (PWA) Support ( ) Router ( ) Vuex ( ) CSS Pre-processors (*) Linter / Formatter ( ) Unit Testing ( ) E2E Testing |
機能の設定に関する質問がいくつかされますが、リンター (linter) の設定以外はそのまま (Enter キー押下で) 進みます。
リンターの設定では、TSLint (deprecated) を選択します。
1 2 3 4 5 6 |
? Pick a linter / formatter config: ESLint with error prevention only ESLint + Airbnb config ESLint + Standard config ESLint + Prettier > TSLint (deprecated) |
全ての設定が終わると、プロジェクトの設定内容を保存するかどうかが確認されます。
1 2 3 4 5 6 7 8 |
? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, TS, Linter ? Use class-style component syntax? Yes ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes ? Pick a linter / formatter config: TSLint ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? (y/N) |
今回は保存しないので No で進みます。
プロジェクトの作成が無事終わったら、プロジェクトのフォルダーに移動して「npm run serve」で開発サーバーを起動します。
1 2 3 |
cd sample-project npm run serve |
http://localhost:8080/ とブラウザのアドレスバーに入力して以下の画面が表示されれば、プロジェクトの作成に成功しています。
プロジェクトの枠組みができたので、テキストエリアコンポーネントを作成していきます。
Vue CLI で作成されるコンポーネントファイルは単一ファイルコンポーネントとして作成されますので、自作のテキストエリアコンポーネントも同様に単一ファイルコンポーネントとして作成します。
テキストエリアコンポーネントの作成
作成したプロジェクトのフォルダ、ファイル構成は以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
sample-project .git node_modules public favicon.ico index.html src assets logo.png components HelloWorld.vue App.vue main.ts shims-tsx.d.ts shims-vue.d.ts .browserslistrc .gitignore babel.config.js package.json postcss.config.js README.md tsconfig.json tslint.json yarn.lock |
上記のファイルについて、以下の3点を改変します。
- public フォルダーの index.html を変更する。
- src\components フォルダーの HelloWorld.vue を削除して CustomTextarea.vue を追加する。
- src フォルダーの App.vue を変更する。
改変後の public フォルダと src フォルダは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
public favicon.ico index.html src assets logo.png components CustomTextarea.vue App.vue main.ts shims-tsx.d.ts shims-vue.d.ts |
public\index.html
index.htmlでは必要な参照を追加します。
index.html 変更前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title>sample-project</title> </head> <body> <noscript> <strong>We're sorry but sample-project doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html> |
index.html 変更後
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <!-- Bulma --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.css"> <!-- Font Awesome --> <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script> <title>sample-project</title> </head> <body> <noscript> <strong>We're sorry but sample-project doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html> |
index.html では、CSS フレームワークの Bulma と、アイコンフォントの Font Awesome を使用したいので参照しています。
src\components\CustomTextarea.vue
高さを自動調整するテキストエリアコンポーネントを作成します。
テキストエリアのプロパティには以下の4つを作成します
- value: string
- placeholder: string
- disabled: boolean
- readonly: boolean
イベントには以下の3つを作成します。
- input
- focus
- blur
CustomTextarea.vue の作成
CustomTextarea.vue についてはデコレータを使用する場合と、使用しない場合の両方のソースコードを記載します。
デコレータを使用する場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<template> <textarea class="textarea" wrap="off" v-bind:rows="rows" v-bind:placeholder="placeholder" v-bind:disabled="disabled" v-bind:readonly="readonly" v-bind:value="value" v-on:input="inputValue($event)" v-on:focus="$emit('focus', $event)" v-on:blur="$emit('blur', $event)" /> </template> <script lang="ts"> import { Component, Prop, Emit, Vue } from 'vue-property-decorator'; @Component export default class CustomTextarea extends Vue { // props @Prop({ default: '' }) private value!: string; @Prop({ default: '' }) private placeholder?: string; @Prop({ default: false }) private disabled?: boolean; @Prop({ default: false }) private readonly?: boolean; // emit @Emit('input') inputValue(e: Event): string { return (e.target as any).value; } // computed get rows(): number { // テキストエリアの行数取得 if (this.value) { return this.value.split('\n').length; } return 1; } } </script> <style scoped> textarea { /* テキストエリアのリサイズを禁止する */ resize: none; font-family: "MS ゴシック"; font-size: 13px; } </style> |
デコレータを使用しない場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<template> <textarea class="textarea" wrap="off" v-bind:rows="rows" v-bind:placeholder="placeholder" v-bind:disabled="disabled" v-bind:readonly="readonly" v-bind:value="value" v-on:input="inputValue($event)" v-on:focus="$emit('focus', $event)" v-on:blur="$emit('blur', $event)" /> </template> <script lang="ts"> import Vue from 'vue' export default Vue.extend({ name: 'CustomTextarea', props: { value: { type: String, require: true }, placeholder: { type: String, default: '' }, disabled: { type: Boolean, default: false }, readonly: { type: Boolean, default: false } }, methods: { inputValue(e: Event): void { this.$emit('input', (e.target as any).value); } }, computed: { rows(): number { // テキストエリアの行数取得 if (this.value) { return String(this.value).split('\n').length; } return 1; } } }); </script> <style scoped> textarea { /* テキストエリアのリサイズを禁止する */ resize: none; font-family: "MS ゴシック"; font-size: 13px; } </style> |
CustomTextarea.vue では、テキストエリアの改行の数に応じて、テキストエリアの高さ (rows) を変更しています。
また、ソースコードを表示するので、自動改行が行われないように wrap=”off” を指定し、スタイルでテキストエリアのリサイズを禁止して、フォントを等幅フォントの「MS ゴシック 13px」にしています。
src\App.vue
テキストエリアを使用するアプリケーションを変更します。
App.vue では、HelloWorld コンポーネントを配置していた箇所を、新しく作成したテキストエリアの CustomTextarea コンポーネントに置き換えます。
テキストエリアに必要なプロパティをバインドして、イベントを処理します。
App.vue 変更前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import HelloWorld from './components/HelloWorld.vue'; @Component({ components: { HelloWorld, }, }) export default class App extends Vue {} </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> |
App.vue 変更後
App.vue についてもデコレータを使用する場合と、使用しない場合の両方のソースコードを記載します。
デコレータを使用する場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<template> <div id="app"> <div class="wrapper"> <div class="box"> <div class="field"> <label class="label">editbox <i class="fas fa-edit"></i></label> <div class="control"> <custom-textarea v-model="text" v-bind:placeholder="placeholder" v-bind:disabled="disabled" v-bind:readonly="readonly" v-on:focus="focus" v-on:blur="blur" /> </div> </div> </div> </div> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import CustomTextarea from './components/CustomTextarea.vue' @Component({ components: { CustomTextarea, }, }) export default class App extends Vue { // data text: string = ''; placeholder: string = 'please input here.'; disabled: boolean = false; readonly: boolean = false; // methods focus(): void { console.log('focus'); } blur(): void { console.log('blur'); } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif, FontAwesome; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; } </style> |
デコレータを使用しない場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<template> <div id="app"> <div class="wrapper"> <div class="box"> <div class="field"> <label class="label">editbox <i class="fas fa-edit"></i></label> <div class="control"> <custom-textarea v-model="text" v-bind:placeholder="placeholder" v-bind:disabled="disabled" v-bind:readonly="readonly" v-on:focus="focus" v-on:blur="blur" /> </div> </div> </div> </div> </div> </template> <script lang="ts"> import Vue from 'vue' import CustomTextarea from './components/CustomTextarea.vue' export default Vue.extend({ name: 'App', components: { CustomTextarea }, data() { return { text: '', placeholder: 'please input here.', disabled: false, readonly: false } }, methods: { focus(): void { console.log('focus'); }, blur(): void { console.log('blur'); }, } }); </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; } .wrapper { padding: 10px; } </style> |
テキストエリアの値は v-model でバインドします。
その他のプロパティの placeholder, disabled, readonly は v-bind でバインドします。
focus イベントと、blur イベントではサンプルとしてコンソールにログを表示する処理を記載しています。
テキストエリアの表示
ここまでの変更を適用して、ブラウザで http://localhost:8080/ に接続すると、以下のようなテキストエリアが表示されます。
このテキストエリアにコードを入力すると、以下のように改行に合わせて高さを自動で調節してくれます。
Vue.js の単一ファイルコンポーネントを使用すると、簡単にコンポーネントが作成できるのでとても便利です。