publish/subscribe problems when using Meteor to make a calendar app - meteor

I'm trying to use Meteor to build a calendar app, but having issues about initial loading of data from database.
At the very beginning, I just used the autopublish, which caused the problem because the subscription might not be ready yet. Then I looked at the questions here Can't put data from a Meteor collection into an array and here Meteor: How can I tell when the database is ready? and make some changes of my code to this:
Meteor.startup(function(){
Session.set("data_loaded", false);
});
Meteor.subscribe("allEvents", function() {
Session.set("data_loaded", true);
});
var myCalendar = null;
Template.Calendar.onRendered(function() {
if (Session.get("data_loaded")) {
myCalendar = $('#myCalendar').fullCalendar({
header: {
left: 'prev,next,today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultView: 'agendaWeek',
dayClick: function(date, jsEvent, view) {
CalEvents.insert({title:"New Event", start:date.format(), end:date.format()});
Session.set("lastMod", new Date());
},
eventClick: function(calEvent, jsEvent, view) {
},
events: function(start, end, timezone, callback) {
var events = [];
allEvents = CalEvents.find({},{reactive:false});
console.log(allEvents);
allEvents.forEach(function(evt){
console.log(evt);
events.push({
id:evt._id,
title:evt.title,
start:evt.start,
end:evt.end});
});
callback(events);
}
}).data().fullCalendar;
}
myCalendar.defaultView = 'agendaWeek';
});
Template.Calendar.lastMod = function() {
return Session.get("lastMod");
};
However, I'm still having the same problem, just at this time, instead of showing a blank calendar, it doesn't show calendar at all in most cases. I feel like I'm not setting Session correctly, especially for that if statement, but I'm not very sure how to do that.
And then, I found this post Displaying loader while meteor collection loads , and followed the step there to make a template-level subscriptions. However, I got another error.
var myCalendar = null;
Template.Calendar.onCreated(function(){
this.subscribe("allEvents");
});
Template.Calendar.onRendered(function() {
myCalendar = $('#myCalendar').fullCalendar({
header: {
left: 'prev,next,today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultView: 'agendaWeek',
dayClick: function(date, jsEvent, view) {
CalEvents.insert({title:"New Event", start:date.format(), end:date.format()});
Session.set("lastMod", new Date());
},
eventClick: function(calEvent, jsEvent, view) {
},
events: function(start, end, timezone, callback) {
var events = [];
allEvents = CalEvents.find({},{reactive:false});
console.log(allEvents);
allEvents.forEach(function(evt){
console.log(evt);
events.push({
id:evt._id,
title:evt.title,
start:evt.start,
end:evt.end});
});
callback(events);
}
}).data().fullCalendar;
myCalendar.defaultView = 'agendaWeek';
});
Template.Calendar.lastMod = function() {
return Session.get("lastMod");
};
template file:
<template name="Calendar">
{{#if Template.subscriptionReady}}
{{> editEvent}}
<br>
<br>
<input type="hidden" name="lastMod" value="{{lastMod}}" id="lastMod">
<div id="myCalendar">
</div>
{{else}}
Loading...
{{/if}}
</template>
When the application starts, it shows the "Loading..." characters, but then I got a TypeError: Cannot read property 'fullCalendar' of undefined from }).data().fullCalendar;
Anyone can help me to get this thing work? Thanks in advance.

Short answer: "meteor search" is your friend. Use the component that I linked in the comment.
Long answer: Getting UI elements to work that modify DOM or create entire DOM trees is tricky with meteor. The overall problem is that meteor doesn't have a single callback which gets called when the DOM is finalized. In fact, DOM is never finalized and could change at any point. Any custom nodes that a UI component created and were in the subtree will get destroyed and will have to be recreated. While it can be done, it is quite a bit of plumbing and isn't easily understood by the newly initiated to meteor.
Luckily however, most of the frequent used UI components already have a meteor smart package and using them is just a matter of doing a "meteor add". Even a bit more experienced ones out of us prefer to simply save time and if a package already have the plumbing of the component sorted out, would just use that instead of spending a few hours trying to get it to work from scratch.

Related

Fullcalendar triggering multiple sources

I'm using fullcalendar v2 and the sources of the events are selectable from an ul-li menu. The initial call uses the link from the first li and I may navigate through all the months in the calendar. When I use the second link, fullcalendar retrieves the events from the previous link as well as the new link. On each new select, fullcalendar remember the event sources. How can I disable this? Here my code:
$("#chgBranch li").on("click", function (event) {
event.preventDefault(); // To prevent following the link (optional)
var urlEvents = $(this).children('a').attr('href');
var branchName = $(this).children('a').text();
$('#calendarSchedules').fullCalendar('removeEvents');
$('#calendarSchedules').fullCalendar('removeEventSource');
// refill calendar
fillEvents(urlEvents, branchName);
});
function fillEvents(urlEvents, branchName) {
// change title of potlet box
var newTitle = '<i class="fa fa-calendar"></i> Available schedules for: ' + branchName;
$('#calendarCaption').html(newTitle);
$('#calendarSchedules').fullCalendar({
year: <?= $yy ?>,
month: <?= --$mm ?>, // javascript month is 0 based
date: 1,
header: {
left: '',
center: 'title',
right: 'prev,next,today'
},
defaultView: 'month',
firstDay: 1, // monday
editable: false,
slotEventOverlap: false,
selectable: true,
selectHelper: true,
dayRender: function (view, element) {
$('.fc td.fc-sun').css('background', '#E7EFEF');
$('.fc td.fc-sat').css('background', '#E7EFEF');
},
eventClick: function (event) {
$('#dueTime').show();
$('#duedate').val(moment(event.start).format('YYYY-MM-DD'));
$('#duetime').val(moment(event.start).format('HH:mm:ss'));
$('#isPartitionable').val(event.partitionable); // user clicked on partitionable dispo or not
}
});
$('#calendarSchedules').fullCalendar('addEventSource', urlEvents);
}
// load first branch item
$('#chgBranch li a:first').trigger('click');
I think you should destroy your calendar also. Because you are initializing fullcalendar every tine on sources change from ul-li.
Try below one.
$("#chgBranch li").on("click", function (event) {
event.preventDefault(); // To prevent following the link (optional)
var urlEvents = $(this).children('a').attr('href');
var branchName = $(this).children('a').text();
$('#calendarCaption').fullCalendar( 'destroy' );
// refill calendar
fillEvents(urlEvents, branchName);
});
This will work for sure. I tried locally and works fine.
I tried it with following method which also works fine, but #Chintan method is also fine:
$('#calendarSchedules').fullCalendar('removeEvents');
$('#calendarSchedules').fullCalendar('removeEventSource', storedEventSource);
And I store the event source in a hidden field once the calendar rendered.

Pnotify and fullcalendar

I am using pnotify and loading callback function to show a notification when the fullcalendar plugin has loaded all events.
loading:function(isLoading, view){
if (isLoading === false){
new PNotify({
title:"Finished loading events",
type:'success',
delay: 1000
});
My problems is that when ever I move to different dates it calls loading again so I am left with so many notifications shown on my screen that it becomes very unusable. How can I bypass this? Is there a way to check if a notification is active and just change the text and title of it?
You can add that logic based on the template you're using (check the template docs).
Your code would be something like
loading:function(isLoading, view){
var exists = false;
$(".ui-pnotify-title").each(function() {
if ($(this).html() == 'Finished loading events')
exists = true;
});
if (!exists) {
new PNotify({
title:"Finished loading events",
type:'success',
delay: 1000
});
}
}
It would be better if you could use a specific id or class to detect if the notification is already shown, but this works.
Take a look at the working jsfiddle.
You can just store it in a variable, do your necessary code (like nullable/undefined checks, etc) and call "update()" (here: http://sciactive.com/pnotify/ - for example, find for 'Click Notice' and see the source)
var p = new PNotify({
title: 'Some title',
text: 'Check me out! I\'m a error.',
type: 'error',
icon: 'fa fa-times-circle'
});
// ... code ...
p.update({title: 'My new title'});

FullCalendar skips back to current date rather than staying on current month

I have FullCalendar installed and working great, pulling in courses from my database.
You can view different courses based on clicking a button that submits the page again but passes different criteria.
The Issue is that on reloading of the page and the new content it skips back to the current date which is rather annoying when when you are looking at courses 3 months into the future!!
Does anybody know how to make the calendar go back to the page you where on after you have refreshed the page???
I have a feeling it might be something to do with getdate as I got the following code to work but can't seem to pass the result back through the URL and into the calendar setup.
$('#my-button').click(function() {
var d = $('#calendar').fullCalendar('getDate');
alert("The current date of the calendar is " + d);
});
If you use jquery.cookie you can store the currently viewed date in a cookie for the page being viewed and use that value to set the defaultDate when the page reloads. Pass these in as options when you initialise your calendar:
defaultView: Cookies.get('fullCalendarCurrentView') || 'month',
defaultDate: Cookies.get('fullCalendarCurrentDate') || null,
viewRender: function(view) {
Cookies.set('fullCalendarCurrentView', view.name, {path: ''});
Cookies.set('fullCalendarCurrentDate', view.intervalStart.format(), {path: ''});
}
This code also saves the current view (e.g. month, day etc...)
I used a combination of the two above. I set the localStorage value for the start date when creating, moving, or resizing an event as well as viewRender and then assigned that value to the defaultDate.
defaultDate: localStorage.getItem('Default_FullCalendar_Date'),
viewRender: function(view) {
localStorage.setItem('Default_FullCalendar_View', view.name);
...
},
select: function(start, due){
localStorage.setItem('Default_FullCalendar_View', start);
...
},
eventDrop: function(event, delta, revertFunc, jsEvent, ui, view){
localStorage.setItem('Default_FullCalendar_View', event._start._d);
...
},
eventResize: function(event, delta, revertFunc, jsEvent, ui, view){
localStorage.setItem('Default_FullCalendar_View', event._start._d);
...
}
Works like a charm.
You can use gotoDate method:
var d = $('#calendar').fullCalendar('getDate');
$('#calencar').fullCalendar( 'gotoDate', d.getFullYear(), d.getMonth(), d.getDate() )
Here is an updated answer for version 4 and 5 of fullcalendar.
since viewRender is no longer an option in these versions. I came up with a different approach using the loading option.
The loading option will give you a boolean argument stating whether the calendar is done loading or not. Inside that function I check if the calendar is done loading and if so, I set the calendar date to localStorage. Next I created an if else statement before the fullcalendar object to check if the localstorage item exists, and if so I set the defaultDate option in the calendar object to to localStorage date; if not, I just set it to today's date.
Example:
let viewDate;
const savedDate = localStorage.getItem("calDate");
if (savedDate !== null) {
viewDate = new Date(savedDate);
} else {
viewDate = today();
}
const calendarElement = document.getElementById('your_calendar');
const calendar = new FullCalendar.Calendar(calendarElement, {
defaultDate: viewDate,
loading: function(stillLoading) {
if (stillLoading === false) {
// When Calendar is done loading....
localStorage.setItem("calDate", calendar.getDate());
}
},
});

Possible Meteor bug in manual publish/subscribe scenario

I am encountering a problem with subscribing/publishing in Meteor. I've written an example Meteor app to help narrow the scope of the problem.
I am publishing a collection on the server that is filtered by a parameter passed in through a subscription on the client. This subscription is within an autosubscribe, which leverages a session variable to reactively update the subscriptions.
When changing the state of this particular session variable, the collection on the client isn't getting updated properly, or at least that's what I gather. I've spent the whole day on this and have not found an issue in code I control. I suspect I'm either not understanding how to setup proper pub-sub in Meteor, or there's a problem within Meteor.
To reproduce the problem, start a new Meteor project and use the following (Make sure to remove the autopublish package when trying it out):
HTML (test.html for example):
<head>
<title>pubsubbug</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
<h1>Example showing possible bug in Meteor wrt pub-sub</h1>
<p><button name="showall">show all ({{showall}})</button></p>
<div style="float:left;width:400px;">
<h2>Notes:</h2>
<ul>
{{#each notes}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
<div style="float:left;">
<h2>Notes (copied):</h2>
<ul>
{{#each notes_copied}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
</template>
JS (test.js for example)
if (Meteor.is_client) {
Notes = new Meteor.Collection("notes_collection");
NotesCopied = new Meteor.Collection("notes_collection_copied");
Session.set("showall", false);
Meteor.autosubscribe(function () {
Meteor.subscribe("notes_subscription", Session.get("showall"), function () {
console.log("Notes count:", Notes.find().count());
});
Meteor.subscribe("notes_subscription_copied", Session.get("showall"), function () {
console.log("Bug? This isn't getting called.");
console.log("NotesCopied count:", NotesCopied.find().count());
});
});
Template.main.notes = function () {
return Notes.find();
};
Template.main.notes_copied = function () {
return NotesCopied.find();
};
Template.main.showall = function () {
return Session.get("showall");
};
Template.main.events = {
"click button[name='showall']": function (evt) {
Session.set("showall", !Session.get("showall"));
}
};
}
if (Meteor.is_server) {
Notes = new Meteor.Collection("notes_collection");
var getNotes = function (showall) {
if (showall) {
return Notes.find({}, {sort: {title: 1}});
} else {
return Notes.find({visible: true}, {sort: {title: 1}});
}
};
Meteor.publish("notes_subscription", function (showall) {
// By sending the Notes back with the same uuid as before, the
// client end seems to get confused:
return getNotes(showall);
});
Meteor.publish("notes_subscription_copied", function (showall) {
var notes = getNotes(showall);
var self = this;
// Copy notes into a new notes collection (see NotesCopied on client).
// By generating a new uuid, we don't get an issue with the notes
// on the client getting screwed up:
notes.forEach(function (note) {
var uuid = Meteor.uuid(); // note._id will cause same problem
self.set("notes_collection_copied", uuid, {title: note.title});
});
self.flush();
self.complete();
});
// Add example notes
Meteor.startup(function () {
if (Notes.find().count() === 0) {
Notes.insert({title: "Note #1 (always visible)", visible: true});
Notes.insert({title: "Note #2 (always visible)", visible: true});
Notes.insert({title: "Note #3 (always visible)", visible: true});
Notes.insert({title: "Note #4 (only visible when showall true)", visible: false});
Notes.insert({title: "Note #5 (only visible when showall true)", visible: false});
Notes.insert({title: "Note #6 (only visible when showall true)", visible: false});
}
});
}
An explanation of what you will be seeing:
There will be a button that, when clicked, simply toggles a session variable (showall) between true and false.
Two subscriptions exist (within an autosubscribe), one that exemplifies the bug, and another that is suffixed with _copied, which is a test to demonstrate that when the collection in question is "copied" and new uuid's are assigned, the results are displayed properly. I couldn't figure out what to do with this particular bit of info... I don't want new uuid's.
So basically, when the show all button is clicked repeatedly, the first column Notes: will display incorrect results, and after a few clicks won't show anything.
On the other hand, the second column Notes (copied):, whose uuid's are re-generated each time, shows up correctly.
Is this a bug? Or is there a proper way to do this?
EDIT: Example above live over at http://pubsubbug.meteor.com/
Not experiencing your bug on the developer branch on Windows. Since this is the case, it is a good sign that there is nothing wrong with your code. It appears that you see something buggy regarding the subscriptions and/or how Mongo queries.
Meteor itself is most likely running the stable (= master) release on their hosting, so you will have to try a different approach or wait for a new release. Unless you can support running on devel...

FullCalendar: How to open event details in Colorbox?

I have created a jquery fullcalendar, pulling the feed from a google calendar and would like to open the event details in a colorbox. So far, I am completely lost as to how to achieve this and am looking for help. Everything that I have tried so far causes the calendar not to appear at all, so there is clearly a problem. Here is the latest code that I have tried:
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
events: {
url: 'my feed url'
}
eventAfterRender: function(event, element, view ) {
if(event.url) {
$('a',$(element)).colorbox({
type: 'ajax'
});
}
}
})
});
</script>
I don't think I completely understand what's going on with fullcalendar's event information; so if someone can provide a working code that I can mess with, I would appreciate it. Thanks very much in advance for any help!
Simply add the eventClick property when initializing the fullcalendar object and call colorbox within,
$('#calendar').fullCalendar({
editable: true,
eventClick: function(calEvent, jsEvent, view) {
$.colorbox({html:"<h1>"+calEvent.title+"</h1><br><p>"+calEvent.start+" TO "+calEvent.end+"</p>"});
},
events: [
{
title: 'All Day Event',
start: new Date(y, m, 1)
},
{
title: 'Long Event',
start: new Date(y, m, d-5),
end: new Date(y, m, d-2)
}
]
});
This is a very basic example. You can expand on it as per your requirements. Hope it helps.

Resources