9

When I use custom directive to change component's value, there is not effect:

Vue.directive('maxchars', {
  bind(el, binding, vnode) {
    let maxChars = binding.value;
    let handler = function(e) {
      if (e.target.value.length > maxChars) {
        e.target.value = e.target.value.substr(0, maxChars)
      }
    }
    el.addEventListener('input', handler);
  }
});
let app = new Vue({
  el: '#app',
  data() {
    return {
      content: '',
      totalCount: 140
    }
  }
})
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<div id='app'>
  <div>
    <div class='content'>
      <textarea v-model='content' v-maxchars='140'>tell me something</textarea>
    </div>
  </div>
</div>

when I use v-bind:input directive to change value is ok!

acdcjunior
  • 132,397
  • 37
  • 331
  • 304
mail G
  • 93
  • 1
  • 1
  • 3

2 Answers2

16

First:

  • You are using v-model, the value of the textarea will be whatever is in the v-model's variable (in this case, the variable content). This means that the initial value of the DOM is ignored.
  • To handle this, I moved (see below) the string from the DOM declaration to the content in data().

Second:

  • Vue does not respond to changes in .value directly. v-model actually watches for input✱ events from the DOM element.
    • ✱ it actually varies depending on the type of element, sometimes it is the change event, or other
  • If you just set the value, Vue will simple override it back (to whatever is in content) next time an update happens.

Solution:

After changing the .value, trigger input event. Vue will pick the event up and update the v-model variable from the current .value before it overrides it.

Demo:

Vue.directive('maxchars', {
  bind(el, binding, vnode) {
    let maxChars = binding.value;
    let handler = function(e) {
      if (e.target.value.length > maxChars) {
        e.target.value = e.target.value.substr(0, maxChars);
        vnode.elm.dispatchEvent(new CustomEvent('input')); // added this
      }
    }
    el.addEventListener('input', handler);
  }
});
let app = new Vue({
  el: '#app',
  data() {
    return {
      content: 'tell me something',
      totalCount: 140
    }
  }
})
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<div id='app'>
  <div>
    <div class='content'>
      <textarea v-model='content' v-maxchars='18'></textarea>
    </div>
    <pre>
    content: {{ content }}
    Max chars is 18, current is {{ content.length }}.
    </pre>
  </div>
</div>
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • 3
    Be careful tho, CustomEvent is not supported in Internet Explorer. – tomJO Jul 31 '19 at 08:13
  • why you need this while we already have `maxlength` attr in html ? – santoshe61 Apr 30 '20 at 08:54
  • @santosh I agree, but I think the point is not so much about what we are doing with the model, but how to do it, that is, how to change the v-model prop via a custom directive. (As a side note, I recently had to implement a max-chars thing like this and preferred a twitter-like solution, showing how many chars were past allowed, instead of truncating them.) – acdcjunior Apr 30 '20 at 13:10
  • 2
    You don't need to remove the event listener in `unbind`? – abhim Oct 01 '20 at 12:23
  • 1
    @abhim perhaps. Depending on your case, if the `el` will still be around, it probably is wise to remove the listener when `unbind`ing. – acdcjunior Oct 01 '20 at 19:00
  • I realised the element won't be around, so why worry about unbinding it (facepalm) – abhim Oct 02 '20 at 16:15
6

Just create a input event using Event function.

var event = new Event("input", { bubbles: true });

then modify the value and then dispatch the event, it will update the v-model value

el.dispatchEvent(event);

Vue.directive('maxchars', {
  update(el, binding, vnode) {
    var event = new Event("input", { bubbles: true });
    let maxChars = binding.value;
      if (el.value.length > maxChars) {
        el.value = e.value.substr(0, maxChars);
        el.dispatchEvent(event);
      }
  }
});

hope it will helpful.

Shaik Matheen
  • 1,233
  • 15
  • 14