vue2 實現 div contenteditable="true" 相似於 v-model 的效果

問題

vue2 中對錶單控件有着良好的雙向數據綁定機制,可是對於要特定實現某些功能的輸入時,咱們就不得不使用到 contenteditable="true"div ,而在這個 div 上是使用 v-model 是沒有效果的。那麼問題就來了,輸入是很是須要雙向綁定的,這裏的雙向數據綁定該如何實現?css

解決思路一:自定義指令

固然,說在這一段的前面,這種解決方式在 vue2 中是不行的,爲何這麼說,由於如今去搜索這個問題絕大多數的搜索結果是這個,因此放在前面。html

實現的原理以及爲何不能用了

原理:自定義一個雙向數據綁定的指令,代碼以下:vue

Vue.directive('demo', {
    twoWay: true,
    bind: function () {
        this.handler = function () {
            this.set(this.el.innerHTML)
        }.bind(this)
        this.el.addEventListener('input', this.handler)
    },
    update: function (newValue, oldValue) {
        this.el.innerHTML = newValue || ''
    },
    unbind: function () {
        this.el.removeEventListener('input', this.handler)
    }
})

至於 this 下的這些方法,在 vue 官網上可能不太容易找到,由於這些是 vue1 中的內容,而在 vue2 中已經被移除了。因此在 vue2 中咱們是不能這麼幹的,固然若是你使用的是 vue1 那麼徹底沒問題,直接拿去用便可。ecmascript

解決思路二:使用組件

單獨聲明一個組件,在組件內部處理數據(也就是innerHTML),並將數據返回給父組件。
代碼以下:異步

<template>
    <div contenteditable="true"
         v-html="innerText"
         @input="changeText"></div>
</template>
<script>
    export default {
        props: ['value'],
        data(){
            return {innerText:this.value}
        },
        methods:{
            changeText(){
                this.innerText = this.$el.innerHTML;
                this.$emit('input',this.innerText);
            }
        }
    }
</script>

而後在父組件中直接使用 v-model 就能夠了(這裏我把組件名稱定義成了 v-edit-div)。ide

<template>
    <div>
        <v-edit-div v-model='text'></v-edit-div>
        <span>{{text}}</span>
    </div>
</template>
<script>
    export default {
        data(){
            return {
                text:'改一下試一試',
            }
        }
    }
</script>

至於爲何能夠直接用 v-model ,看官網的 API 吧。
v-model 傳送門 使用自定義事件的表單輸入組件,那一章節。ui

問題解決。this

=============== 分割線:更新於17-08-25 =====================spa

忙的不行,以前在評論區也有發現這個例子其實會有很多的問題,包括如何實現異步數據的刷新,更新值以後光標定位的問題等等,在考慮了異步數據和光標問題後,有了如下的這個版本雙向綁定

<template>
    <div class="edit-div"
         v-html="innerText"
         :contenteditable="canEdit"
         @focus="isLocked = true"
         @blur="isLocked = false"
         @input="changeText">
    </div>
</template>
<script type="text/ecmascript-6">
    export default{
        name: 'editDiv',
        props: {
            value: {
                type: String,
                default: ''
            },
            canEdit: {
                type: Boolean,
                default: true
            }
        },
        data(){
            return {
                innerText: this.value,
                isLocked: false
            }
        },
        watch: {
            'value'(){
                if (!this.isLocked || !this.innerText) {
                    this.innerText = this.value;
                }
            }
        },
        methods: {
            changeText(){
                this.$emit('input', this.$el.innerHTML);
            }
        }
    }
</script>
<style lang="scss" rel="stylesheet/scss">
    .edit-div {
        width: 100%;
        height: 100%;
        overflow: auto;
        word-break: break-all;
        outline: none;
        user-select: text;
        white-space: pre-wrap;
        text-align: left;
        &[contenteditable=true]{
            user-modify: read-write-plaintext-only;
            &:empty:before {
                content: attr(placeholder);
                display: block;
                color: #ccc;
            }
        }
    }
</style>

這個版本是在項目中最終使用的版本,須要用的直接拿走用便可。
注:

  1. canEdit 標誌這個div是不是可編輯的,在父組件直接使用 v-model 便可。
  2. 該組件應該是一個div元素(也不必定非要是div)的子元素,父元素的大小即爲子元素的大小。
相關文章
相關標籤/搜索