Pass data from child to parent in Vuejs (is it so complicated?) - vue.js

I have read about it:
vuejs update parent data from child component
https://forum.vuejs.org/t/passing-data-back-to-parent/1201/2
The concept is the same, I need to pass a data object from child to parent. I have used $emit to pass data to parent component but it doesn't works. Do you know what is wrong? You can check my code here:
Vue.component('list-products', {
delimiters: ['[[', ']]'],
template: '#list-products-template',
props: ['products'],
data: function () {
return {
productSelected: {}
}
},
methods: {
showDetailModal: function (product) {
console.log('click product in child, how can i pass this product to productSelected data in parent?');
console.log(product);
this.productSelected = product;
this.$emit('clickedShowDetailModal', product);
}
}
});
var app = new Vue({
delimiters: ['[[', ']]'],
el: '#resultComponent',
data: {
listProducts: [
{'name':'test1',id:1},
{'name':'test2',id:2},
{'name':'test3',id:3}
],
productSelected: {}
},
methods: {
clickedShowDetailModal: function (value) {
console.log('value');
console.log(value);
this.productSelected = value;
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="resultComponent" data-toggler=".small-up-2" class="row small-up-1">
<list-products :products="listProducts"></list-products>
</div>
<script type="text/x-template" id="list-products-template">
<div>
<div class="column column-block" v-for="(product, index) in products" :product="product" :index="index" :key="product.id">
<li class="more-benefits">
<a #click="showDetailModal(product)">Click me [[ product.name ]] and check console.log »</a>
</li>
</div>
</div>
</script>

Props are for parent -> child
You can use $emit for child -> parent
v-on directive captures the child components events that is emitted by $emit
Child component triggers clicked event :
export default {
methods: {
onClickButton (event) {
this.$emit('clicked', 'someValue')
}
}
}
Parent component receive clicked event:
<div>
<child #clicked="onClickChild"></child>
</div>
...
export default {
methods: {
onClickChild (value) {
console.log(value) // someValue
}
}
}

You aren't listening to the event. I changed the event name to clicked-show-detail. Try this.
In the showDetailModal method of your component.
this.$emit('clicked-show-detail', product);
In your Vue.
<list-products :products="listProducts" #clicked-show-detail="clickedShowDetailModal"></list-products>
Example.

Nightmare to find "hello world" example out there for $emit so I added the example below (Minimal lines of code + semantic names of functions).
"Hello world" On click change parent data
Vue.component('child', {
template: `
<div class="child">
<button v-on:click="childMethod">CLICK - child Method pass data from product component</button>
</div>
`,
data: function () {
return {
child_msg: "message from child"
}
},
methods: {
childMethod: function() {
this.$emit('child-method', this.child_msg)
}
}
})
var app = new Vue({
el: '#app',
data: {
msg: "I am the blue parent!!!!!!!!!!!!!!!!!!",
},
methods: {
updateParent(value_from_child) {
this.msg = value_from_child;
alert("hello child" + value_from_child)
}
}
})
.child{ background: gray; padding: 15px; }
button{ cursor: pointer; }
#app{ border: 1px red dashed; padding: 15px; background: lightblue; color: blue;
}
<div id="app">
<p>{{msg}}</p>
<!-- ###### The trick happens her ###### -->
<child class="child" v-on:child-method="updateParent"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
codepen: https://codepen.io/ezra_siton/pen/YzyXNox?editors=1010

Related

Vuejs v-on click doesn't work inside component

I use VueJs and I create the following component with it.
var ComponentTest = {
props: ['list', 'symbole'],
data: function(){
return {
regexSymbole: new RegExp(this.symbole),
}
},
template: `
<div>
<ul>
<li v-for="item in list"
v-html="replaceSymbole(item.name)">
</li>
</ul>
</div>
`,
methods: {
replaceSymbole: function(name){
return name.replace(this.regexSymbole, '<span v-on:click="test">---</span>');
},
test: function(event){
console.log('Test ...');
console.log(this.$el);
},
}
};
var app = new Vue({
el: '#app',
components: {
'component-test': ComponentTest,
},
data: {
list: [{"id":1,"name":"# name1"},{"id":2,"name":"# name2"},{"id":3,"name":"# name3"}],
symbole: '#'
},
});
and this my html code
<div id="app">
<component-test :list="list" :symbole="symbole"></component-test>
</div>
When I click on the "span" tag inside "li" tag, nothing append.
I don't have any warnings and any errors.
How I can call my component method "test" when I click in the "span" tag.
How implement click event for this case.
You cannot use vue directives in strings that you feed to v-html. They are not interpreted, and instead end up as actual attributes. You have several options:
Prepare your data better, so you can use normal templates. You would, for example, prepare your data as an object: { linkText: '---', position: 'before', name: 'name1' }, then render it based on position. I think this is by far the nicest solution.
<template>
<div>
<ul>
<li v-for="(item, index) in preparedList" :key="index">
<template v-if="item.position === 'before'">
<span v-on:click="test">{{ item.linkText }}</span>
{{ item.name }}
</template>
<template v-else-if="item.position === 'after'">
{{ item.name }}
<span v-on:click="test">{{ item.linkText }}</span>
</template>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["list", "symbole"],
computed: {
preparedList() {
return this.list.map(item => this.replaceSymbole(item.name));
}
},
methods: {
replaceSymbole: function(question) {
if (question.indexOf("#") === 0) {
return {
linkText: "---",
position: "before",
name: question.replace("#", "").trim()
};
} else {
return {
linkText: "---",
position: "after",
name: question.replace("#", "").trim()
};
}
},
test: function(event) {
console.log("Test ...");
console.log(this.$el);
}
}
};
</script>
You can put the click handler on the surrounding li, and filter the event. The first argument to your click handler is the MouseEvent that was fired.
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id" v-on:click="clickHandler"
v-html="replaceSymbole(item.name)">
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["list", "symbole"],
data() {
return {
regexSymbole: new RegExp(this.symbole)
};
},
computed: {
preparedList() {
return this.list.map(item => this.replaceSymbole(item.name));
}
},
methods: {
replaceSymbole: function(name) {
return name.replace(
this.regexSymbole,
'<span class="clickable-area">---</span>'
);
},
test: function(event) {
console.log("Test ...");
console.log(this.$el);
},
clickHandler(event) {
const classes = event.srcElement.className.split(" ");
// Not something you do not want to trigger the event on
if (classes.indexOf("clickable-area") === -1) {
return;
}
// Here we can call test
this.test(event);
}
}
};
</script>
Your last option is to manually add event handlers to your spans. I do not!!! recommend this. You must also remove these event handlers when you destroy the component or when the list changes, or you will create a memory leak.

vue component to use common functions

I'm looking to get a handle on the Vue CLI3 project system. Currently refactoring a long single html file of in-line vue into real '.vue' components. One goal is to use some common functions among my vue components for various things.
In my common-functions.js file I've got something like this:
function capitalize(str) {
return str[0].toUpperCase() + str.substr(1, );
};
And in my HelloWorld.vue file I've got this and it's not working through many various attempts. All searches I find seem to be dealing with other things, surely there's an easy way to just use some common functions, right??
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<ul>
<li v-for='c in categoryNames'>{{ c }}</li>
</ul>
</div>
</template>
<script>
require('../js/common-functions.js');
export default {
name: 'HelloWorld',
data () {
return {
msg: capitalize('welcome to Your Vue.js App!'),
categoryNames : this.$root.categoryNames
}
}
}
</script>
Of course the message is:
[Vue warn]: Error in data(): "ReferenceError: capitalize is not defined"
found in
---> <HelloWorld> at src/components/HelloWorld.vue
<App> at src/App.vue
<Root>
At the end of common-functions.js, export the function:
export default capitalize;
And in the HelloWorld.vue, import it with:
import capitalize from '../js/common-functions.js';
// this should replace the require line
One Solution:
Register your global functions to Vue.prototype by Vue.use().
Like below demo:
let myGlobalAPIGroup1 = { // API Group 1
install: function (_Vue) {
if(!_Vue.prototype.$apiGroup1) {
_Vue.prototype.$apiGroup1 = {}
}
_Vue.prototype.$apiGroup1.capitalize = function (str) {
return str[0].toUpperCase() + str.substr(1, );
}
}
}
let myGlobalAPIGroup2 = { // API Group 2
install: function (_Vue) {
if(!_Vue.prototype.$apiGroup2) {
_Vue.prototype.$apiGroup2 = {}
}
_Vue.prototype.$apiGroup2.capitalize = function (str) {
return str[0].toUpperCase() + str.substr(1, ) + '#';
}
}
}
Vue.use(myGlobalAPIGroup1) //register
Vue.use(myGlobalAPIGroup2) //register
new Vue({
el: '#app',
data() {
return {
testValues: ['label a', 'label b'],
}
},
methods:{
testAPI1: function(item) {
return this.$apiGroup1.capitalize(item)
},
testAPI2: function(item) {
return this.$apiGroup2.capitalize(item)
}
}
})
#app > div {
display: inline-block;
margin-left: 5px;
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<h3>Group 1:</h3>
<p v-for="(item, index) in testValues" :key="index">{{testAPI1(item)}}</p>
</div>
<div>
<h3>Group 2:</h3>
<p v-for="(item, index) in testValues" :key="index">{{testAPI2(item)}}</p>
</div>
</div>

VueJS: #click.native.stop = "" possible?

I have several nested components on the page with parents component having #click.native implementation. Therefore when I click on the area occupied by a child component (living inside parent), both click actions executed (parent and all nested children) for example
<products>
<product-details>
<slide-show>
<media-manager>
<modal-dialog>
<product-details>
<slide-show>
<media-manager>
<modal-dialog>
</products>
So I have a list of multiple products, and when I click on "canvas" belonging to modal dialog - I also get #click.native fired on product-details to which modal-dialog belongs. Would be nice to have something like #click.native.stop="code", is this possible?
Right now I have to do this:
#click.native="clickHandler"
and then
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
code
<template>
<div class="media-manager">
<div v-if="!getMedia">
<h1>When you're ready please upload a new image</h1>
<a href="#"
class="btn btn--diagonal btn--orange"
#click="upload=true">Upload Here</a>
</div>
<img :src="getMedia.media_url"
#click="upload=true"
v-if="getMedia">
<br>
<a class="arrow-btn"
#click="upload=true"
v-if="getMedia">Add more images</a>
<!-- use the modal component, pass in the prop -->
<ModalDialog
v-if="upload"
#click.native="clickHandler"
#close="upload=false">
<h3 slot="header">Upload Images</h3>
<p slot="body">Hello World</p>
</ModalDialog>
</div>
</template>
<script>
import ModalDialog from '#/components/common/ModalDialog';
export default {
components: {
ModalDialog,
},
props: {
files: {
default: () => [],
type: Array,
},
},
data() {
return {
upload: false,
}
},
computed: {
/**
* Obtain single image from the media array
*/
getMedia() {
const [
media,
] = this.files;
return media;
},
},
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
}
};
</script>
<style lang="scss" scoped>
.media-manager img {
max-width: 100%;
height: auto;
}
a {
cursor: pointer;
}
</style>
Did you check the manual? https://v2.vuejs.org/v2/guide/events.html
There is #click.stop="" or #click.stop.prevent=""
So you don't need to use this
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
}
In the Vue, modifiers can be chained. So, you are free to use modifiers like this:
#click.native.prevent or #click.stop.prevent
<my-component #click.native.prevent="doSomething"></my-component>
Check events
I had the same problem. I fixed the issue by using following:
<MyComponent #click.native.prevent="myFunction(params)" />

