Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vue.js DOM Template Parsing Caveats

Tags:

html

vue.js

Vue docs DOM Template Parsing Caveats says:

It should be noted that this limitation does not apply if you are using string templates from one of the following sources:

  • String templates (e.g. template: '...')

v-for with a Component says

Note the is="todo-item" attribute. This is necessary in DOM templates […]

But is not this a "string template"?

Vue.component('todo-item', {
  template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  ',
  props: ['title']
})
like image 978
Yiping Avatar asked Dec 24 '22 05:12

Yiping


2 Answers

You might be confused by the "scope" of the string template that relieves you from the mentioned limitation.

If the HTML parent element that limits what type of children it can have (in this case <ul>) is in a string template, then you are fine.

But if it is within the real DOM, then the browser will start checking for the limitations and hoist out child elements that it does not expect.

The precise example in Vue docs may no longer be very relevant, since browsers seem now to accept Custom Elements as children of <ul> (at least in Chrome and Firefox):

Vue.component('todo-item', {
  template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  ',
  props: ['title']
})

new Vue({
  el: '#todo-list-example',
  data: {
    newTodoText: '',
    todos: [{
        id: 1,
        title: 'Do the dishes',
      },
      {
        id: 2,
        title: 'Take out the trash',
      },
      {
        id: 3,
        title: 'Mow the lawn'
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function() {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})
li {
  /* Items will be RED if hoisted out of the UL */
  background-color: red;
}

ul li {
  /* Items will be GREEN if kept inside the UL */
  background-color: green;
}
<script src="https://unpkg.com/vue@2"></script>

<div id="todo-list-example">
  <form v-on:submit.prevent="addNewTodo">
    <label for="new-todo">Add a todo</label>
    <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat">
    <button>Add</button>
  </form>
  <ul>
    <!-- Using directly the Component in DOM -->
    <todo-item
      v-for="(todo, index) in todos"
      v-bind:key="todo.id"
      v-bind:title="todo.title"
      v-on:remove="todos.splice(index, 1)"
    ></todo-item>
  </ul>
</div>

However consider the case where we replace <ul> by a <table> and <li> by the table rows <tr><td>:

Vue.component('todo-item', {
  template: '\
    <tr><td>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </td></tr>\
  ',
  props: ['title']
})

new Vue({
  el: '#todo-list-example',
  data: {
    newTodoText: '',
    todos: [{
        id: 1,
        title: 'Do the dishes',
      },
      {
        id: 2,
        title: 'Take out the trash',
      },
      {
        id: 3,
        title: 'Mow the lawn'
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function() {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})
tr,
td {
  /* Cells will be RED if hoisted out of the table */
  background-color: red;
}

table tr,
table td {
  /* Cells will be GREEN if kept inside the table */
  background-color: green;
}
<script src="https://unpkg.com/vue@2"></script>

<div id="todo-list-example">
  <form v-on:submit.prevent="addNewTodo">
    <label for="new-todo">Add a todo</label>
    <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat">
    <button>Add</button>
  </form>
  <table>
    <!-- Using directly the Component in real DOM -->
    <todo-item
      v-for="(todo, index) in todos"
      v-bind:key="todo.id"
      v-bind:title="todo.title"
      v-on:remove="todos.splice(index, 1)"
    ></todo-item>
  </table>
</div>

Now if the app template is no longer left in real DOM but also specified as a template string:

Vue.component('todo-item', {
  template: '\
    <tr><td>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </td></tr>\
  ',
  props: ['title']
})

new Vue({
  el: '#todo-list-example',
  template: `
  <div>
    <form v-on:submit.prevent="addNewTodo">
      <label for="new-todo">Add a todo</label>
      <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat">
      <button>Add</button>
    </form>
    <table>
      <!-- Using directly the Component in template string -->
      <todo-item
        v-for="(todo, index) in todos"
        v-bind:key="todo.id"
        v-bind:title="todo.title"
        v-on:remove="todos.splice(index, 1)"
      ></todo-item>
    </table>
  </div>
  `,
  data: {
    newTodoText: '',
    todos: [{
        id: 1,
        title: 'Do the dishes',
      },
      {
        id: 2,
        title: 'Take out the trash',
      },
      {
        id: 3,
        title: 'Mow the lawn'
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function() {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})
tr,
td {
  /* Cells will be RED if hoisted out of the table */
  background-color: red;
}

table tr,
table td {
  /* Cells will be GREEN if kept inside the table */
  background-color: green;
}
<script src="https://unpkg.com/vue@2"></script>

<div id="todo-list-example">
</div>

And for the sake of completeness, if we keep the app template in real DOM, but correctly use the is special attribute to refer to our Component, as done in the Vue doc example:

Vue.component('todo-item', {
  template: '\
    <tr><td>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </td></tr>\
  ',
  props: ['title']
})

new Vue({
  el: '#todo-list-example',
  data: {
    newTodoText: '',
    todos: [{
        id: 1,
        title: 'Do the dishes',
      },
      {
        id: 2,
        title: 'Take out the trash',
      },
      {
        id: 3,
        title: 'Mow the lawn'
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function() {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})
tr,
td {
  /* Cells will be RED if hoisted out of the table */
  background-color: red;
}

table tr,
table td {
  /* Cells will be GREEN if kept inside the table */
  background-color: green;
}
<script src="https://unpkg.com/vue@2"></script>

<div id="todo-list-example">
  <form v-on:submit.prevent="addNewTodo">
    <label for="new-todo">Add a todo</label>
    <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat">
    <button>Add</button>
  </form>
  <table>
    <!-- Using a TR with "is" in real DOM -->
    <tr
      is="todo-item"
      v-for="(todo, index) in todos"
      v-bind:key="todo.id"
      v-bind:title="todo.title"
      v-on:remove="todos.splice(index, 1)"
    ></tr>
  </table>
</div>
like image 132
ghybs Avatar answered Dec 25 '22 18:12

ghybs


No! The caveat applies to templates that are loaded by the browser before they are processed by Vue. This is the case if you do something like...

vm = new Vue({el : '#vueRoot'})
// the template under vueRoot is in a real .html page and must be processed by Vue after loading by the browser.

If you use the <template> element, the same thing applies. The browser is loading into the DOM what it thinks is HTML, but is really a raw Vue template.

In your example todo-item, what counts is not the component template code you included, but the place where it's called. You can use <todo-item> in javascript, but you must (or should) use <li is="todo-item"> in the cases above, where this custom element will be loaded into the DOM before Vue takes it out again.

like image 24
bbsimonbb Avatar answered Dec 25 '22 19:12

bbsimonbb