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.
Related
I'm quite new in Vue.js. I do some simple exercises about communication between Vue components. But I still have a problem who is a child and who is a parent. For example I have this code:
HTML:
<body>
<div id="root" class="component">
<coupon #applied="onCouponApplied"></coupon>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.css"
/>
<!-- <script src="app.js"></script> -->
<script src="main.js"></script>
</body>
VUE.JS:
Vue.component("coupon", {
template: `
<div>
<input palceholder="Put your name" #blur="onCouponApplied"/>
</div>
`,
data() {
return {
message: ""
};
},
methods: {
onCouponApplied() {
this.$emit("applied");
}
}
});
new Vue({
el: "#root",
methods: {
onCouponApplied() {
alert("that's work!");
}
}
});
So.. here I have component coupon and new Vue. I guess that the new Vue is a parent. But... I try to understand, how it is work. Can anyone explain to me very simply how it works? I read the vue.js documentation, but still have a problem :/
All components refer to the Vue instance you mentioned. So your coupon component is a child of your root div. The parent component is the component that embeds another component.
A "UserListView" would have a list component which embeds user components. The view is the parent of the list and the list could be the parent of the user components.
Your whole app consists of components. Every Vue app has at least one component that is the parent component (Vue instance).
Every other component that you make becomes either direct or indirect child of the Vue instance.
From official docs:
A Vue application consists of a root Vue instance created with new
Vue, optionally organized into a tree of nested, reusable components.
The root div is the parent. You are creating a new Vue instance ( new Vue({ ) and associating it with the div element tagged with an id of 'root' ( el: "#root", ). The coupon component is contained within this div element, making it a child.
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
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>
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).
Update
Apparently when using <template> the reading of innerHTML will return all attributes in lower case. Angular2 will not understand ngfor or ngif as of this beta 9 version and throws error. <script> is treated as a text fragment rather than DOM, which means attributes stay as they are.
Here:
https://groups.google.com/forum/#!topic/angular/yz-XdYV2vYw
Originial
Taking the following html and angular2 beta 9 component:
HTML CODE
<my-page>Loading...</my-page>
<script type="text/html" id="my-component-template1">
<select [(ngModel)]="SelectedType">
<option *ngFor="#someType of MyTypes" [selected]="SelectedType == someType" [value]="someType">{{someType}}</option>
</select>
</script>
<template id="my-component-template2">
<select [(ngModel)]="SelectedType">
<option *ngFor="#someType of MyTypes" [selected]="SelectedType == someType" [value]="someType">{{someType}}</option>
</select>
</template>
JS CODE
var myComponent =
ng.core.Component({
selector: 'my-page',
//complains if i use #my-component-template2
template: document.querySelector('#my-component-template1').innerHTML
})
.Class({
constructor: function () {
var self = this;
self.MyTypes = ['first', 'second'];
self.SelectedType = self.MyTypes[0];
}
});
document.addEventListener('DOMContentLoaded', function () {
ng.platform.browser.bootstrap(myComponent);
});
If i use my-component-template1 it works fine, but if i choose my-component-template2 it complains that ngModel and ngForOf are not a known native properties.
I tested a div as a template and apparently that won't work either with the same errors. So question is, why is it breaking if the template is part of the DOM? Furthermore, I really don't want to use the script text/html hack. Assuming this is why <template> was added in html5 specification. Why is this happening and how can i fix it?
The <template> tag is only used by Angular 2 structural directives (like the built-in ngIf, ngFor, ngSwitch, etc) - its use is somehow similar with the html5 specification since it defines content which is stored for subsequent use.
The * in front of a structural directive is just syntactic sugar which allows us to skip the <template> tag and focus directly on the HTML element that we are including, excluding, or repeating - you can read more about it here.
An example from Angular 2 docs which showcases this:
<!-- Examples (A) and (B) are the same -->
<!-- (A) *ngIf paragraph -->
<p *ngIf="condition">
Our heroes are true!
</p>
<!-- (B) [ngIf] with template -->
<template [ngIf]="condition">
<p>
Our heroes are true!
</p>
</template>
At the moment, I'm not sure if there's a way of defining inline Angular 2 HTML templates like there's in AngularJS 1. Your hack, as you put it, seems to do its job.
Angular handles <template> tags itself and doesn't simply add them to the DOM. If you inject TemplateRef into the constructor of your component you should get a reference to the template.
class MyComponent {
constructor(private tmplRef:TemplateRef) {
}
}