events in fullcalendar/VUE application not updating - javascript

Inside of my cal.vue component I have a fullcalendar component. Within cal.vue I have a method called submit. Inside of submit I make a (successful) reference to the fullcalendar component with this.$refs.calendar. However when I do this.$refs.calendar.$emit('refetchEvents'); in my submit function the events are not fetched (my events are not updated on my calendar). Why are my events not being updated upon submit and how can I update them?
Here is the relevant code:
<template>
<div id="calendar" :navL="navLinks" :event-sources="eventSources" #event-selected="eventSelected" #event-created="eventCreated" :config="config" >
<button class="btn btn-secondary" v-on:click="submit()">
Submit
</button>
<full-calendar id="target" ref="calendar" :event-sources="eventSources" #event-selected="eventSelected" #day-click="click"#event-created="eventCreated" :config="config"></full-calendar>
</div>
</template>
<script>
const self = this;
export default {
name: 'calendar',
data() {
return {
eventSources: [
{
url: 'http://localhost:3000/getAppointments',
type: 'GET',
data: {
id: this.$store.getters.user
},
error: function() {
alert('there was an error while fetching events!');
},
}
],
};
},
methods: {
submit: function() {
console.log(this.$refs.calendar); //prints fullcalendar VUE component
this.$refs.calendar.$emit('refetchEvents'); //nothing appears to happen here
},
},
}
</script>

According to the documentation,
this.$refs.calendar.$emit('refetchEvents')
should be
this.$refs.calendar.$emit('refetch-events')

If using the vanilla FullCalendar via npm packages: "#fullcalendar/core", "#fullcalendar/daygrid", "#fullcalendar/timegrid", "#fullcalendar/vue"
html: <full-calendar ref="fullCalendar" :events="events" />
inside a method to update an event:
let calendar = this.$refs.fullCalendar.getApi()
const updatedEvent = calendar.getEventById(eventId)
updatedEvent.setProp('title', 'New Title')
updatedEvent.setProp('backgroundColor', 'yellow')
updatedEvent.setProp('borderColor', 'yellow')
updatedEvent.setProp('textColor', 'black')
The above will rerender the fullCalendar event but will not change your this.events array. There may be something that's a bit more all inclusive for ALL events in one shot otherwise you'd have to do:
this.events.forEach(event => {
...similar code the above
})

Related

VueJS - Run Code after event is successfully emitted

I'm trying to clear up the form in the child component after the event containing the entered form data has been successfully passed from the child to parent component. However, I notice that the form gets cleared before the data gets propagated via the event to the parent component, such that the event passes empty values to the parent. I tried delaying the clearForm() using a timeout, but it didn't help. Is there a way to modify the behavior such that the clearForm() happens only after the event completes and the data has been saved?
Attached is the code.
Child Component
<template>
<!-- Contains a form -- >
</template>
<script>
export default {
data() {
return {
additionalInfo:
{
id: new Date().toISOString(),
fullName: '',
preAuthorize: '',
serviceAddress: ''
},
validation: {
fullNameIsValid: true,
serviceAddressIsValid: true
},
formIsValid: true,
addServiceButtonText: '+ Add Service Notes (Optional)',
serviceNotes: [],
showServiceNotes: false,
enteredServiceNote: '', //service notes addendum
}
},
computed : {
// something
},
methods: {
setServiceNotes(){
this.showServiceNotes = !this.showServiceNotes;
},
addAnotherParty(){
this.validateForm();
if(!this.formIsValid){
return;
}
this.$emit('add-parties', this.additionalInfo); //event
console.log(this.clearForm);
},
clearForm(){
this.additionalInfo.fullName = '';
this.additionalInfo.serviceAddress = '';
this.additionalInfo.preAuthorize = false;
}
}
}
</script>
Parent Component
<template>
<div>
<base-card
ref="childComponent"
#add-parties="updateAdditionalInfoList">
<!-- Wrapper for the `Parties Being Served` component-->
<template v-slot:title>
<slot></slot>
</template>
</base-card>
</div>
</template>
<script>
export default {
data() {
return {
hasElement: false,
selectedComponent: 'base-card',
additionalInfoList : [],
clearForm: false
}
},
methods: {
updateAdditionalInfoList(additionalInfo){ //save changes passed via event
this.additionalInfoList.push(additionalInfo);
console.log('emitted');
console.log(this.additionalInfoList);
setTimeout(() => {
this.$refs.childComponent.clearForm(); //clear the form in child
}, 2000);
}
}
}
</script>
Try this
addAnotherParty(){
this.validateForm();
if(!this.formIsValid){
return;
}
let emitObj = JSON.parse(JSON.stringify(this.additionalInfo));
this.$emit('add-parties', emitObj); //event
console.log(this.clearForm);
}
If your object is not deep then you can use
let emitObj = Object.assign({}, this.additionalInfo);
instead of stringify and parse

