How to keep data "reactive" when creating an array from a Collection - meteor

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);
});
},

Related

agendaWeek: events not shown

what I'm possibly doing wrong!?
I do NOT see my events when 'defaultView: agendaWeek' (or agendaDay).
PS: basicWeek (or basicDay) works just fine.
BUT: when clicking the 'month' view: ALL events are showing correctly in 'month' view! Then, when again clicking 'week' (or Day) I see them ALL correctly as agendaWeek (or agendaDay) view!
I'm running fullcalendar: 3.6.2
PS: sorry, posted same issue in non-related forum
must miss a stupid thing - thanks for your time and hints, ed
<script type="text/javascript">
(function ($) {
#if (Model.tagColorsEnabled && Model.tagColors != null) {
<text>
var tagSet = [
#foreach (var tc in Model.tagColors)
{
#Display(tc)
}
];
var tagIndex = [];
for (var tag in tagSet) {
tagIndex[tagSet[tag].slug] = tag;
}
</text>
}
$('#calendar').fullCalendar({
locale: '#culture',
timeFormat: 'HH:mm', //eddie timeFormat: 'HH:mm{-HH:mm}',
slotLabelFormat: 'HH:mm', //eddie timeFormat: 'HH:mm{-HH:mm}',
// height: 500, //added by eddie
// allDay: false, // eddie
header: {
left: 'prev,next today',
center: 'title',
right: 'month, agendaWeek, agendaDay',
// right: 'month, agendaWeek, agendaDay' // eddie
},
eventRender: function (event, element) {
var colors = { bg : event.defaultBackgroundColor, br: event.defaultBorderColor, fg: event.defaultTextColor };
#if (Model.tagColorsEnabled)
{
<text>
var min = 100;
for (var klassNdx in event.className) {
var klass = event.className[klassNdx];
if (klass.substring(0, 4) === "tag-") {
var entry = tagIndex[klass.substring(4)];
if (entry !== undefined) {
if (entry < min) {
colors = tagSet[entry];
min = entry;
}
}
}
}
</text>
}
$(element)
.css("background-color", colors.bg)
.css("color", colors.fg)
.css("border-color", colors.br);
},
editable: false,
events: {
url: '#Url.Content("~/_Calendar/" + Model.queryId)',
},
// defaultView: '#Model.defaultView',
// defaultView: 'basicWeek',
defaultView: 'agendaWeek',
weekends: #Model.showWeekends
//viewRender: function (v, e) { alert("Rendering view"); }
});
})(jQuery);
</script>
solved - sort of!
When using 'defaultView: basicWeek / agendaWeek'; fact is that I get different DateTime info like:
basicWeek: '2017-1-06' --> which in turn works correctly!
agendaWeek: '2017-1-06T00:00:00' --> which results in an error!
When stripping off 'T00:.....' --> problem solved
I know that this is probably not the 'clean' solution BUT for the time being ..... will further check if time is available!
For now I like to thank ADyson for his advise and time spent.

fullcalendar - multiple sources to turn off and on

I've a calendar that I want to list various types of event on and enable a checkbox filter to show/hide those kind of events.
Is there a way to say on this action, ONLY load from 1 data-source AND remember that URL on month next/prev links?
I've started using eventSources, but it loads them all, rather than the one(s) I want.. Here's what I have.
var fcSources = {
all: {
url: tournament_url + '/getCalendar/?typefilter=[1,2]'
},
type1: {
url: tournament_url + '/getCalendar/?typefilter=[1]'
},
type2: {
url: tournament_url + '/getCalendar/?typefilter=[2]'
}
};
These URLS all provide a json string of events, based on the types prrovided.
Here's my calendar:
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek'
},
firstDay: 1, // Monday = 1
defaultDate: new Date(), // Now
editable: true,
eventSources: [ fcSources.all ],
events: fcSources.all,
nextDayThreshold: '00:00:00',
... etc etc
Above my calendar I have this:
input type="checkbox" name="event_filter[]" value="type1" /> Type 1
input type="checkbox" name="event_filter[]" value="type2" /> Type 2
And finally , two Jquery fucntions.. one to get all the filters:
function getFilters(getName) {
var filters = [];
var checked = [];
$("input[name='"+getName+"[]']").each(function () {
filters.push( $(this).val() );
});
$("input[name='"+getName+"[]']:checked").each(function () {
checked.push( $(this).val() );
});
return [filters, checked];
}
and the last to load them:
$(".event_filter").on('click', function() {
var doFilters = getFilters('event_filter');
$('#calendar').fullCalendar( 'removeEvents' );
if (doFilters[0] === doFilters[1]) {
$('#calendar').fullCalendar( 'addEventSource', fcSources.all );
} else {
$.each(doFilters[1], function(myFilter, myVal) {
console.log(myVal);
$('#calendar').fullCalendar( 'addEventSource', fcSources.myVal );
});
}
// $('#calendar').fullCalendar( 'refetchEvents' );
});
Since the sources are all the same place and just different data on the URL, this approach could meet your needs. In the demo it just alerts the URL that is being tried but doesn't actually supply any data...
https://jsfiddle.net/gzbrc2h6/1/
var tournament_url = 'https://www.example.com/'
$('#calendar').fullCalendar({
events: {
url: tournament_url + '/getCalendar/',
data: function() {
var vals = [];
// Are the [ and ] needed in the url? If not, remove them here
// This could (should!) also be set to all input[name='event_filter[]'] val's instead of hard-coded...
var filterVal = '[1,2]';
$('input[name="event_filter[]"]:checked').each(function() {
vals.push($(this).val());
});
if (vals.length) {
filterVal = '[' + vals.join(',') + ']' // Are the [ and ] needed in the url? If not, remove here too
}
return {
typefilter: filterVal
};
},
beforeSend: function(jqXHR, settings) {
alert(unescape(settings.url));
}
}
});
// when they change the checkboxes, refresh calendar
$('input[name="event_filter[]"]').on('change', function() {
$('#calendar').fullCalendar('refetchEvents');
});
Try this!
$('#calendar').fullCalendar({
events: function( start, end, timezone, callback ) {
var checked = [];
$("input[name='event_filter[]']:checked").each(function () {
checked.push( $(this).val() );
});
var tournament_url = 'https://www.example.com';
$.ajax({
url: tournament_url + '/getCalendar/',
data: {typefilter: '['+checked.join(',')+']'},
success: function(events) {
callback(events);
}
});
}
});
$('input[name="event_filter[]"]').on('change', function() {
$('#calendar').fullCalendar('refetchEvents');
});
This works for me.

