vuejs 2 v-for :key not working, html being replaced? - vue.js

I'm rendering some HTML in a v-for
But everytime I change any of the data, all my html gets replaced (input fields lose their values)
I tried giving the :key all kinds of different values
I didn't have this problem in vue v1, only in v2
http://jsbin.com/jomuzexihu/1/edit?html,js,output

I had a little play with this and it appears that Vue does not re-render the entire list when using <input /> or if you use a component but it does with v-html. Heres the fiddle for the comparison:
https://jsfiddle.net/cxataxcf/
The key actually isn't needed here because the list isn't being re-ordered, so your issue isn't to do with :key but rather with v-html. Heres what the docs say about v-html:
The contents are inserted as plain HTML - data bindings are ignored. Note that you cannot use v-html to compose template partials, because Vue is not a string-based templating engine. Instead, components are preferred as the fundamental unit for UI reuse and composition.
So I guess this is where the problem lies.
It might be worth raising an issue on Vue's github page to see whether this is the expected behavior for v-html, but Vue 2.0 is much more heavily focused on components than vue 1.x and doesn't appear to recommend using v-html, so it may just be that you need to re-factor your code to use components instead.
Edit
The solution to this problem is to simply wrap the code in a component and pass the HTML as a prop:
Vue.component('unknown-html', {
props: {
html: ""
},
template: '<div><div v-html="html"></div>'
})
The markup:
<unknown-html :html="thing.html"></unknown-html>
And the View model:
var app = new Vue({
el: '#app',
data: {
numInputs: 1,
stuff: [{
'html':'<input />'
}, {
'html':'<button>Foo</button>'
}]
}
})
Here's the JSFiddle: https://jsfiddle.net/wrox5acb/

You are trying to inject raw html directly into the DOM. Probably it was possible in earlier versions of Vue.js, but it is definitely not the recommended way.
You can instead have an array of objects and bind it to html as shown in this jsFiddle: https://jsfiddle.net/43xz6xqz/
Vue.js version: 2.0.3
In the example above, vue.js is responsible for creating the input elements and also for binding these input elements to the object values using v-model.
To extract these values, you may use a computed property as shown in the sample code.

I guess, for performance optimization, when the key is not change, Vue will not rerender the dom, but will update the data import through directive.So when your input element is import through an directive (v-html), it will be rerendered everytime when stuff changes.
Due to the vue is not a string template engines, but template based on dom, so in the case of #craig_h 's example , to use the incomming html in a string template within a component:
Vue.component('unknown-html', {
props: {
html: ""
},
template: '<div><div v-html="html"></div>'
})
view:
<unknown-html :html="thing.html"></unknown-html>
So when the stuff changes, it will not to rerender the template declare in string, for vue is not a string template engine.

Related

Vuejs:Why is render method returning dynamic `<component>` giving errors?

I am trying to return vue's builtin <component/> from the render method, but it is not recognizing the component. what could be the possible mistake?
Vue.component('comp1',{
template:'<h1>Component1</h1>'
});
Vue.component('comp2',{
template:'<h1>Component2</h1>'
});
let app=new Vue({
el:'#app',
data:function(){
return {
comp:'comp1'
}
},
mounted:function(){
setInterval(()=>{
if(this.comp=='comp1'){
this.comp="comp2"
}else{
this.comp="comp1"
}
},1000);
},
render (h) {
return h('component', this.comp)
}
})
linkto js bin: https://jsbin.com/wakivet/edit?html,js,console,output
Replace:
render(h){
return h('component',this.comp);
}
with
template: '<component :is="comp" />'
There is no built-in component called component in Vue.
Vue needed a syntax for creating dynamic components in a template. It also needed a way to solve the problems caused by in-DOM templates requiring specific elements as children of other elements. Both of these problems could be solved the same way, with the introduction of the is attribute.
But even though the desired tag name is specified via the is attribute we still need to include a tag name in the template. Any tag will work, but the convention is to use the tag <component>.
<component :is="comp">
Using this convention allows other developers to know that the tag is dynamic. It also makes it easier for the Vue template compiler to optimise the template.
Vue will log a warning if anyone tries to register a global component called component, just like it would for other reserved tag names. This helps to ensure there's no ambiguity when those tags are used.
However, it is just a convention. Using other tags will work just fine and with in-DOM templates it is sometimes necessary, see https://v2.vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats.
But what about render functions?
The first argument passed to the function h is the tag name. We don't need to use tricks like the is attribute to make that dynamic, we can just pass it directly:
h(this.comp)
In fact, if you write a template containing <component :is="comp"> then Vue will compile it down to something very similar to this. The compiled render function doesn't keep the is attribute, it throws that away and just passes its value to the h function.
You can see this for yourself if you inspect a compiled template. For example, you can use the tool at https://v2.vuejs.org/v2/guide/render-function.html#Template-Compilation to see how templates are compiled to render functions. The function _c is what you called h.
So to reiterate, there is no such component as <component>. It is merely a placeholder in template syntax for some other component. When you're in a render function you should just use the desired component directly.

