I'm newbie in virtual DOM topic and last days I've been thinking of how it should work.
Let's imagine that I have a simple template engine in my project, for instance twig. And I'm using vue.js as a javascript framework.
So I could build the following page:
<div id="app">
<p>Some text</p>
<ul>
<li v-for="item in items">{{ item }}</li>
</ul>
<component><component/>
</div>
<template id="component">
<div class="center-xs">
<div class="row">
<div class="col-xs-12">
Some text
</div>
</div>
</div>
</template>
<script>
Vue.component('component', {
template: '#component'
});
var app = new Vue({
el: '#app',
data: {
items: ['item1', 'item2']
}
});
</script>
Which part of code would be Virtual DOM:
a. Nothing
b. Everything inside of #app
c. Items and component
d. Only component
And why? It'd great if share with me any info (links, your thoughts, official docs).
Thank u!
The code in the <div id=app> (e.g.<p> <ul>) is not DOM it is HTML markup that the browser then parses and then renders as its DOM. Since there is existing html code in the #app element it will be included by Vue as part of its template and thus be part of the Virtual DOM. Though technically since the <p> element never has any Vue operations performed on it, it is ignored.
If you have Vue Devtools extension installed in your browser you can see the representation of the Virtual DOM in the components view. <ROOT> will be your #app div of course, and then you will only see any <components> there, not the <p> element or even the <ul><li> elements.
It is a good practice to never have any html markup at all or otherwise component tags in your root element. Simply<div id='app'></div> then have all the other elements rendered exclusively by Vue through the Virtual DOM. This is achieved simply through the render function.
new Vue({
el: '#app',
components: {
TopLevelComponent
},
render: function(createElement) {
return createElement(TopLevelComponent);
}
})
https://v2.vuejs.org/v2/guide/render-function.html#The-Virtual-DOM
Related
I have a simple Vue app where I'm trying to render multiple custom components, here's what I'm trying:
JS
Vue.component('refinement-list', {
props: ['attribute', 'title'],
template: '<div>{{attribute}} - {{title}}</div>'
});
new Vue({
el: '#app'
});
HTML
<div id="app">
<refinement-list attribute="test" title="Test" />
<refinement-list attribute="sample" title="Sample" />
</div>
However the problem is that only the first component is rendered, see working example:
https://codepen.io/javiervd/pen/vYBpQMm
I tried registering the component locally instead but no luck. I'm also using Vue with a script tag in my app because I can't use a build system for this particular project, not sure if that matters.
Can anyone point me in the right direction? Thanks.
Since you are defining the template in the DOM, you can't use self-closing tags for the custom components.
This should work:
<div id="app">
<refinement-list attribute="test" title="Test"></refinement-list>
<refinement-list attribute="sample" title="Sample"></refinement-list>
</div>
This limitation doesn't exist in template strings or Single File Components.
You can read more about this here: https://v2.vuejs.org/v2/style-guide/#Self-closing-components-strongly-recommended
You should give like this in the HTML section if you want to use the self-closing tags. But it's not recommended for DOM Templates
<div id="app">
<div>
<refinement-list attribute="test" title="Test1" />
</div>
<div>
<refinement-list attribute="test" title="Test2" />
</div>
</div>
I forked your codepen and can confirm this is working with the separate closing tag style, in place of using self-closing tags for the components.
<div id="app">
<refinement-list attribute="test" title="Test"></refinement-list>
<refinement-list attribute="sample" title="Sample"></refinement-list>
</div>
ref: https://codepen.io/edm00se/pen/pozpqym?editors=1010
The self-closing tag versus (full?) closing tag is discussed in the vue style guide page (v2) and it is expressed that for string templates (as I suspect codepen is doing when loading your HTML content), to use the closing tag, while self-closing are fine in single-file components, JSX, and string templates (things which are processed during build and component names are better known).
Is it possible to use v-model on an element in the DOM that is outside the root element of the Vue instance?
I.e, Could you have the following:
$(function(){
Vue.component('custom-element', {
template: '#custom-template'
})
var vm = new Vue({
el: "#app"
data: {
selected: ""
}
})
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<select id="SportSelector" v-model="selected">
<option value="football"> Football </option>
<option value="tennis"> Tennis </option>
</select>
<div id="app">
<custom-element>
</custom-element>
</div>
<script type="text/x-template" id="custom-template">
<div>
<p> {{selected}} </p>
</div>
</script>
Where the select is outside the root element ("app") of the Vue. This is a simplified version of my problem, but I believe highlights the difficulties I'm having.
I saw this answer talking about centralised state management for sharing data between Vues but that seems a bit heavy to have to wrap the select in an entirely different Vue, I feel like i'm missing something major! (still very new to Vue). Does everything the Vue interacts with have to be under the root element of the instance of the Vue?
No, you cannot use v-model on elements outside the context of the Vue. The reason is Vue compiles down to a render function that renders the contents of the element it controls; it doesn't render anything outside that element, unless you involve specific code or a library like vue-portal.
That said, you can update Vue when the select changes using standard javascript set the Vue data property. I also cleaned up a few things like passing the selected data to your component (which is necessary; components cannot access their parents data directly).
$(function(){
Vue.component('custom-element', {
props: ["selected"],
template: '#custom-template'
})
var vm = new Vue({
el: "#app",
data: {
selected: ""
}
})
//get a reference to the select
const sports = document.querySelector("#SportSelector")
//add a listener that updates the Vue data when the select changes
sports.addEventListener("change", evt => vm.selected = evt.target.value)
//initialize Vue with the current selected value
vm.selected = sports.value
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<select id="SportSelector">
<option value="football"> Football </option>
<option value="tennis"> Tennis </option>
</select>
<div id="app">
<custom-element :selected="selected">
</custom-element>
</div>
<script type="text/x-template" id="custom-template">
<div>
<p> {{selected}} </p>
</div>
</script>
You can use portal-vue to accomplish this. It allows you to place the code from your component anywhere in the DOM.
Add it your view instance:
import PortalVue from 'portal-vue';
Vue.use(PortalVue);
In your component you define the content you want to render outside the component:
<portal to="destination">
<p>This slot content will be rendered wherever the <portal-target> with name 'destination'
is located.</p>
</portal>
You can then place this anywhere in the DOM:
<portal-target name="destination">
<!--
This component can be located anywhere in your App.
The slot content of the above portal component will be rendered here.
-->
</portal-target>
Example code from https://github.com/LinusBorg/portal-vue
Unless you have some funky interaction going on with the select, add it to your Vue instance. The Vue instance should encapsulate all your templates and logic for the form.
If you're trying to mix jQuery and Vue on the same element you're probably not going to have a good time.
I'm using VueJS 2.0
Is there any way to make the below render as a link?
Here is my vue component:
<template>
<div v-html="markup"></div>
</template>
<script>
new Vue({
data() {
return {
markup: '<router-link :to="{path: 'https://www.google.com'}"></router-link>',
});
},
});
</script>
In the above example, I want to dynamically export a piece of markup, it contains some dynamic contents, such as router-link like above.
But that content did not compile, and exports a <router-link> tag as a final result.
Any way to make it compile programmatically?
What I really want is to find a way to compile a piece of html manually. If v-html doesn`t work, Is there any other way?
v-html works only for pre-compiled html which is basically generated text.
If you want do dynamically change content, simply use if conditions to render your list view based on prop that will tell you the type of the list view.
I don't think it's a good idea to save the markup in your db. It's rather more convenient to save some settings in your db and based on those to render the necessary html. (the prop type in your case). Maybe if you provide a more concrete example, some suggestions will follow. As you can see, the answers were based on your router-link example which I think is not enough to answer your question
I don't think you can instantiate Vue instances via v-html directive. You must override the default to do that, which would take lots of efforts.
If you just want dynamic links, why not try this:
data: {
menu: []
}
and then :
<router-link v-for="item in menu" :to="item.src">{{item.name}}</router-link>
PS: Can you give an example that you must do such things? I am really interesting in what needs it would be.
Given that you want to render a list of links, one way to do this can be like this:
<template>
<router-link v-for="list in lists" :to="{path: list}"></router-link>
</template>
<script>
new Vue({
data() {
return {
lists: ['https://www.google.com', 'https://www.stackoverflow.com']
});
},
});
</script>
Edit:
You can use an approach like following as well using with the help of dynamic components.
Vue.use(VueRouter)
new Vue({
el: "#app",
data: {
dynamicComp: "router-link"
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/2.2.0/vue-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
somethind
<component :is="dynamicComp" :to="{path: 'https://www.google.com'}"></component>
</div>
Is it possible/viable to use Vue to create components that get instantiated onto custom tags rendered by e.g. a PHP application? Some kind of "custom elements light"?
It "works" if I mount the Vue instance onto the page root element, but - as far as I understand - Vue uses the whole page as a template in this case. And I imagine this could be a performance issue and cause trouble if other javascript code messes with the DOM.
The goal is to progressively replace legacy jQuery code. Is there an common approach for this problem?
Edit: Nesting these components is also a requirement. A simple example would be nested collapsibles.
Edit 2: Some fiddles to illustrate the problem.
A working solution based on riot.js:
https://jsfiddle.net/36xju9sh/
<div id="page">
<!-- This is supposed to show "This is a test." twice. -->
<test>
<test></test>
</test>
</div>
<script type="riot/tag">
<test>
<p>This is a test.</p>
<yield/>
</test>
</script>
<script>riot.mount('*')</script>
Two approaches using Vue.js:
https://jsfiddle.net/89ejjjsy/1/
HTML:
<div id="page">
<!-- This is supposed to show "This is a test." twice. -->
<test>
<test></test>
</test>
</div>
<script type="text/x-template" id="test-template">
<p>This is a test.</p>
<slot></slot>
</script>
Javascript:
Vue.config.ignoreCustomElements = ['test'];
// This won't hit the nested <test> element.
new Vue({
el:'test',
template: '#test-template',
});
// This does, but takes over the whole page.
Vue.component('test', {
template: '#test-template',
});
new Vue({
el: '#page',
});
You do not have to use whole page as Vue instance scope. It is more than ok to for example, use #comments-container as scope for comments component which will be nested somewhere on your page.
You can have multiple VueJS instances on one page.
I want to add component in random position (not random actually, based on some logic) based on click of other elements.
In jQuery, we can achieve that by $('#someId').append('element') or by $('#someId').find('.someClass').after('element')
What I want achieve (jquery version): jsfiddle
How to do that in Vue?
N.B: This couldn't be achieved by v-for as all elements won't appear sequentially or in the same place. Also, components should not be appeared on initialization as this is dynamic.
If this is about arranging that which component will appear after another. Let me assume I have all those components in an array and you can re-arrange those in the array as par your logic, and you can use special attribute: is to render those component.
Here in sample I have an array of components and I use v-for to render those one after another:
See fiddle here.
HTML:
<div id="app">
<h1>
Following are the components
</h1>
<div v-for="comp in compArray" :is="comp"></div>
<br>
<button #click="resuffle">
Resuffle
</button>
</div>
<template id="comp1">
<div>
<span>This is component 1</span>
</div>
</template>
<template id="comp2">
<div>
<span>This is component 2</span>
</div>
</template>
<template id="comp3">
<div>
<span>This is component 3</span>
</div>
</template>
JS:
Vue.component('comp1', {
template: '#comp1',
})
Vue.component('comp2', {
template: '#comp2',
})
Vue.component('comp3', {
template: '#comp3',
})
new Vue({
el: '#app',
data: {
compArray: ['comp1', 'comp2', 'comp3']
},
methods: {
resuffle: function() {
this.compArray.push(this.compArray[1])
this.compArray.splice(1,1)
}
},
})
Can you provide an example of your parent component when you are traversing your array/list to map your element with a component? (just to understand your use case)
You could use v-for but on multiple arrays generated by some computed properties in the place you want to display them (if the places you wanna display components are not also randomly chosen).