bootstrap3 pretty-fullcalendar meteor not reactive in blaze

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.

click prev/next and load new data

I have fullcalendar working fine loading data using json from a flatfile(s).
I would like to load each months data from that months file on clicking prev/next.
Using ajax? events: function( start, end, callback ) addEventSource - removeEventSource? other?
Allow that jQuery is still a work in progress for me.
UPDATE:
I have set:
viewDisplay: function(view) { var next = view.title; alert(next); },
which gives me Month Year on clicking next/prev
How to parse this to json-events.php
I tried [split next] events: "json-events.php?month=month",
nope...
This is not using php, but you can change the AJAX call in getCalData() to do it.
EventSource will be filled with the next data.
I hope this helps a little bit.
$(document).ready(function () {
var calendar = $('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'resourceMonth'
},
defaultView: 'resourceWeek',
eventSources: [ getCalData() ],
header: {
left: 'prev,next today',
center: 'title',
right: ''
},
viewDisplay: function (view) {
$('#calendar').fullCalendar('removeEvents');
$('#calendar').fullCalendar('addEventSource', getCalData());
$('#calendar').fullCalendar('rerenderEvents');
}
});
});
function getCalData() {
var source = [{}];
$.ajax({
async: false,
url: '/mysite/GetWeekData',
data: { myParam: $('#calendar').fullCalendar('getView').visStart },
success: function (data) {
$(data).each(function () {
source.push({
title: $(this).attr('title'),
start: $(this).attr('start'),
});
});
},
error: function () {
alert('could not get the data');
},
});
return source;
}
fullcalendar by default send 2 additional parameters when getting events, params[:start], params[:end]
u can use them to return queried events between these 2 values
you can just add View Render property to configuration Object of fullcalendar
viewRender: function(view, element) {
//console.debug("View Changed: ",view.start, view.end);
// events = loadevents();
jQuery(nativeelement).fullCalendar('removeEvents');
jQuery(nativeelement).fullCalendar('addEventSource', events);
}
now you can load month by month.
This syntax for angular2

Display only allDay events in a month view full calendar

How to display only allDay = true events in full calendar month view , and remaining non all Day events as usal in other views
You can do this by checking for view.name in a callback like eventRender. Take a look at this fiddle: http://jsfiddle.net/100thGear/vyKSZ/
Hope this helps!
$('#external-events div.external-event').each(function() {
// store data so the calendar knows to render an event upon drop
$(this).data('event', {
title: $.trim($(this).text()), // use the element's text as the event title
stick: true // maintain when user navigates (see docs on the renderEvent method)
});
// make the event draggable using jQuery UI
$(this).draggable({
zIndex: 1111999,
revert: true, // will cause the event to go back to its
revertDuration: 0 // original position after the drag
});
});
/* initialize the calendar
-----------------------------------------------------------------*/
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek'
},
editable: true,
droppable: true, // this allows things to be dropped onto the calendar
drop: function() {
// is the "remove after drop" checkbox checked?
if ($('#drop-remove').is(':checked')) {
// if so, remove the element from the "Draggable Events" list
$(this).remove();
}
},
eventDrop: function(event, delta, revertFunc) {
alert( event.id );
$.ajax({
type: "POST",
url: "${pageContext.request.contextPath}/task/periodic-task-update",
data : {
id : event.id ,
date :event.start.format()
},
success: function(data) {
if(data=='Task Period Succesfully Changed'){
toastr.success("Task Period Succesfully Changed.");
}else{
toastr.success("Something Wrong");
revertFunc();
}
},
error: function(data,textStatus,xhr) {
toastr.success("Something Wrong");
revertFunc();
}
});
},
events: [
<c:forEach var='periodicTask' items='${periodicTaskTemplates}'>
<c:forEach varStatus="i" begin = "1" end = "12">
{ id: '${periodicTask.id}', title: '${periodicTask.task}', start: new Date(y, '${i.index}', '${periodicTask.startDate}'), end: new Date(y, '${i.index}', '${periodicTask.lastDate}') ,type:'${periodicTask.description}',location:'${periodicTask.location.name}'},
</c:forEach>
</c:forEach>`
],
});

Resources