VueJS 2 update contenteditable in component from parent method

I have editable element updated by component method, but i have also json import and i want to update element my parent method. I can update model, but editable element doesn´t bind it. If i insert content to component template, it will bind updated model, but then i can´t really edit it.
Here´s my example: https://jsfiddle.net/kuwf9auc/1/
Vue.component('editable', {
template: '<div contenteditable="true" #input="update"></div>', /* if i insert {{content}} into this div, it wil update, but editing behave weird */
props: ['content'],
mounted: function () {
this.$el.innerText = this.content;
},
methods: {
update: function (event) {
console.log(this.content);
console.log(event.target.innerText);
this.$emit('update', event.target.innerText);
}
}
})
var app = new Vue({
el: '#myapp',
data: {
herobanner: {
headline: 'I can be edited by typing, but not updated with JSON upload.'
}
},
methods: {
uploadJSON: function (event) {
var input = event.target;
input.src = URL.createObjectURL(event.target.files[0]);
var data = input.src;
$.get(data, function(data) {
importdata = $.parseJSON(data);
this.$data.herobanner = importdata.herobanner;
}.bind(this));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<main id="myapp" class="container-fluid">
<input type="file" id="file" name="file" #change="uploadJSON" style="display: none; width: 1px; height: 1px"/>
<a href="" onclick="document.getElementById('file').click(); return false" title="Import settings from JSON file">
upload JSON
</a>
<h1>
<editable :content="herobanner.headline" #update="herobanner.headline = $event"></editable>
</h1>
Real value of model:
<br>
<h2>{{herobanner.headline}}</h2>
</main>
Working example:
Vue.component('editable', {
template: `
<div contenteditable="true" #blur="emitChange">
{{ content }}
</div>
`,
props: ['content'],
methods: {
emitChange (ev) {
this.$emit('update', ev.target.textContent)
}
}
})
new Vue({
el: '#app',
data: {
herobanner: {
headline: 'Parent is updated on blur event, so click outside this text to update it.'
}
},
methods: {
async loadJson () {
var response = await fetch('https://swapi.co/api/people/1')
var hero = await response.json()
this.herobanner.headline = hero.name
},
updateHeadline (content) {
this.herobanner.headline = content
}
}
})
<main id="app">
<button #click="loadJson">Load JSON data</button>
<h1>
<editable
:content="herobanner.headline"
v-on:update="updateHeadline"
>
</editable>
</h1>
<h2>{{herobanner.headline}}</h2>
</main>
<script src="https://unpkg.com/vue#2.5.3/dist/vue.min.js"></script>

VueJS Not emit message to parent

I don't have much knowledge about child and parent component and i am just simply trying to change value in child then emit then value to parent so i can show somewhere, But it looks like not working, Not emiting the value.
This should change to : Hello from child
{{ message }} From Parent
Can anyone look this code and tell me what is mistake ?
Vue.component('child1', {
template: '<p #click="runMe">{{ display }}</p>',
props: ['display'],
data: {
display: ''
},
methods: {
runMe() {
this.display = "Hello from child"
this.$emit("changeMessage", this.display)
}
}
})
new Vue({
el: "#app",
data: {
message: "Hello 2"
},
methods: {
messageRun() {
this.message = "Change By"
}
}
})
.btnMain {
display: block;
background: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.2/vue.min.js"></script>
<div id="app">
<button #click="messageRun" class="btnMain">Click Here</button>
<child1 :display="message" #changeMessage="message = $event"></child1>
<hr>
{{ message }} From Parent
</div>
In your code you have this where you emit:
this.$emit("changeMessage", this.display)
Change to :
this.$emit("newmessage", this.display)
I mean use lower case single word, if you use camelCase vue convert it to change-message but this is not acceptable by attribute, I tried this on your code.
After change this do this :
<child1 :display="message" #newmessage="message = $event"></child1>