How to reference child elements in a component? - vue.js

I'm trying to build a parent-child tree in Vue, and I want to pass the parent's DOM/element ID down to the child so that they can append to it. Here is a simple example of what I am trying to achieve as the DOM structure in the browser:
<div id="my-id">
<p>parent-comp header</p>
<div id="my-id-child">
<p id="my-id-child-content">child-comp content</p>
</div>
<p>parent-comp footer</p>
</div>
The only way I have been able to do this so far is to duplicate the id into another property propid and pass them both down the tree. This seems messy. See my sample code:
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<parent-comp id="my-id" propid="my-id"></parent-comp>
<script type="application/javascript">
Vue.component('parent-comp', {
props: {
propid: String
},
template: ` <div>
<p>parent-comp header</p><child-comp :id="propid + '-child'" :propid="propid + '-child'"></child-comp><p>parent-comp footer</p>
</div>`,
});
Vue.component('child-comp', {
props: {
propid: String
},
template: `<div>
<p :id="propid + '-content'">child-comp content</p>
</div>`,
});
new Vue({
el: '#my-id'
});
</script>
</body>
</html>
I'm guessing there must be a better way to achieve this? It seems like it should be a simple task? Thanks.

There are indeed better ways to refer to specific elements within the current component's template: use refs in the template, which would allow your component script to access them via this.$refs:
<template>
<div>
<button ref="myBtn">Click</button>
</div>
</template>
<script>
export default {
methods: {
doFoo() {
console.log(this.$refs.myBtn)
}
}
}
</script>
The scope isolation is maintained. For example, when there are multiple instances of component A on a page, and one of them gets a reference to its button (via this.$refs.myBtn) to disable it, only that instance's button is disabled. All other A instances are unaffected.

Related

Component inside `transition-group` is rendered twice (duplicated)

Here is a strange issue i am facing.
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<div id="app">
<transition-group name="fadeLeft" tag="ul">
<section key="0">
<testt></testt>
<template v-pre id="myId">
<div>My neighbors: <a v-for="(val,index) in myArray">{{val}}</a></div>
</template>
</section>
</transition-group>
</div>
<script>
Vue.component('testt', {
template: '#myId',
props: {
myArray: {
type: Array,
default: function() {
return ['James', 'Mike'];
}
}
}
})
new Vue({
el: '#app'
})
</script>
transition-group renders the component twice (and the second rendering is done without parsed "{{variable}}").
If you just remove transition-group parent at all, it works as expected and there is no duplicated content. So, definitely the problem seems somewhere there. How to fix that (so, retain transition-group and solve the problem)
Please don't post answers "use component outside of app" or similar offtopic, i described the problem I need to find answer. Also, the aprior is that the template needs to be within transition-group decendants.
The problem:
In the section tack you have the testt tag which was rendered with the parsed HTML and also the template which was rendered as another literal tag (no rendering). And since transition-group elements must be keyed, the template had to be moved out.
The solution:
<script src="https://unpkg.com/vue"></script>
<div id="app">
<transition-group name="fadeLeft" tag="ul">
<section key="0">
<testt></testt>
</section>
</transition-group>
<template v-pre id="myId">
<div>My neighbors: <a v-for="(val,index) in myArray">{{val}} </a></div>
</template>
</div>
<script>
Vue.component('testt', {
template: '#myId',
props: {
myArray : { type:Array, default :function(){ return ['James','Mike'];} }
}
})
new Vue({
el: '#app'
})
</script>

How to bind v-model from input in one div to another div or component

