vuejs - remount after update background url - javascript

I have a vue component with the binding style:
<div id="profile-icon" :style="{'background': 'url('+profile.icon+')'}"></div>
<input name="name" v-model="profile.name" type="text" placeholder="Name">
The image will only display if I update the profile.icon under beforeMount function:
props: ['profileprops'],
data: function() {
return {
profile: {
address:'',
name:'Name',
description:'Short description',
address:'address',
user_id:'',
background:'/images/admin/bg.png',
icon:''
},
}
},
beforeMount: function() {
console.log('profileedit mounted.')
var self = this;
if(self.profileprops){
self.profile = self.profileprops;
};
if(!self.profile.icon){
self.profile.icon = '/images/admin/icon-144.png';
},
mounted: function() {
var self = this;
self.$eventHub.$on('ImageSelected', (imagelink) => {///<-- I have another vue component for selecting images to emit event and to catch it here.
self.profile.icon = imagelink;
$('#profilemodal').modal('hide');
});
},
watch: {
'profile': {
handler: function(newVal, oldVal) {
console.log("changed")
},
deep: true
},
}
I did a few tests:
if I select the image, I can see the console.log within the $on has given the correct data from profile.icon. However, the :style did not update the background image with the new profile.icon. And watch.profile did not react at all.
If I start to type on input with v-model bind with profile.name, everything update immediately, including the background image in :style.
I believe I must have miss something to sync the inside and outside of the $on. How can I resolve this?

I found the solution here.
So I just $forceUpdate() inside $on:
self.$eventHub.$on('ImageSelected', (imagelink) => {
self.profile.icon = imagelink;
self.$forceUpdate();//<-- this does the magic.
});
then the data will update and re-render.

Related

Vuejs: Passing SAVE function into CRUD component

I am struggeling with a proper solution which requires an advanced parent-child communication in vuejs. There can be many different parent components which has a logic how to save data. From the other side there will be only one child component which has a list of elements and a form to create new elements but it doesn't know how to save the data.
The question is: Is there any other way (better approach) to have the same functionality but to get rid of this.$refs.child links. For example I am wondering if I can just pass a function (SaveParent1(...) or SaveParent2(...)) to the child component. But the problem is the function contains some parent's variables which won't be available in child context and those variables could be changed during the runtime.
Just few clarifications:
The methods SaveParent1 and SaveParent2 in real life return
Promise (axios).
The child-component is like a CRUD which is used
everywhere else.
At the moment the communication looks like that: CHILD -event-> PARENT -ref-> CHILD.
Bellow is the example:
<div id="app">
<h2>😀Advanced Parent-Child Communication:</h2>
<parent-component1 param1="ABC"></parent-component1>
<parent-component2 param2="XYZ"></parent-component2>
</div>
Vue.component('parent-component1', {
props: { param1: { type: String, required: true } },
methods: {
onChildSubmit(p) {
// Here will be some logic to save the param. Many different parents might have different logic and all of them use the same child component. So child-component contains list, form and validation message but does not know how to save the param to the database.
var error = SaveParent1({ form: { p: p, param1: this.param1 } });
if (error)
this.$refs.child.paramFailed(error);
else
this.$refs.child.paramAdded(p);
}
},
template: `<div class="parent"><p>Here is parent ONE:</p><child-component ref="child" #submit="onChildSubmit"></child-component></div>`
});
Vue.component('parent-component2', {
props: { param2: { type: String, required: true } },
methods: {
onChildSubmit(p) {
// Here is a different logic to save the param. In prictice it is gonna be different requests to the server.
var error = SaveParent2({ form: { p: p, param2: this.param2 } });
if (error)
this.$refs.child.paramFailed(error);
else
this.$refs.child.paramAdded(p);
}
},
template: `<div class="parent"><p>Here is parent TWO:</p><child-component ref="child" #submit="onChildSubmit"></child-component></div>`
});
Vue.component('child-component', {
data() {
return {
currentParam: "",
allParams: [],
errorMessage: ""
}
},
methods: {
submit() {
this.errorMessage = "";
this.$emit('submit', this.currentParam);
},
paramAdded(p) {
this.currentParam = "";
this.allParams.push(p);
},
paramFailed(msg) {
this.errorMessage = msg;
}
},
template: `<div><ol><li v-for="p in allParams">{{p}}</li></ol><label>Add Param: <input v-model="currentParam"></label><button #click="submit" :disabled="!currentParam">Submit</button><p class="error">{{errorMessage}}</p></div>`
});
function SaveParent1(data) {
// Axios API to save data. Bellow is a simulation.
if (Math.random() > 0.5)
return null;
else
return 'Parent1: You are not lucky today';
}
function SaveParent2(data) {
// Axios API to save data. Bellow is a simulation.
if (Math.random() > 0.5)
return null;
else
return 'Parent2: You are not lucky today';
}
new Vue({
el: "#app"
});
There is also a live demo available: https://jsfiddle.net/FairKing/novdmcxp/
Architecturally I recommend having a service that is completely abstract from the component hierarchy and that you can inject and use in each of the components. With this kind of component hierarchy and architecture it is easy to run into these issues. It is important to abstract as much functionality and business logic from the components as possible. I think of components in these modern frameworks just merely as HTML templates on steroids, which should at most act as controllers, keeping them as dumb and as thin as possible so that you don't run into these situations. I do not know vue.js so I cannot give you the technical solution but hope this indication helps
I think I have found a solution. So no two ways communication. I can just pass a method and the child will do everything without communicating with parent. I am happy with that I am marking it as an answer. Thanks everyone for your help.
Let me please know what do you think guys.
Bellow is my solution:
<div id="app">
<h2>😀Advanced Parent-Child Communication:</h2>
<parent-component1 param1="ABC"></parent-component1>
<parent-component2 param2="XYZ"></parent-component2>
</div>
Vue.component('parent-component1', {
props: { param1: { type: String, required: true } },
computed: {
saveFunc() {
return function(p) { SaveParent1({ form: { p: p, param1: this.param1 } }); }.bind(this);
}
},
template: `<div class="parent"><p>Here is parent ONE:</p><child-component :saveFunc="saveFunc"></child-component></div>`
});
Vue.component('parent-component2', {
props: { param2: { type: String, required: true } },
computed: {
saveFunc() {
return function(p) { SaveParent2({ form: { p: p, param2: this.param2 } }); }.bind(this);
}
},
template: `<div class="parent"><p>Here is parent TWO:</p><child-component :saveFunc="saveFunc"></child-component></div>`
});
Vue.component('child-component', {
props: {
saveFunc: { type: Function, required: true }, // This is gonna be a Promise in real life.
},
data() {
return {
currentParam: "",
allParams: [],
errorMessage: ""
}
},
methods: {
submit() {
this.errorMessage = "";
var error = this.saveFunc(this.currentParam);
if (error)
this.paramFailed(error);
else
this.paramAdded(this.currentParam);
},
paramAdded(p) {
this.currentParam = "";
this.allParams.push(p);
},
paramFailed(msg) {
this.errorMessage = msg;
}
},
template: `<div><ol><li v-for="p in allParams">{{p}}</li></ol><label>Add Param: <input v-model="currentParam"></label><button #click="submit" :disabled="!currentParam">Submit</button><p class="error">{{errorMessage}}</p></div>`
});
function SaveParent1(data) {
console.log(data);
// Axios API to save data
if (Math.random() > 0.5)
return null;
else
return 'Parent1: You are not lucky today';
}
function SaveParent2(data) {
console.log(data);
// Axios API to save data
if (Math.random() > 0.5)
return null;
else
return 'Parent2: You are not lucky today';
}
new Vue({
el: "#app"
});
The demo link: https://jsfiddle.net/FairKing/novdmcxp/126/

Vue- variables inside event bus are not reactive

I created global event bus
Vue.prototype.$eventHub = new Vue()
In 'component 2' clicking on the button, emitted the event then navigating to 'component 1'
this.$eventHub.$emit('deleted')
In another 'component 1' i am trying to use it
data: function () {
return {
testVariable : true
}
},
beforeMount () {
var self = this
this.$eventHub.$on('deleted', function () {
console.log("executed")
self.testVariable = false
})
}
In the template section i used {{testVariable}}, it's always displaying true. Why it is not reactive? what did i miss?

Vue render button

My question is about render a button on vue instance, to click in a button and then it render another button with event click, If I simple mount the button it dont get the function tes.
const Hello = {
props: ['text'],
template: '<button v-on:click="tes"> </button> ',
};
new Vue({
el: '#app',
data: {
message: 'Click me'
},
methods:{
alertar: function(event){
const HelloCtor = Vue.extend(Hello);
var instance = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
instance.$mount() // pass nothing
this.appendChild(instance.$el)
},
tes: function(){
alert('Teste');
}
}
})
Erro :
vue.js:597 [Vue warn]: Invalid handler for event "click": got undefined
(found in <Root>)
warn # vue.js:597
(index):52 Uncaught TypeError: this.appendChild is not a function
at Vue.alertar ((index):52)
at invoker (vue.js:2029)
at HTMLParagraphElement.fn._withTask.fn._withTas
The problem is that you create a child component inside of your parent Vue that contains the template with the binding to the tes function. That means that the child will look in its own methods for tes, however it is a property of your parent, not of the child itself so it will never be able to find it in its own scope. You have to add the function to the child component instead:
const Hello = {
props: ['text'],
template: '<button v-on:click="tes"> </button> ',
methods: {
tes: function(){
alert('Teste');
}
}
};
Just expanding #Philip answer
Basically you can't access parent methods in programatically created components.
You need to specify the methods inside the child components.
const Hello = {
props: ['text'],
template: '<button v-on:click="this.tes"> Vue Generated</button> ',
methods: {
tes: function(){
alert('Teste');
}}
};
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
mounted(){
this.alertar()
},
methods:{
alertar: function(event){
const HelloCtor = Vue.extend(Hello);
var instance = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
instance.$mount() // pass nothing
this.$refs.container.appendChild(instance.$el)
},
tes: function(){
alert('Teste');
}
}
})
Check this fiddle here
https://jsfiddle.net/50wL7mdz/370645/
However in some cases you may be able to access the parent components methods using
$parent directive which I believe will not work when components is created programatically.

