I'm using Vue to build an app with Esri's Mapping API.
Via the Esri API, I'm able to set a popup template content using an object:
const popupWindowTemplate = {
title: "{mag} magnitude near {place}",
content: getContent
};
and a function
getContent: function(){
let node = document.createElement('div');
node.innerHTML = "<button type='button'>Do my thing!</button>"
return node;
}
However, I would like the getTemplate function to return a Vue component rendered in the innerHTML instead of hard coded html.
I have a component:
const buffer = Vue.component('do-mything', {
template: '<div><button type="button" #click="domything">Do my thing!</button></div>',
data() {
return {
somevalue: ''
};
}
});
and suspect it has something to do with components render functions, but have been having trouble figuring out how to insert the component in the getContent function.
Assuming the that API, you integrate with, expects getContent method to return a DOM element, you can try to:
create a component instance
render it off-document (by calling $mount without args on the component instance)
return the DOM element the component was rendered to
To implement the above getContent method may look like as follows:
getContent: function(){
const vueComponent = new Vue({
template: '<div><button type="button" #click="domything">Do my thing!</button></div>',
methods: {
domything() {
console.log('Method was called')
}
}
});
return vueComponent.$mount().$el;
}
Related
Say, I have a component with the following template:
<div id="root"></div>
Then somehow I call the code that appends a new element to it (using jQuery for now):
$('#root').append('<div id="a1" v-bind:styles="styles.a1"></div>')
where "styles.a1" is just an object from component's data:
data() {
return {
styles: { a1: {color: 'red'} }
};
}
I suppose the newborn element's binding will not work because Vue doesn't know anything about this new element and its binding.
But maybe there is some way to implement the desired logic?
PS I know that Vue's nature is to manipulate with data, not DOM but here is the situation I have to deal with.
If you're using Vue components you shouldn't be manipulating the DOM.
You should either use template logic for that.
<template v-if="someCondition">
<div id="a1" v-bind:styles="styles.a1"></div>
</template>
Wrap non Vue code inside it's own Vue component.
Vue.component('date-picker', {
template: '<input/>',
mounted: function() {
$(this.$el).datepicker();
},
beforeDestroy: function() {
$(this.$el).datepicker('hide').datepicker('destroy');
}
});
https://vuejsdevelopers.com/2017/05/20/vue-js-safely-jquery-plugin/
Use a custom directive.
Vue.directive('demo', {
bind: function () {
$(this.el).css({color:'#fff', backgroundColor:this.arg});
},
update: function (value) {
$(this.el).html(
'argument - ' + this.arg + '!<br>' +
'key - ' + this.key + '<br>' +
'value - ' + value);
}
});
You can bind a style without a value first,
data() {
return {
styles: { a1: '' }
};
}
then you can add data into that from an event via normal JS. (JQuery isn't even necessary here).
methods: {
quack() {
this.styles.a1 = { color: red }
}
}
Edit: I got it wrong. Seems Vue does not detect newly added elements after being mounted. So what you can do is mount the Vue instance after the appended elements are added or to re-render the whole instance with forceUpdate . (The latter will re-render the whole instance so I recommend breaking down into multiple Vue instances for the sake of performance)
ForceUpdate
I have the following:
Vue.component('times-updated', {
template: '<span>Times Updated: {{ timesUpdated }}</span>',
data: function() {
return {
timesUpdated: this.$parent.myData.timesUpdated
}
}
});
var vm = new Vue({
el: '#test',
data: function() {
return {
myData: {}
}
}
})
setInterval(function(){
$.ajax({
url: `${window.location.href}/json`, // This just returns an array : array.timesUpdated: 2 etc
}).done(function (data) {
vm.myData = data; // changes this data
});
}, 1000)
and am using the following html:
<div class="test">
<times-updated></times-updated>
</div>
I poll a REST API that returns an array which includes a timesUpdated property:
{
timesUpdated: 5
}
My intention is that every second I use jQuery's $.ajax method to call the API, update the myData data object on vm, which would then update the times-updated component.
The code works on initial page load, the times-updated component can retrieve the value on its parent's myData property, but whilst I have confirms that vm.myData does reflect the new value from the API, the component doesn't update its display to show the new count.
What am i doing wrong?
The data function is only called once during the life cycle of the component; when it is initially created. So essentially your component is just displaying the value as it existed when the component was created.
Additionally, it's generally bad practice to reach out of a component to get a data value. Vue is props down, events up. You should convert your component to use a property.
Vue.component('times-updated', {
props:["times"],
template: '<span>Times Updated: {{ times }}</span>',
})
The fact that you are using a function to define the Vue in this particular case doesn't really matter, it's just not a typical practice. Components require a function because they need an isolated scope.
Here is an example.
That callback is required only in components
// vue instance
new Vue({
data: {
status: true
}
};
// vue components (callback)
Vue.component('custom-component', {
data: function() {
return {
status: false
}
}
});
So I working on app in Vue. I have problem with sending and receiving data between components. Already tried with $dispatch/$broadcast, $emit/$on but still now working. I want to send selected active_club from ClubSelection.vue to vue_main.js.
Vue version: 2.0.3
Structure of my app:
vue_main - main Vue file
HeaderElement.vue (child of vue_main)
ClubSelection.vue (child of HeaderElement)
Need to send active_club from ClubSelection to vue_main.
ClubSelection.vue
<script>
export default{
props: [
'club', 'title'
],
created(){
//Get club list
this.$http.get('/api/clubs', function(data) {
this.clubs = data;
console.log(data);
//read active club from parent
this.selected = this.$parent.$parent.active_club;
});
},
data(){
return{
clubs: [],
selected: null,
}
},
watch: {
selected: function(v) {
this.club = v;
//Post to database selected club
this.$http.post('/api/clubs/' + v + '/active')
},
club: function(v) {
this.selected = v;
//Change active_club at parent (THIS NOT WORKING)
// this.$emit('active_club', v);
// this.$parent.active_club = v;
club.$emit('active_club', v);
},
}
}
</script>
vue_main.js
const app = new Vue({
router,
data() {
return {
user: [],
active_club: null,
ranking: null
}
},
created: function() {
var self = this;
this.$http.get('/api/users/me', function(data) {
this.user = data;
self.active_club = data.active_club;
})
}
}).$mount('#app');
const club = new Vue();
//THIS NOT WORKING
club.$on('active_club', function (id) {
alert(id)
this.active_club = id;
});
Errors:
Vue warn]: Error in watcher "club" (found in component
)
vue_main.js:16924 Uncaught (in promise) ReferenceError: club is not
defined
I have tried many set ups, this is one of them. How to make this working?
$dispatch and $broadcast are deprecated in Vue 2.0.
In your case, what you need is communication between a parent component and child component. When a child $emits an event, parent can listen to it by providing a method in template markup itself, using v-on:parentMethod() as follows:
<child-component v-on:child-event="handlerMethod"></child-component>
The above markup is done inside parent component's template. And the parent component needs to have that handlerMethod in its methods.
Here is a sample "parent-child communication" question on Stackoverflow, which has a jsFiddle example also: Delete a Vue child component
You may use the above answer as reference to implement $emit in your app.
Edit: Additional Notes
I forgot to mention the note about three level hierarchy you have. In your app, you have the following hierarchy:
parent: vue_main
child 1: HeaderElement
child 1.1: ClubSelection
For sending events from ClubSelection to vue_main, you may either use non parent-child communication method or you can relay the event using the intermediate HeaderElement.
Here is how the event relay can work:
Step 1: ClubSelection sends a $emit, which is received by HeaderElement using v-on.
Step 2: The handlerMethod in HeaderElement does a this.$emit, which can be received by your main template using another v-on.
While the above may look a bit convoluted, it is much more efficient than broadcasting to every single component in the app, as it is generally done in Angualr 1.x or other frameworks.
In a project with vue.js 2:
I've a component living in a .vue file that represents a list of elements. Also, I've a sidebar that is the summary of this list. This sidebar is another component in a .vue file.
So, how I can keep communication between each them, for example, if I removed a element from a list, reflect the change in a var declared in sidebar that is the total number of elements?To ilustrate:
SideBar.vue
<template>
...
<span></span> ===> here I need total of elements listed in ListElements.vue
...
<template>
ListElements.vue
<template>
...
#click="deleteEntry"
...
<template>
<script>
methods: {
deleteEntry(entry) {
//here I need to notify to SideBar.vue in order to update the total of elements in the this.entries list.
let index = this.entries.indexOf(entry);
if (window.confirm('Are you sure you want to delete this time entry?')) {
this.entries.splice(index, 1);
}
}
</script>
OK, I've created a simplified example of how this works. Your bus needs to be global so it is accessible by all Vue components, this simply means placing it outside of all other components and view models:
var bus = new Vue({});
var vm = new Vue({
// Main view model has access to bus
el: '#app'
});
Then you just need to emit the event on the bus on some event and catch that in the other component:
Component one emits a message to the bus on keyup:
Vue.component('component-one', {
template: '<div>Enter a message: <input v-model="msg" v-on:keyup="updateMessage"> </div>',
methods: {
updateMessage() {
bus.$emit('msg', this.msg);
}
},
data() {
return {
msg: ""
}
}
});
Component-two listens for the message:
Vue.component('component-two', {
template: "<div><b>Component one says: {{ msg }}</b></div>",
created() {
bus.$on('msg', (msg) => {
this.msg = msg;
});
},
data() {
return {
msg: ""
}
}
});
Here's the fiddle: https://jsfiddle.net/v7o6d2vL/
For your single page components to get access the the bus you just need to make sure your bus is in the global scope, which you can do by using window:
window.bus = new Vue({});
you can then use bus.$emit() and bus.$on() inside your components as normal
First of all let me say I am still using ES5, mostly because I am writing this for a frontend of a Google Apps Scripts application and didn't have the time/patience to make TypeScript work.
I am currently using this method in order to upgrade my Angular1 app to Angular2:
http://www.codelord.net/2016/01/07/adding-the-first-angular-2-service-to-your-angular-1-app/
I have a overlayLoaderService service to show a loading spinner in an overlay div with simple functions to get and set the loading state, and a overlayLoader component to show the div itself.
Service:
var overlayLoaderService = ng.core.
Injectable().
Class({
constructor: function() {
this.loading = false;
this.stateChange = new ng.core.EventEmitter();
},
setState: function(state) {
this.loading.value = state;
this.stateChange.emit(state);
},
getState: function() {
console.log(this.loading);
}
});
upgradeAdapter.addProvider(overlayLoaderService);
angular.module('Gojira').factory('overlayLoaderService', upgradeAdapter.downgradeNg2Provider(overlayLoaderService));
Component:
var OverlayLoaderComponent = ng.core
.Component({
selector: 'overlay-loader',
template: '<div [hidden]="loading" id="overlay-loader"></div>',
providers: [overlayLoaderService]
}).Class({
constructor: [overlayLoaderService, function(overlayLoaderService) {
this.loading = !overlayLoaderService.loading.value;
this._subscription = overlayLoaderService.stateChange.subscribe(function (value) {
console.log(value);
this.loading = !value;
});
}],
});
angular.module('Gojira').directive('overlayLoader', upgradeAdapter.downgradeNg2Component(OverlayLoaderComponent));
What I am trying to do is to achieve is the component to update its loading property when I call setState() method in the overlayLoaderService.
The subscription is never called, so I guess I am doing something terribly wrong here.
Any help would be appreciated.
remove
providers: [overlayLoaderService]
from OverlayLoaderComponent should work, as the provider has already been added to the adapter. Otherwise, it seems like angular 1 component and angular 2 component are using different instance of that service.