I'm trying to write a simple app based on FullCalendar package. When I run the code, none of the events is rendered, however right after clicking a day, an event gets shown on that day. If I click another day right after, it will erase the last one, and show the most recent one.
CalEvents = new Mongo.Collection("calevents");
// to be used later to handle editing
if (Meteor.isClient) {
Session.setDefault("event2edit", null);
Session.setDefault("showEditWindow", false);
Session.setDefault("lastMod", null);
Router.route('/', function () {
this.render('home');
});
Router.route('/calendar', function () {
this.render('calendar');
});
// runs when page has been rendered
Template.calendar.rendered = function () {
$('#calendar').fullCalendar({
events: function (start, end, timezone, callback) {
var events = [];
calEvents = CalEvents.find();
calEvents.forEach(function (evt) {
events.push({
id: evt._id,
title: evt.title,
start: evt.start,
end: evt.end
});
});
//alert(events.length);
callback(events);
},
dayClick: function(date, jsEvent, view){
CalEvents.insert({title:'NEW', start:date, end:date});
Session.set('lastMod', new Date());
updateCalendar();
},
eventClick: function (calEvent, jsEvent, view) {
}
});
}
Template.calendar.lastMod = function () {
return Session.get('lastMod');
}
}
var updateCalendar = function(){
$('#calendar').fullCalendar( 'refetchEvents' );
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
Is it a bug? Or is my code missing something? Thank you.
Try to pack your
$('#calendar').fullCalendar({
into a variable, like
calendar = $('#calendar').fullCalendar({
and add the following closing of your calendar function:
}).data().fullCalendar
and insert the Tracker.autorun function at the end of your Template.calendar.rendered block:
Template.calendar.rendered = function () {
calendar = $('#calendar').fullCalendar({
events: function (start, end, timezone, callback) {
var events = [];
calEvents = CalEvents.find();
calEvents.forEach(function (evt) {
events.push({
id: evt._id,
title: evt.title,
start: evt.start,
end: evt.end
});
});
//alert(events.length);
callback(events);
},
dayClick: function(date, jsEvent, view){
CalEvents.insert({title:'NEW', start:date, end:date});
Session.set('lastMod', new Date());
updateCalendar();
},
eventClick: function (calEvent, jsEvent, view) {
}
}).data().fullCalendar
Tracker.autorun(function(){
allReqsCursor = CalEvents.find().fetch();
if(calendar) calendar.refetchEvents();
});
};
Additionally, for increase in Performance:
For-Loop vs forEach Loop
You might consider using the for-loop instead of forEach-loop as former is 10 to 40 times faster, especially with pre-cached parameters i and len:
forEach Loop: (Originally)
calEvents = CalEvents.find();
calEvents.forEach(function(evt) {
events.push({
id: evt._id,
title: evt.t,
start: evt.s,
end: evt.e,
});
});
callback(events);
For loop: (10 to 40 times faster with pre-cached parameters i and len)
calEvents = CalEvents.find().fetch();
var len = calEvents.length, i = 0; //pre-cache the i and len
for(i; i < len; i++){
events.push({
id: calEvents[i]._id,
title: calEvents[i].t,
start: calEvents[i].s,
end: calEvents[i].e,
});
};
callback(events);
Hope this helps.
Related
I'm using an auto-run block where I re-execute the same mongo with a few session variables ! loop over those doc , construct an array of events then I call the addEventSource and refetchResources function which are pretty expensive computationally ! the fullcalendar becomes slow the more data ! on every action the auto-run block is rerun ! what in your in your opinion can be done to speed things up ? I thought about only re-rendering the delta elements but this doesn't cover the deletion and update .
calendar = $('#calendar').fullCalendar({
schedulerLicenseKey: Meteor.settings.public.fullCalendarLicenseKey,
now: new Date(),
editable: true, // enable draggable events
droppable: true, // this allows things to be dropped onto the calendar
aspectRatio: 1.8,
timezone:'local',
disableDragging: true,
displayEventTime: false,
selectable:true,
allDaySlot:true,
slotDuration:'24:00',
lazyFetching:true,
resourceLabelText: 'Employees',
nextDayThreshold:"12:00",
resources: function(callback) {
var tmp_obj = { usersSorting : { } };
tmp_obj.usersSorting["indexByLocation."+Session.get("locationId")] = 1;
var users = [];
var data = Meteor.users.find({
$or:[
{"profile.showInScheduler":{$exists:false}},
{"profile.showInScheduler":true}
],
assignedTo:{$in:[Session.get("locationId")]},
'locations._id':Session.get("locationId"),
"profile.companyId":Session.get("companyId")
},{sort : tmp_obj.usersSorting});
var arr = data.map(function(c) {
var employeeType = c.userSettings.employeeType;
var type = EmployeeType.findOne({_id:employeeType});
var img = Images.findOne({_id: c.picture});
var imgUrl = img ? img.url() : "/images/default-avatar.png";
c.name = c.name || "";
var totalHoursAllLocation = 0;
var totalHoursCurrentLocation = 0;
return {
id: c._id,
title: "t",
width:"2px"
};
});
callback(arr);
},
drop: function(date, jsEvent, ui, resourceId) {
},
eventResize: function( event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view ) {
},
dayClick: function(date, jsEvent, view,res,res2) {
},
eventClick: function ( event, jsEvent, view ) {
}
}).data().fullCalendar;
/********************* reactive calendar *****************/
this.autorun(function() {
if(Session.get("activeUsers")) {
schedulerSubs = Meteor.subscribe("SchedulesByLocation", Session.get("companyId"), Session.get("locationId"), Session.get("activeUsers"), moment(Session.get("currentDate")).startOf('week').toDate(), moment(Session.get("currentDate")).startOf('week').add(2, "weeks").endOf('isoweek').add(1,"days").toDate());
}
const company = Companies.findOne({_id: Session.get("companyId")});
Session.set("loading", true);
let events = [];
let usersInLocation = Meteor.users.find({
assignedTo: {$in: [Session.get("locationId")]},
'locations._id': Session.get("locationId"),
"profile.companyId": Session.get("companyId")
}).fetch();
let userIds = _.map(usersInLocation, "_id");
userIds.push("temp" + Session.get("companyId") + Session.get("locationId"));
Session.set("activeUsers",userIds);
if(schedulerSubs && schedulerSubs.ready()) {
var data;
SchedulerEvts = Schedules.find({
uid: {$in: userIds},
locationId: Session.get("locationId"),
companyId: Session.get("companyId"),
start: {$gte: moment(Session.get("currentDate")).startOf('week').toDate()},
end: {$lte: moment(Session.get("currentDate")).add(2, "week").endOf('isoweek').toDate()}
}).fetch();
SchedulerEvts.forEach(function (evt) {
var event = null;
var color = "";
var oloc = "";
var attendance = null;
var locationName = "";
var id = evt._id;
event = {
id:id,
type : evt.type,
title: evt.name,
start: evt.start,
end: evt.end,
color:color,
resourceId: evt.uid,
locationName:locationName,
};
events.push(event);
});
if (calendar) {
calendar.removeEvents();
calendar.addEventSource(events);
calendar.refetchResources();
}
}
I am trying to make collection aggregate in my Meteor.js app as shown below, yet each time I call my server logSummary method I get the following error. Can someone please tell me what I am doing wrong / how to resolve this error? Thanks.
Note: I am using Meteor-aggregate package
TypeError: undefined is not a function
at Object.Template.detailedreport.helpers.myCollection (http://localhost:3000/client/views/report.js?
Code:
Template.detailedreport.rendered = function() {
Session.set("dreport_customer", "");
Session.set("dreport_project", "");
Session.set("dreport_startDate", new Date());
Session.set("dreport_endDate", new Date());
$('.set-start-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-end-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-start-date').on("dp.change",function (e) {
Session.set("dreport_startDate", $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
});
$('.set-end-date').on("dp.change",function (e) {
Session.set("dreport_endDate", $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
});
};
Template.detailedreport.helpers({
customerslist: function() {
return Customers.find({}, {sort:{name: -1}});
},
projectslist: function() {
return Projects.find({customerid: Session.get("dreport_customer")}, {sort:{title: -1}});
},
myCollection: function () {
var now = Session.get("dreport_startDate");
var then = Session.get("dreport_endDate");
var custID = Session.get("dreport_customer");
var projID = Session.get("dreport_project");
Meteor.call('logSummary', now, then, projID, custID, function(error, data){
if(error)
return alert(error.reason);
return data;
});
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: '0._id.day', label: 'Day' },
{ key: '0.totalhours', label: 'Hours Spent'}
]
};
}
});
Template.detailedreport.events({
'submit form': function(e) {
e.preventDefault();
Session.set('dreport_endDate', $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
Session.set('dreport_startDate', $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
Session.set('dreport_project', $(e.target).find('[name=project]').val());
Session.set('dreport_customer', $(e.target).find('[name=customer]').val());
},
'change #customer': function(e){
Session.set("dreport_project", "");
Session.set("dreport_customer", e.currentTarget.value);
},
'change #project': function(e){
Session.set("dreport_project", e.currentTarget.value);
}
});
Template:
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=myCollection settings=settings}}
</div>
Server:
Meteor.methods({
logSummary: function(startDate, endDate, projid, custid){
var pipeline = [
{ $match: { date: { $gte: new Date(startDate), $lte: new Date(endDate) },
projectid: projid,
customerid: custid
}
},
{ $group: {
_id: {
"projectid": "$projectid",
"day": { "$dayOfMonth": "$date" },
"month": { "$month": "$date" },
"year": { "$year": "$date" }
},
totalhours: {"$sum": "$hours"}
}}
];
return ProjectLog.aggregate(pipeline);;
}
});
Looking at the ReactiveTable documentation, it looks like you need to do something like:
Template.myTemplate.helpers({
myCollection: function () {
return myCollection;
}
});
Where myCollection is the name of a Mongo/Meteor collection (e.g. BlogPosts) which you defined with e.g. BlogPosts = new Mongo.Collection('blogPosts');
The reason you're getting undefined is not a function is that you are calling a Meteor method inside a template helper. The call is asynchronous so the return value is undefined. Now you are passing undefined to ReactiveTable. ReactiveTable will be trying to call something like myCollection.find() which is essentially undefined.find() and will therefore throw the error you're seeing.
Later on the Meteor call will finish and the data value will be lost because the function has already returned.
You can call Meteor.call inside the onCreated function like so:
Template.myTemplate.onCreated(function () {
Meteor.call('myFunction', 'my', 'params', function (err, result) {
if (err) {
// do something about the error
} else {
Session.set('myData',result);
}
});
});
Template.myTemplate.helpers({
myData: function () {
Session.get('myData')
}
});
This won't fix the issue with ReactiveTable, however.
If the collection you're trying to display is only used for this single page, you could put the aggregation inside the publish function so that minimongo contains only the documents that match the aggregation and therefore the correct documents will appear in your ReactiveTable.
I am using Reactive-table to display paginated data in my meteor.js app as shown below, yet data displayed in Reactive-table is dependent on on specific user event (Selecting client, project, date range and clicking on the submit button). So I was wondering if it is possible to trigger template.helpers >> myCollection function from the 'submit form' event? OR is it better to define a global variable to store data returned from user query based on the user (client, project, date range selection) then make this global variable the return from the myCollection function?
I have tried researching how to call .helpers function from an template.events event but couldn't find any information. So any help on which approach is better and if calling the .events function is better then how to do that, will be highly appreciated. Thanks.
Below is the code I have in my app:
Template.detailedreport.rendered = function() {
Session.set("dreport_customer", "");
Session.set("dreport_project", "");
Session.set("dreport_startDate", new Date());
Session.set("dreport_endDate", new Date());
$('.set-start-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-end-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-start-date').on("dp.change",function (e) {
Session.set("dreport_startDate", $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
});
$('.set-end-date').on("dp.change",function (e) {
Session.set("dreport_endDate", $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
});
};
Template.detailedreport.helpers({
customerslist: function() {
return Customers.find({}, {sort:{name: -1}});
},
projectslist: function() {
return Projects.find({customerid: Session.get("dreport_customer")}, {sort:{title: -1}});
},
myCollection: function () {
var now = Session.get("dreport_startDate");
var then = Session.get("dreport_endDate");
var custID = Session.get("dreport_customer");
var projID = Session.get("dreport_project");
Meteor.call('logSummary', now, then, projID, custID, function(error, data){
if(error)
return alert(error.reason);
return data;
});
}
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: '0._id.day', label: 'Day' },
{ key: '0.totalhours', label: 'Hours Spent'}
]
};
}
});
Template.detailedreport.events({
'submit form': function(e) {
e.preventDefault();
var now = $('.set-start-date').data('DateTimePicker').getDate().toLocaleString();
var then = $('.set-end-date').data('DateTimePicker').getDate().toLocaleString();
var custID = $(e.target).find('[name=customer]').val();
var projID = $(e.target).find('[name=project]').val();
//Here is the problem as I am not sure how to refresh myCollection function in .helpers
},
'change #customer': function(e){
Session.set("dreport_project", "");
Session.set("dreport_customer", e.currentTarget.value);
},
'change #project': function(e){
Session.set("dreport_project", e.currentTarget.value);
}
});
Template:
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=myCollection settings=settings}}
</div>
Server:
Meteor.methods({
logSummary: function(startDate, endDate, projid, custid){
//Left without filtering based on date, proj, cust for testing only...
return Storylog.find({});
}
});
Template helpers are reactive, meaning that they will be recomputed if their dependencies change. So all you need to do is update their dependencies and then the myCollection helper will be recomputed.
Replace your comment // Here is the problem... with:
Session.set('dreport_endDate', then);
Session.set('dreport_startDate', now);
Session.set('dreport_project', projID);
Session.set('dreport_customer', custID);
I'm using a bootstrap3 pretty-fullcalendar in a project and pre blaze, when I changed some properties of an event (such as color) it was immediately reflected in the display on the calendar. Now, when I change the attribute, I have to reload the calendar manually to have the change show up.
I'm instantiating the calendar in the template render function as
Template.packLayout.rendered = function(){
$('#calendar').fullCalendar({
//dayClick:function( date, allDay, jsEvent, view ) {
// Requests.insert({title:'Request',start:date,end:date,color:'red',className:'todo'});
// Session.set('lastMod',new Date());
//},
eventClick:function(reqEvent,jsEvent,view){
Session.set('editingReqEvent',reqEvent.id);
Session.set('showEditEvent',true);
},
eventDrop:function(reqEvent){
Requests.update(reqEvent.id, {$set: {start:reqEvent.start,end:reqEvent.end}});
Session.set('lastMod',new Date());
},
events: function(start, end, callback) {
var events = [];
reqEvents = Requests.find();
reqEvents.forEach(function(evt){
event = {id:evt._id,title:evt.title,start:evt.start,end:evt.end,color:evt.color};
events.push(event);
})
callback(events);
},
editable:true,
weekMode: 'liquid'
});
}
Has something changed that would make this happen?
Here is how i managed to get it working:
1) keep your calendar code as per "rendered"
Template.calendar.rendered = function () {
console.log('Calendar - running redered');
Session.set('calendarTemplateRendered', true);
var entries = Calendar.find().fetch(),
$calendar = $('#calendar');
$calendar.html('').fullCalendar({
header: {
left: '',
center: '',
right: ''
},
contentHeight: 1100,
defaultDate: '2014-01-12',
defaultView: 'agendaWeek',
editable: true,
selectable: true,
selectHelper: true,
select: function (start, end) {
var title = prompt('Event Title:');
var eventData;
if (title) {
eventData = {
title: title,
start: start,
end: end
};
$('#calendar').fullCalendar('renderEvent', eventData, true); // stick? = true
}
$('#calendar').fullCalendar('unselect');
},
events: entries
});
Add a autorun:
Deps.autorun(function () {
if (Session.equals('calendarTemplateRendered', false) ||
!calendarSubs.ready() ||
typeof Calendar === 'undefined') {
console.log('exiting because there is no objects to process');
return;
}
console.log('trying to autorun');
var entries = Calendar.find().fetch(),
$calendar = $('#calendar');
$calendar.fullCalendar('removeEvents');
$calendar.fullCalendar('addEventSource', entries);
$calendar.fullCalendar('rerenderEvents');
}
Blaze will do the rest for you - (redraw the UI properly). Now you can just modify your Calendar subscription as you like and it will work perfectly.
I am integrating fullCalendar in my meteor application. fullCalendar expects a specific data format. I can create that data from my Collection. However the data is no longer reactive.
What is a way I can make the data I translated from my Collection to an Array "reactive"?
Thanks.
Client html:
<template name="carpool_calendar">
<div id="calendar"></div>
</template>
Client JS:
Template.carpool_calendar.rendered = function () {
//initialize the calendar in this template
$('#calendar').fullCalendar({
events: function(start, end, callback) {
var events = [];
var calendarEvents = Carpool_Events.find();
calendarEvents.forEach(function (carpool_event) {
events.push({
title: carpool_event.owner,
start: carpool_event.eventDate
});
console.log("Event owner " + ": " + carpool_event.owner);
});
callback(events);
},
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
weekends: false, // will hide Saturdays and Sundays
editable: true
});
};
Updated Client JS (This is not quite right yet. Its recreating the calendar on every data change...the page gets longer and longer with new calendar instances):
Template.carpool_calendar.rendered = function () {
Meteor.autorun(function() {
//initialize the calendar in this template
$('#calendar').fullCalendar({
events: function(start, end, callback) {
var events = [];
var calendarEvents = Carpool_Events.find();
calendarEvents.forEach(function (carpool_event) {
events.push({
title: carpool_event.owner,
start: carpool_event.eventDate
});
console.log("Event owner " + ": " + carpool_event.owner);
});
callback(events);
},
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
weekends: false, // will hide Saturdays and Sundays
editable: true
});
})};
Client JS Fully working "reactive" fullcalendar:
Template.carpool_calendar.rendered = function () {
//initialize the calendar in this template
$('#calendar').fullCalendar({
events: function(start, end, callback) {
var events = [];
var calendarEvents = Carpool_Events.find();
calendarEvents.forEach(function (carpool_event) {
events.push({
title: carpool_event.owner,
start: carpool_event.eventDate
});
console.log("Event owner " + ": " + carpool_event.owner);
});
callback(events);
},
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
weekends: false, // will hide Saturdays and Sundays
editable: true
});
Meteor.autorun(function() {
var calendarEvents = Carpool_Events.find();
$('#calendar').fullCalendar('refetchEvents');
});
};
Like TimDog said, you can't give the UI element a reactive array, and let it take care of the rest. But another option is you could use Meteor.autorun so when your collection changes, it can trigger a JS function to make an updated array, thereby making it somewhat reactive.
I'm not sure how to use this calendar exactly, but adding this into your client side code might help.
Meteor.autorun(function() {
calendarEvents = Carpool_Events.find();
$('#calendar').fullCalendar({
events: function(start, end, callback) {
var events = [];
calendarEvents.forEach(function (carpool_event) {
events.push({
title: carpool_event.owner,
start: carpool_event.eventDate
});
});
callback(events);
}
});
});
This is part of a bigger question regarding how to properly create UI components for Meteor that ensure reactive data contexts. It's a very good question and one that's been asked before.
The short answer is that there is no standardized framework yet -- like a Meteor.UI smart package. In the interim, however, your best bet is to hack the fullCalendar widget using the {{#each}} helper source as your guide. You'll want to pay attention to how data elements are labeled with Spark:
'each': function (data, options) {
var parentData = this;
if (data && data.length > 0)
return _.map(data, function(x, i) {
// infer a branch key from the data
var branch = (x._id || (typeof x === 'string' ? x : null) ||
Spark.UNIQUE_LABEL);
return Spark.labelBranch(branch, function() {
return options.fn(x);
});
}).join('');
else
return Spark.labelBranch(
'else',
function () {
return options.inverse(parentData);
});
},