Vue watcher executed before the new data is bound?

I am using this code:
var vueApp = new Vue({
el: '#app',
data: {
modalKanji: {}
},
methods: {
showModalKanji(character) {
sendAjax('GET', '/api/Dictionary/GetKanji?character=' + character, function (res) { vueApp.modalKanji = JSON.parse(res); });
}
},
watch: {
'modalKanji': function (newData) {
setTimeout(function () {
uglipop({
class: 'modalKanji', //styling class for Modal
source: 'div',
content: 'divModalKanji'
});
}, 1000);
}
}
});
and I have an element that when clicked on, displays a popup with the kanji data inside:
<span #click="showModalKanji(kebChar)" style="cursor:pointer;>
{{kebChar}}
</span>
<div id="divModalKanji" style='display:none;'>
<div v-if="typeof(modalKanji.Result) !== 'undefined'">
{{ modalKanji.Result.literal }}
</div>
</div>
It works, but only when used with a setTimeout delay to "let the time for Vue to update its model"...if I remove the setTimeout so the code is called instantaneousely in the watch function, the popup data is always "1 iteration behind", it's showing the info of the previous kanji I clicked...
Is there a way for a watcher function to be called AFTER Vue has completed is binding with the new data?
I think you need nextTick, see Async-Update-Queue
watch: {
'modalKanji': function (newData) {
this.$nextTick(function () {
uglipop({
class: 'modalKanji', //styling class for Modal
source: 'div',
content: 'divModalKanji'
});
});
}
}

Capture events of underlying element in component

Trying to use this component.
<select2 v-model="value" :options="options" #change="onChange()"></select2>
The #change callback is not getting called. I know that I can use watch: { value: function () { ... } but, is there a way to capture underlying tag events?
In the current version, select2 component does not handle on-change function. For this, you have to modify the select2 component, you have to add one more prop: onChange and inside component execute the function passed in this prop, changes will be something like following:
Vue.component('select2', {
props: ['options', 'value', 'onChange'], //Added one more prop
template: '#select2-template',
mounted: function () {
var vm = this
$(this.$el)
.val(this.value)
// init select2
.select2({ data: this.options })
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
//New addition to handle onChange function
if (this.onChange !== undefined) {
this.onChange(this.value)
}
})
},
watch: {
value: function (value) {
// update value
$(this.$el).select2('val', value)
},
options: function (options) {
// update options
$(this.$el).select2({ data: options })
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
})
Now, you can pass a function which will be executed onChange like following:
<select2 v-model="value" :options="options" :on-change="onChange()"></select2>

Categories

Resources