Generate Vue Component with vanilla JS

I am currently using the bootstrap table component to display a list (bootstrap-table.com)
This component (or jQuery plugin wrapped within a vue component) has a formatter for each column.
example with source code: https://examples.bootstrap-table.com/#welcomes/vue-component.html#view-source
I need to build a more complex set of links which involve some js processing that could easily be done with vue.
Instead of the formatter returning the <a> tag I would like to return the contents of a <component>. I have tried this but haven't been able to render a component.
I'm almost sure this is impossible because the child component is not previously binded which means the component won't be "reactive".
As an alternative I can build a js function that generates all the required links.
Another phrasing for this question would be: Is there any possibility to generate a vue component from vanilla js?
The above was not possible the way was tried because the components cannot be simply rendered like html tags as they can have methods and computed data therefore they must be compiled.
It looks like the solution for the above situation is using the Vue.compile() method:
const template = `
<ul>
<li v-for="item in items">
{{ item }}
</li>
</ul>`;
const compiledTemplate = Vue.compile(template);
new Vue({
el: '#app',
data() {
return {
items: ['Item1', 'Item2']
}
},
render(createElement) {
return compiledTemplate.render.call(this, createElement);
}
});
Demo:
https://codepen.io/couellet/pen/ZZNXzy
Demo reference:
https://snipcart.com/blog/vue-render-functions
Vue docs:
https://v2.vuejs.org/v2/api/#Vue-compile

VUE.JS - Using the vNode argument to add unknown quantity of items to a dynamic navigation

I'm currently trying to create a component which manages several linked dropdowns and elements on a page. In addition, this element supplies a rather fancy navigation element, containing anchor links which automatically scroll to the desired element in this component.
The problem is that the actual contents of the component are completely dynamic and partially determined by the content manager in the CMS. There are several sub components that are always present, but apart from that the content manager can add any number of sections, (using various named and an unnamed ) and each of these sections should be added to the navigation of the component.
I see 3 options:
For every component added, it's title and unique id is added to a property array on the parent component. This, however, is a bit prone to errors. (Unfortunately I will have no control over the eventual backend implementation, so I'm trying to create a foolproof system to avoid to much wasted time.)
Due to other components, I'm already using Vuex to manage most of the data in the app. I figured I use a simple directive, to be added on each item in the parent component. This directive is responsible for adding that element to the Vuex store. The parent component simply reads the contents of the store and generates the navigation based on this.
The problem here is that, as far as I can tell, I have to use the vNode argument in the bind hook of my directive to access the Vuex store. This seems... hacky. Is there anything wrong with this approach?
In the mounted hook of my parent component, I traverse the DOM, searching for elements with a particular data-property, and add a link to each of these elements to my navigation. Seems prone to failure and fragile.
What is the preferred approach for this situation?
As a follow up question - What is the correct use case for the vNode argument in a vue directive? The documentation seems rather sparse on this subject.
I would steer away from using a directive in this case. In Vue 2, the primary use case for directives is for low level DOM manipulation.
Note that in Vue 2.0, the primary form of code reuse and abstraction
is components - however there may be cases where you just need some
low-level DOM access on plain elements, and this is where custom
directives would still be useful.
Instead I would suggest a mixin approach, where your mixin essentially registers your components that should be included in navigation with Vuex.
Consider the following code.
const NavMixin = {
computed:{
navElement(){
return this.$el
},
title(){
return this.$vnode.tag.split("-").pop()
}
},
mounted(){
this.$store.commit('addLink', {
element: this.navElement,
title: this.title
})
}
}
This mixin defines a couple of computed values that determine the element that should be used for navigation and the title of the component. Obviously the title is a placeholder and you should modify it to suit your needs. The mounted hook registers the component with Vue. Should a component need a custom title or navElement, mixed in computed properties are overridden by the component's definition.
Next I define my components and use the mixin.
Vue.component("child1",{
mixins:[NavMixin],
template:`<h1>I am child1</h1>`
})
Vue.component("child2",{
mixins:[NavMixin],
template:`<h1>I am child2</h1>`
})
Vue.component("child3",{
template:`<h1>I am child3</h1>`
})
Note that here I am not adding the mixin to the third component, because I could conceive of a situation where you may not want all components included in navigation.
Here is a quick example of usage.
console.clear()
const store = new Vuex.Store({
state: {
links: []
},
mutations: {
addLink (state, link) {
state.links.push(link)
}
}
})
const NavMixin = {
computed:{
navElement(){
return this.$el
},
title(){
return this.$vnode.tag.split("-").pop()
}
},
mounted(){
this.$store.commit('addLink', {
element: this.navElement,
title: this.title
})
}
}
Vue.component("child1",{
mixins:[NavMixin],
template:`<h1>I am child1</h1>`,
})
Vue.component("child2",{
mixins:[NavMixin],
template:`<h1>I am child2</h1>`
})
Vue.component("child3",{
template:`<h1>I am child3</h1>`
})
Vue.component("container",{
template:`
<div>
<button v-for="link in $store.state.links" #click="scroll(link)">{{link.title}}</button>
<slot></slot>
</div>
`,
methods:{
scroll(link){
document.querySelector("body").scrollTop = link.element.offsetTop
}
},
})
new Vue({
el:"#app",
store
})
h1{
height:300px
}
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<container>
<child1></child1>
<child3></child3>
<child2></child2>
</container>
</div>
This solution is pretty robust. You do not need to parse anything. You have control over which components are added to the navigation. You handle potentially nested components. You said you don't know which types of components will be added, but you should have control over the definition of the components that will be used, which means its relatively simple to include the mixin.

