前回に続き、機能を作る
IndentInput.vue
, Editor.vue
2つのコンポーネントを作る
まとめ
わからなかったこと
- 本記事のように動きを持たせたい場合に於ける、 Vue.js 上でのデータの管理方法
- Vue.js がデータの拘束方法を提供してくれているものの、複雑なことがしたくなったときにどうしても
data-xxxx
に値を渡すなど "Vue.js から逃げたくなってしまう"、のはいいのか、あるいはその対処法
「動いてるからいいじゃん」...? え いや そんなバカな... (´・ω・`)
ゴール
- [Ctrl] + [←], [Ctrl] + [→] で入力欄を "インデント" する (CSS クラスを動的に切り替える)
<template/>
を記述する
Editor.vue
:
<template> <div> <h1>{{ counter }}</h1> <h2>{{ indentLevelList }}</h2> <div class="flex-container"> <div class="row"> <div @keyup.ctrl.backspace="decrementCount" @keyup.ctrl.enter="incrementCount" @keyup.ctrl.uparrow="moveUpFocus" @keyup.ctrl.downarrow="moveDownFocus" @keyup.ctrl.rightarrow="incrementIndentLevel" @keyup.ctrl.leftarrow="decrementIndentLevel" > <indent-input v-for="i in counter" :data-index="i" :key="i" ref="input" :level="indentLevelList[i - 1]" ></indent-input> </div> </div> </div> </div> </template>
IndentInput.vue
:
<template> <input ref="input" :class="indentClass" type="text"> </template>
<script/>
を記述する
Editor.vue
:
<script> import IndentInput from '@/components/IndentInput' export default { name: 'Editor', // FIXME: 新しい <input/> を挿入したときに入力済みの文字のインデックス位置がズレている // FIXME: <input/> の削除で 削除対象の位置がズレている // TODO: インデックスと併せて入力された文字の内容を保持するデータ構造に変更する data () { return { counter: 1, indentLevelList: [0] } }, methods: { moveUpFocus: function (event) { event.target.previousSibling.focus() }, moveDownFocus: function (event) { event.target.nextSibling.focus() }, incrementIndentLevel: function (event) { this.indentLevelList[event.target.getAttribute('data-index') - 1]++ this.$forceUpdate() }, decrementIndentLevel: function (event) { this.indentLevelList[event.target.getAttribute('data-index') - 1]-- this.$forceUpdate() }, decrementCount: function (event) { this.counter-- let startIndex = event.target.getAttribute('data-index') - 1 let deleteionSize = 1 this.indentLevelList.splice(startIndex, deleteionSize) this.moveUpFocus(event) }, incrementCount: function (event) { this.counter++ let dataIndex = event.target.getAttribute('data-index') let startIndex = parseInt(dataIndex) - 1 let deleteionSize = 0 let level = this.indentLevelList[parseInt(dataIndex) - 1] this.indentLevelList.splice(startIndex + 1, deleteionSize, level) let self = this this.$nextTick(() => { self.moveDownFocus(event) }) } }, components: { 'indent-input': IndentInput } } </script>
IndentInput.vue
:
<script> export default { name: 'IndentInput', data () { return { indentClass: 'indent-0' } }, props: { 'level': { type: Number, default: 0 } }, mounted () { if (this.$refs.input.getAttribute('data-level') !== null) { this.indentClass = ''.concat('indent-', this.$refs.input.getAttribute('data-level')) this.$refs.input.removeAttribute('data-level') } this.updateIndentLevel() this.$refs.input.focus() }, updated () { this.updateIndentLevel() }, watch: { level: function (value) { this.updateIndentLevel() this.$forceUpdate() } }, methods: { updateIndentLevel: function () { if (this.level != null) { this.indentClass = ''.concat('indent-', this.level) } } } } </script>
<style/>
を記述する
Editor.vue
:
<style scoped> html, body { height: 100%; } body { margin: 0; } .flex-container { height: 100%; padding: 0; margin: 0; display: flex; align-items: center; justify-content: center; } .row { width: auto; } .flex-item { padding: 5px; width: auto; height: 20px; margin: 10px; line-height: 20px; color: white; font-weight: bold; font-size: 2em; text-align: center; } .control { display: flex; } </style>
IndentInput.vue
:
<style scoped> input { display: block; --indent-margin: 30px; --indent-prefix: 15px; } .indent-1 { margin-left: var(--indent-margin); } .indent-2 { margin-left: calc(var(--indent-margin) + (var(--indent-prefix) * 2)) } .indent-3 { margin-left: calc(var(--indent-margin) + (var(--indent-prefix) * 3)) } .indent-4 { margin-left: calc(var(--indent-margin) + (var(--indent-prefix) * 4)) } </style>