vue.js app.$destroy() does not remove app instance

I am learning vue js. I have an app called growler. I am trying to call $destroy method on-click of button.
<button id="destroyButton" class="btn btn-danger" v-on:click="onDestroyClick">Destroy</button>
If I have method as part of Javascript event, it is working.
<script type="text/javascript">
document.getElementById('destroyButton').addEventListener('click', function() {
growler.$destroy();
});
</script>
But, if I call this method as part of vue on-click event, it is not working.
methods: {
onDestroyClick: function() {
this.$destroy();
}
}
I am having lifecycle hooks for different events of the instance. I am trying to log them in the console.
beforeDestroy: function() {
console.log('beforeDestroy');
},
destroyed: function() {
console.log('afterDestroy');
}
This is working fine from Javascript Event listener. I am able to see Destroy messages in the console log.
Can you please tell, why it is not working as part of on-click event method. App instance is not destroyed.
Adding the answer here, as it was just mentioned in the comments.
It was a problem with bracces. The code is pasted below:
The corresponding jsfiddle is here
var growler = new Vue({
el: '#growler',
data :
{
message : "test"
},
methods: {
onDestroyClick: function() {
this.$destroy();
}
},
beforeDestroy: function() {
console.log('beforeDestroy');;
},
afterDestroy: function() {
console.log('afterDestroy');
}
});

Polymer iron-scroll-threshold not triggering

I want to make a feed that automatically loads items when the bottom of the current page is reached, however the iron-scroll-threshold doesn't trigger. I'm using my api call to fill the items in the template and the restaurants load just fine. Also when I bind a load function to a button it works just fine. It seems that the iron-scroll-threshold never triggers. Can anyone explain to me what I'm missing/doing wrong?
Code:
<iron-scroll-threshold id="threshold" lower-threshold="100" on-lower-threshold="loadMoreData">
<div id="scroller" class="vertical layout center" fill>
<template is="dom-repeat" items="[[restaurants]]" filter="{{computeFilter(searchInput)}}" scroll-target="threshold" on-scroll="_scrollHandler">
<!-- Items -->
</template>
<div class="loadingIndicator" hidden$="[[!loadingRestaurants]]">
<paper-spinner active$="[[loadingRestaurants]]"></paper-spinner> Fetching restaurants</b>
</div>
</div>
</iron-scroll-threshold>
<script>
Polymer({
is: 'my-view2',
properties: {
restaurants:{
type: Array,
value: []
},
offset:{
type: Number,
value: 0
},
limit:{
type: Number,
value: 50
},
loadingRestaurants: Boolean
},
ready: function () {
this.$.requestRestaurants.generateRequest();
},
handleResponse: function (data) {
var self = this;
var response = data.detail.response;
response.forEach(function(restaurant){
self.push('restaurants', restaurant);
});
console.log(this.restaurants);
this.$.threshold.clearTriggers();
},
toggle: function(e) {
console.log(this.$.threshold.top);
var index = "#collapse" + e.model.__data.index;
this.$$(index).toggle();
this.loadMore();
},
loadMore: function() {
console.log("test");
this.offset+=50;
this.limit+=50;
this.$.requestRestaurants.generateRequest();
this.$.threshold.clearLower();
this.$.threshold.clearTriggers();
}
});
The naming was inconsistent
on-lower-threshold="loadMoreData"
loadMore: function()

Listen to custom event in Vue.js