Vuejs add multiple lines to a template?

I would like to know how best to arrange new lines when constructing a Vuejs template. The code I have as is does not work, as it breaks the JavaScript container. Vue.js wants me to put the entire html in one line, which is somewhat impractical when I plan on adding footer content.
Vue.component('footer-component', {
template: "
<div id='footer'>
Footer Stuff
</div>"
})
new Vue({
el: 'footer'
})
I've been trying to find examples of HTML templating with Vue, but I have trouble finding them. When using React I am able to code my HTML over multiple lines. How might I do the same for Vuejs?
You can use template literals to surround your template string. This would allow you to write multiple lines of HTML in your template property.
You can read more of this in Mozilla's Javascript reference on Template literals.
Vue.component('footer-component', {
template: `
<div id='footer'>
Footer Stuff
</div>`
})
new Vue({
el: 'footer'
})
I am also looking that you want to reference the element 'footer' in your Vue object, maybe you should try to reference another element. In this case, the element you want to be the father of your footer-component.
Here is an interesting article that describes several (7) methods of defining vue component templates:
7 Ways To Define A Component Template in Vue.js
It includes the above mentioned method of using template literals, but lists also other options of dealing with multi line templates like x-templates, inline templates, single file components, and even JSX.
I'm not the author of said article (it's Anthony Gore), but new to vue and had the same question about multiline templates as well.

Use cases for vue.js directive vs component?

