0
votes

I'm looking for a concise example of two Vue components. The first component should contain a text input or textarea. The second component displays a character counter. I would like the first component to emit change events, and the second component should listen for those events and display its computed values (character count). I'm new to Vue and trying to wrap my head around the best way to implement this functionality. It seems rather straightforward in pure JavaScript but doing it the Vue way is not as clear to me. Thanks.

Here is how I'd do it in JavaScript:

Here's the textarea:

<textarea id="pagetext" name="pagetext"
                          onChange="characterCount();"
                          onKeyup="characterCount();">Type here</textarea>

Here's the JavaScript:

function characterCount()
{ 
        var characters=document.myForm.pagetext.value.length;
        document.getElementById('charcounter').innerHTML=characters+"";
}

My concern with Vue is passing the entire value around... for performance reasons this seems less than ideal. I may want my text editing Vue component to self-contain the value and emit the stats, ie the value for character count which would then be observed by a text stats component.

2
Pretty simple, you use events and props to propagate the value from one component to another. In this case, you'll probably want to emit the input value to its parent. There, you can either directly compute the input length or push the value to another child component via prop. - ssc-hrep3
Write up a demo snippet in pure JavaScript, and we can show you how to modify it to the Vue way. - Roy J
Added a plain old JavaScript snippet and a performance question. - Cliff Helsel

2 Answers

0
votes

I've written up a snippet with four examples: your original, a simple Vue app (no components) that does the same thing, and two apps with two components that are coordinated by the parent.

The simple Vue app is actually more concise than the pure JavaScript app, and I think it shows off the reason for having a framework: your view doesn't act as a store for your program data, from which you have to pull it out.

In the final example, the parent still owns pageText, but passes it down to the my-textarea component. I like to hide the emitting behind the abstraction of a settable computed, so that the element can use v-model. Any changes are emitted up to the parent, which changes pageText, which propagates back down to the component.

I think your performance concerns fall into the realm of premature optimization, but it is possible not to use the text content as data at all, and only be concerned with the length. The fourth example does that. emitLength could have used event.target.value.length, but I wanted to use it in the mounted to initialize the length properly, so I used a ref.

function characterCount() {
  var characters = document.myForm.pagetext.value.length;
  document.getElementById('charcounter').innerHTML = characters + "";
}
new Vue({
  el: '#app',
  data: {
    pageText: 'Type here'
  }
});

new Vue({
  el: '#app2',
  data: {
    pageText: 'Type here'
  },
  components: {
    myTextarea: {
      props: ['value'],
      template: '<textarea name="pagetext" v-model="proxyValue"></textarea>',
      computed: {
        proxyValue: {
          get() {
            return this.value;
          },
          set(newValue) {
            this.$emit('input', newValue);
          }
        }
      }
    },
    textLength: {
      props: ['value'],
      template: '<div>{{value}}</div>'
    }
  }
});

new Vue({
  el: '#app3',
  data: {
    textLength: null
  },
  components: {
    myTextarea: {
      template: '<textarea ref="ta" name="pagetext" @input="emitLength">Type here</textarea>',
      methods: {
        emitLength() {
          this.$emit('change', this.$refs.ta.value.length);
        }
      },
      mounted() {
        this.emitLength();
      }
    },
    textLength: {
      props: ['value'],
      template: '<div>{{value}}</div>'
    }
  }
});
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<form name="myForm">
  <textarea id="pagetext" name="pagetext" onChange="characterCount();" onKeyup="characterCount();">Type here</textarea>
</form>
<div id="charcounter"></div>

<div id="app">
  <h1>Vue (simple)</h1>
  <form>
    <textarea name="pagetext" v-model="pageText"></textarea>
  </form>
  <div>{{pageText.length}}</div>
</div>

<div id="app2">
  <h1>Vue (with components)</h1>
  <form>
    <my-textarea v-model="pageText"></my-textarea>
  </form>
  <text-length :value="pageText.length"></text-length>
</div>

<div id="app3">
  <h1>Vue emitting stats</h1>
  <form>
    <my-textarea @change="(v) => textLength=v"></my-textarea>
  </form>
  <text-length :value="textLength"></text-length>
</div>
0
votes

You can create a "Model" for value of textarea and provide this model to second component by using following way https://vuejs.org/v2/guide/components-props.html