Vue.js works great with browser events such as click or mousedown. But not work at all with custom events. Here is the code:
HTML:
<div id="app" style="display: none" v-show="true">
<div v-el:ping v-on:ping="ping">
<div>
<button v-on:click="click">Click</button>
</div>
</div>
</div>
JavaScript:
new Vue({
el: '#app',
data: {
},
methods: {
ping: function (event) {
console.log('Vue ping', event);
alert('Vue ping');
},
click: function (event) {
jQuery(event.target).trigger('ping');
}
},
ready: function () {
console.log(this.$els);
jQuery(this.$els.ping).on('ping', function (event) {
console.log('jQuery ping', event);
alert('jQuery ping');
});
}
});
I expect alert with Vue ping and jQuery ping. But only the later pops up.
CodePen
Vue has its own internal system for custom events, which you should use instead of jQuery / native DOM events:
click: function (event) {
// jQuery(event.target).trigger('ping');
this.$dispatch('ping', event.target) // send event up the parent chain, optionally send along a reference to the element.
// or:
this.$emit('ping') // trigger event on the current instance
}
Edit: $dispatch is for parent-child communication, You seem to want to trigger a custom event from within the same comonent. In that case, you could instead simply call a method.
If you still want to listen to a custom event inside the same component, you:
want to use $emit
cannot use v-on:custom-event-name in the template (that's only to be used on components). Rather, add the event method to the events::
events: {
ping: function() {....}
}
Here it is in vanilla JS:
HTML:
<div id="app">
<div v-el:ping>
<div>
<button v-on:click="click">Click</button>
</div>
</div>
</div>
JS:
(function() {
new Vue({
el: '#app',
data: {
event: null
},
methods: {
ping: function(event) {
alert('Vue ping');
},
click: function(event) {
this.$els.ping.dispatchEvent(this.event);
}
},
ready: function() {
this.event = document.createEvent("HTMLEvents");
this.event.initEvent("ping", true, true);
this.$els.ping.addEventListener('ping', this.ping);
}
});
})();
pen: http://codepen.io/anon/pen/wGdvaV?editors=1010#0
You should avoid to mix a dom events and vue-components related ones because it's a different layers of abstraction.
Anyway, if you still want to do that, I think you need to cache this.el inside a vue-component instance or take it via computed-property like this
{
computed : {
jqueryEl(){ return $(this.el) }
}
}
And then trigger a custom jQuery events by this.jqueryEl.trigger('ping').
Sure to properly take care of keep the element's bindings up to date!
For example you can bind jQuery events dynamically (and also unbind on component destroy!) like this:
ready : function(){
jQuery('body').on('ping.namespace', '[data-jquery="ping"]', function(){ ... })
},
destroy : function(){
jQuery('body').off('ping.namespace')
}
And don't forget to add attribute [data-jquery="ping"] to an element which you would like to response a ping event.
Hope this information helps you to achieve the expected result.

VueJS and tinyMCE, custom directives

I've been struggling hard with getting VueJS and TinyMCE to work together. I've come to the conclusion that using directives would be the way to go.
So far I've been able to pass in the body as a directive parameter, and tinyMCE sets the content. However, I can't get the two way binding to work. I'm also afraid that I'm doing things completely wrong based on the tinyMCE api.
The relevant tinyMCE functions I assume would be:
http://community.tinymce.com/wiki.php/api4:method.tinymce.Editor.setContent
// Sets the content of a specific editor (my_editor in this example)
tinymce.get('my_editor').setContent(data);
and
http://community.tinymce.com/wiki.php/api4:method.tinymce.Editor.getContent
// Get content of a specific editor:
tinymce.get('content id').getContent()
HTML
<div id="app">
<h3>This is the tinyMCE editor</h3>
<textarea id="editor" v-editor :body="body"></textarea>
<hr>
<p>This input field is properly binded</p>
<input v-model="body">
<hr>
<pre>data binding: {{ body }} </pre>
</div>
JS
tinymce.init({
selector:'#editor',
});
Vue.directive('editor', {
twoWay: true,
params: ['body'],
bind: function () {
tinyMCE.get('editor').setContent(this.params.body);
tinyMCE.get('editor').on('change', function(e) {
alert("changed");
});
},
update: function (value) {
$(this.el).val(value).trigger('change')
},
});
var editor = new Vue({
el: '#app',
data: {
body: 'The message'
}
})
Fiddle
https://jsfiddle.net/nf3ftm8f/
With Vue.js 2.0, the directives are only used for applying low-level direct DOM manipulations. They don't have this reference to Vue instance data anymore. (Ref: https://v2.vuejs.org/v2/guide/migration.html#Custom-Directives-simplified)
Hence I recommend to use Component instead.
TinymceComponent:
// Use JSPM to load dependencies: vue.js 2.1.4, tinymce: 4.5.0
import Vue from 'vue/dist/vue';
import tinymce from 'tinymce';
// Local component
var TinymceComponent = {
template: `<textarea class="form-control">{{ initValue }}</textarea>`,
props: [ 'initValue', 'disabled' ],
mounted: function() {
var vm = this,
tinymceDict = '/lib/jspm_packages/github/tinymce/tinymce-dist#4.5.1/';
// Init tinymce
tinymce.init({
selector: '#' + vm.$el.id,
menubar: false,
toolbar: 'bold italic underline | bullist numlist',
theme_url: tinymceDict + 'themes/modern/theme.js,
skin_url: tinymceDict + 'skins/lightgray',
setup: function(editor) {
// If the Vue model is disabled, we want to set the Tinymce readonly
editor.settings.readonly = vm.disabled;
if (!vm.disabled) {
editor.on('blur', function() {
var newContent = editor.getContent();
// Fire an event to let its parent know
vm.$emit('content-updated', newContent);
});
}
}
});
},
updated: function() {
// Since we're using Ajax to load data, hence we have to use this hook because when parent's data got loaded, it will fire this hook.
// Depends on your use case, you might not need this
var vm = this;
if (vm.initValue) {
var editor = tinymce.get(vm.$el.id);
editor.setContent(vm.initValue);
}
}
};
// Vue instance
new Vue({
......
components: {
'tinymce': TinymceComponent
}
......
});
Vue Instance (simplified)
new Vue({
el: '#some-id',
data: {
......
description: null
......
},
components: {
'tinymce': TinymceComponent
},
methods: {
......
updateDescription: function(newContent) {
this.description = newContent;
},
load: function() {
......
this.description = "Oh yeah";
......
}
......
},
mounted: function() {
this.load();
}
});
HTML (MVC view)
<form id="some-id">
......
<div class="form-group">
<tinymce :init-value="description"
v-on:content-updated="updateDescription"
:id="description-tinymce"
:disabled="false">
</tinymce>
</div>
......
</form>
The flows
First the data is loaded through remote resources, i.e., AJAX. The description got set.
The description got passed down to the component via props: initValue.
When the component is mounted, the tinymce is initialized with the initial description.
It also sets up the on blur event to get the updated content.
Whenever the user loses focus on the editor, a new content is captured and the component emits an event content-updated, letting the parent know that something has happened.
On Html you have v-on:content-updated. Since the parent is listening to the content-updated event, the parent method updateDescription will be called when the event is emited.
!!Couple Important Notes!!
By design, the component has 1 way binding, from parent to component. So when the description gets updated from Vue instance, the component's initValue property should be updated as well, automatically.
It would be nice if we can pass whatever the user types in tinymce editor back to the parent Vue instance but 2 ways bindings is not supposed. That's when you need to use $emit to fire up events and notify parents from components.
You don't have to define a function in parent and do v-on:content-updated="updateDescription". You can just directly update the data by doing v-on:content-updated="description = $event". The $event has the parameter you defined for the function inside the component - the newContent parameter.
Hope I explained things clearly. This whole thing took me 2 weeks to figure it out!!
Here's a Tinymce component for Vue.
http://jsbin.com/pucubol/edit?html,js,output
It's also good to know about v-model and custom input components:
https://v2.vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
Vue.component('tinymce', {
props: ['value'],
template: `<div><textarea rows="10" v-bind:value="value"></textarea></div>`,
methods: {
updateValue: function (value) {
console.log(value);
this.$emit('input', value.trim());
}
},
mounted: function(){
var component = this;
tinymce.init({
target: this.$el.children[0],
setup: function (editor) {
editor.on('Change', function (e) {
component.updateValue(editor.getContent());
})
}
});
}
});
<tinymce v-model="whatever"></tinymce>
Try this:
Vue.directive('editor', {
twoWay: true,
params: ['body'],
bind: function () {
tinyMCE.get('editor').setContent(this.params.body);
var that = this;
tinyMCE.get('editor').on('change', function(e) {
that.vm.body = this.getContent();
});
}
});
The trick was storing the directive in the temporary variable "that" so you could access it from within the change event callback.
There is now an npm package which is a thin wrapper around TinyMCE, making it easier to use in a Vue application.
It is open source with code on GitHub.
Installation:
$ npm install #tinymce/tinymce-vue
Usage:
import Editor from '#tinymce/tinyme-vue';
Templates:
<editor api-key="API_KEY" :init="{plugins: 'wordcount'}"></editor>
Where API_KEY is your API key from tiny. The init section is the same as the default init statement except you do not need the selector. For an example see the documentation.

Categories