When should I use a directive vs a component in vue.js? I'm implementing some stuff from Bootstrap and it looks like I could do it either way (I'm starting with the dropdown menu).
I get the feeling that a directive is more for manipulating the dom on a single element, while components are for packaging a bunch of data and/or dom manipulation. Is this a good way to look at it?
This Stack Overflow question is the #1 result to the Google query "vue directive vs component". Saurshaz’s answer is currently the accepted one and it’s very wrong in Vue 2.0. I imagine this is leading a lot of people astray so I'm going to weigh in here.
The answer to “should I use a directive or a component in Vue” is almost always a component.
Do you want to have reusable HTML? I.e. reusable widgets? Then use a component. Do you want two of these widgets to have discrete data? Then use a component. The data of one will NOT override the data of another. Maybe that was true in Vue 1.0, I don't know. But it's absolutely not true in Vue 2.0. In Vue 2.0, your components have a data function that returns a unique set of data. Consider this real-life of a Vue dropdown that has an HTML markup similar to the UI Bootstrap dropdown:
<template>
<span class="dropdown sm-dropdown" #click="toggle" :class="{'open': isOpen}">
<a class="dropdown-toggle">
<span class="special-field">{{ label }}</span>
</a>
<ul class="dropdown-menu">
<li v-for="choice in choices">
<a #click.prevent="click(choice)">{{ choice.label }}</a>
</li>
</ul>
</span>
</template>
<script>
export default {
name: 'Dropdown',
props: ['label', 'options', 'onChange'],
data() {
return {
choices: this.options,
isOpen: false
}
},
methods: {
click(option) {
this.onChange(option);
},
toggle() {
this.isOpen = !this.isOpen;
}
}
}
</script>
Now in a parent component, I can do something like this:
<template>
<div class="container">
<dropdown
label="-- Select --"
:options="ratingChoices"
:onChange="toggleChoice"
>
</dropdown>
<dropdown
label="-- Select --"
:options="ratingChoices"
:onChange="toggleChoice"
>
</dropdown>
</div>
</template>
<script>
import Dropdown from '../dropdown/dropdown.component.vue';
export default {
name: 'main-directive',
components: { Dropdown },
methods: {
toggleChoice(newChoice) {
// Save this state to a store, e.g. Vuex
}
},
computed: {
ratingChoices() {
return [{
value: true,
label: 'Yes'
}, {
value: false,
label: 'No'
}]
}
}
}
</script>
There's a decent amount of code here. What's happening is we're setting up a parent component and inside that parent component we have two dropdowns. In other words, the dropdown component is being called twice. The point I'm trying to make in showing this code is this: when you click on the dropdown, the isOpen for that dropdown changes for that directive and for that directive only. Clicking on one of the dropdowns does not affect the other dropdown in any way.
Don't choose between components or directives based on whether or not you're wanting discrete data. Components allow for discrete data.
So when would you want to choose a directive in Vue?
Here are a couple of guidelines that'll hopefully get you thinking in the right direction.
You want to choose a directive when you're wanting to extend the functionality of HTML components and you suspect that you’re going to need this extendability across multiple components and you don't want your DOM to get deeper as a result. To understand what I mean by this, let's look at the directives that Vue provides out of the box. Take its v-for directive for instance. It allows you to loop through a collection. That's very useful and you need to be able to do that in any component you want, and you don't want the DOM to get any deeper. That's a good example of when a directive is the better choice.[1]
You want to choose a directive when you want a single HTML tag to have multiple functionality. For example, an element that both triggers an Ajax request and that has a custom tooltip. Assuming you want tooltips on elements other than Ajax-triggering elements, it makes sense to split these up into two different things. In this example I would make the tooltip a directive and the Ajax feature driven by a component so I could take advantage of the built-in #click directive that’s available in components.
1 A footnote for the more curious. In theory v-for could have been made as a component, but doing so would have required a deeper-than-necessary DOM every time you wanted to use v-for as well as a more awkward syntax. If Vue had chosen to make a component out of it, instead of this:
<a v-for="link in links" :href="link.href">link.anchor</a>
The syntax would have had to have been this:
<v-for items="link in links">
<a :href="link.href">link.anchor</a>
</v-for>
Not only is this clumsy, but since the component code would have needed to implement the <slot></slot> syntax in order to get the innerHTML, and since slots cannot be immediate children of a <template> declaration (since there's no guarantee that slot markup has a single node of entry at its top level), this means there would have to be a surrounding top-level element in the component definition for v-for. Hence the DOM would get deeper than necessary. Directive was unequivocally the right choice here.
I think of it this way:
Components define widgets - these are sections of html that have behavior associated with them.
Directives modify behavior of sections of html (which may or may not be widgets).
I think this difference is better explained with two examples.
Components: are wrappers that are best suited when you need to insert (or add) your own HTML tags over something to render it. E.g. a widget, a custom button, etc where you would need to add some HTML tags to show it properly.
Directives: don't add tags but rather give you direct access to the HTML tag (to which you have added the directive). This gives you access to modify the attributes of that HTML element directly. E.g. initializing a tooltip, set css styles, bind to an event, etc.
Reusability is a reason for using directives,
While Components are also creating reusable 'widgets', two components in the same html system would overwrite the previous ones 'data', So think of directives in a case like this.
Another point worth thinking of is - Can user be using it via HTML only after some instructions ?