I have my input field in one div, and will have label in another div (as sidebar in my application). I want to update label in sidebar, as I type in input on first div.
I am happy to create second div a component if that's the way. I was reading online, and it was said we could use props to pass data to component. But I am not able to link input field to component. Please find my code as below:
var app = new Vue({
el: '#div1',
data: {
message: ''
}
})
Vue.component('testp', {
props: ['message'],
template: '<p>Message is: {{ message }}</p>'
})
var div2 = new Vue({
el: '#div2'
});
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="div1">
<input v-model="message" placeholder="edit me">
</div>
<div id="div2">
<testp></testp>
</div>
</body>
</html>
As Pointed in Comment You have no reason to have two separate Vue instance and the First Answer is correct. But in some cases where you really need to have multiple Vue instances, you can actually use them in the following manner.
var app = new Vue({
el: '#div1',
data: {
message: ''
}
})
Vue.component('testp', {
props: ['message'],
template: '<p>Message is: {{ message }}</p>'
})
var div2 = new Vue({
el: '#div2',
computed: {
newMessage() {
return app.message;
}
},
});
Html
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="div1">
<input v-model="message" placeholder="edit me">
</div>
<div id="div2">
<testp :message="newMessage"></testp>
</div>
</body>
</html>
Please observe the computed value newMessage is actually getting its value form a different Vue instance (app) and it is also reactive. Therefore whenever the value in first Vue instance changes, it is updated in another Vue instance.
Codepen: https://codepen.io/ashwinbande/pen/xMgQQz
Like I have pointed out in my comments, there is no reason for you to use two separate Vue instances. What you can do is simply wrap everything within an app container, e.g. <div id="#app">, and then instantiate your VueJS instance on that element instead.
Then, you can use v-bind:message="message" on the <testp> component to pass in the message from the parent. In this sense #div1 and #div2 are used entirely for markup/decorative purposes and are not used as VueJS app containers in your code.
Vue.component('testp', {
props: ['message'],
template: '<p>Message is: {{ message }}</p>'
});
var app = new Vue({
el: '#app',
data: {
message: ''
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div id="div1">
<input v-model="message" placeholder="edit me">
</div>
<div id="div2">
<testp v-bind:message="message"></testp>
</div>
</div>

Using components without npm inside existing web page

I'm new to Vue.js and also do most work in a conventional LAMP environment.
The vue components tried so far don't seem to work linked in. Can anyone please:
Provide a sample process to install vue components in a legacy LAMP environment
Provide a simple html template showing how to load a vue component
Thanks for any tips.
Since you are in a legacy context, you probably won't use npm/webpack/babel. In this case, you'll import every package you need via <script> tags.
Process:
Look for the component you need
Check their docs for the steps needed to import them.
Usually it is a <script> tag (and a CSS <link> style) followed by some steps to configure (but not always).
In some rare cases the lib doesn't provide instructions for usage via <script>, in this case you can try using <script src="https://unkpg.com/NODE-PACKAGE-NAME"> and then see if it is possible to use it directly.
Examples:
Declaring your own <custom-comp> component and registering it globally via Vue.component.
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<custom-comp v-bind:myname="name"></custom-comp>
</div>
<template id="cc">
<p>I am the custom component. You handled me {{ myname }} via props. I already had {{ myown }}.</p>
</template>
<script>
Vue.component('custom-comp', {
template: '#cc',
props: ['myname'],
data() {
return {
myown: 'Eve'
}
}
});
new Vue({
el: '#app',
data: {
message: 'Hello, Vue.js',
name: 'Alice'
}
});
</script>
Using a third-party component that gives instructions on how to use without NPM. Example bootstrap-vue. How to use? Follow their instructions for each specific component. Demo of the Card Component below.
<script src="https://unpkg.com/vue"></script>
<!-- Add this to <head> -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css"/>
<!-- Add this after vue.js -->
<script src="//unpkg.com/babel-polyfill#latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.js"></script>
<div id="app">
<div>
<b-card title="Card Title"
img-src="https://lorempixel.com/600/300/food/5/"
img-alt="Image"
img-top
tag="article"
style="max-width: 20rem;"
class="mb-2">
<p class="card-text">
Some quick example text to build on the card title.
</p>
<b-button href="#" variant="primary">Go somewhere</b-button>
</b-card>
</div>
</div>
<script>
new Vue({
el: '#app'
});
</script>
Finally, using a third-party component that doesn't show any specific instructon on how to use without NPM. In the demo below we see the vue2-datepicker. They don't give specific instructions on how to use via <script>, but by looking at their readme, we see their component typically exports a DatePicker variable. Use then use <script src="https://unpkg.com/vue2-datepicker"> to load the component and register it for use via Vue.component('date-picker', DatePicker.default);. The need for .default varies. For other components, Vue.component('comp-name', ComponentName); (instead of ComponentName.default) directly could work.
// After importing the <script> tag, you use this command to register the component
// so you can use. Sometimes the components auto-register and this is not needed
// (but generally when this happens, they tell in their docs). Sometimes you need
// to add `.default` as we do below. It's a matter of trying the possibilities out.
Vue.component('date-picker', DatePicker.default);
new Vue({
el: '#app',
data() {
return {
time1: '',
time2: '',
shortcuts: [
{
text: 'Today',
start: new Date(),
end: new Date()
}
]
}
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue2-datepicker"></script>
<div id="app">
<div>
<date-picker v-model="time1" :first-day-of-week="1" lang="en"></date-picker>
<date-picker v-model="time2" range :shortcuts="shortcuts" lang="en"></date-picker>
</div>
</div>

Vue.js - Render issue

I'm trying to create a component, that can show/hide on click, similar to an accordion.
I have the following error and I don't know why:
[Vue warn]: Property or method "is_open" is not defined on the
instance but referenced during render. Make sure to declare reactive
data properties in the data option. (found in root instance)
<div id="app">
<div is="m-panel" v-show="is_open"></div>
<div is="m-panel" v-show="is_open"></div>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="comp_a.js" ></script>
<!--<script src="app.js" ></script>-->
</html>
Vue.component('m-panel', {
data: function() {
return {
is_open: true
}
},
template: '<p>Lorem Ipsum</p>'
})
new Vue({
el:'#app',
})
Your code seems a little confused, your is_open is in your component but you are trying to access it in the parent. You just need to make sure this logic is contained inside your component. The easiest way is to simply place an event on the relevant element in your component template:
<template>
<div>
<!-- Toggle when button is clicked-->
<button #click="is_open=!is_open">
Open Me!
</button>
<span v-show="is_open">
I'm Open!
</span>
</div>
</template>
Here's the JSFiddle: https://jsfiddle.net/ytw22k3w/
Because u used is_open property in '#app instance' but u didnt declare in it,u decladed in 'm-panel component' which has no relation with it.Try something like this can avoid it.
new Vue({
el:'#app',
data:{
is_open:''
}
})

Vue dynamic component event bindings

I have a dynamic component that is resolved and bound on runtime using the documented dynamic component syntax;
<div class="field">
<component v-if="component" :is="component" #valueUpdated="onUpdate($event)"></component>
</div>
Decided using a prop assigned to on mounting.
For whatever reason, when the child component, rendered dynamically, emits an event this.$emit('eventName', /*someData*/) the parent does not seem to be able to hear it. Is the approach used in standard components applicable to those rendered dynamically? Props seem to work, so maybe I am not doing something correctly?
yeah you can use props with dynamic components it's just you need to use lowercase hyphenated (kebab-case) names for the html attribute, i.e.
<component v-if="component" :is="component" #value-updated="onUpdate"></component>
Vue.component('foo', {
template: `
<div>
<button #click="$emit('value-updated', { bar: 'baz' })">pass data</button
</div>
`
});
new Vue({
el: '#app',
data: {
currentComponent: 'foo',
output: {},
},
methods: {
onUpdate (payload) {
this.output = payload
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<component v-if="currentComponent" :is="currentComponent" #value-updated="onUpdate"></component>
<pre>{{ output }}</pre>
</div>