Thursday, December 22, 2011

Full Calender JS - unique event id's

I was working with full calendar js dragging and dropping my tasks on to the calendar as events for a timesheet app and ran into a wierd issue. When I loaded up the available tasks to be dragged I set the event object's id key equal to the task's id as shown below.


//---load in all of task to be dragged onto the calendar
var loadTasks = function(){

$('##external-events div.external-event').each(function() {

var self = $(this);

var eventObject = {
title: $.trim(self.text())
,id: $.trim(self.data("taskid")) //---RIGHT HERE
,taskID:$.trim(self.data("taskid"))
};

self.data('eventObject', eventObject);

self.draggable({
zIndex: 999,
revert: true,
revertDuration: 0
});
});

};


This will work if you don't drag the same task on the calendar twice. If you worked on a task from 8am to 9am and 1pm to 2pm if you grabbed the 8am to 9am event and attempting to resize it to 8am to 8.30am, full calendar js would move it and then it would also move the 1pm to 2pm event as well to 1pm to 1.30pm.

Every event object you put on the calendar should have a unique id.

If found this little snippet on the web that helped me make unique id's for event objects.

http://snipplr.com/view/2574/

var uid = (
function(){
var id=0;
return function(){
return id++ ;
};
}
)();


And on the "drop" variable of the full calendar js invoking I reset the event's id and rerendered the event.

, drop: function(date, allDay, jsEvent, ui) {

var originalEventObject = $(this).data('eventObject');
var copiedEventObject = $.extend({}, originalEventObject);
copiedEventObject.id = uid(); //---RIGHT HERE
copiedEventObject.start = date;
copiedEventObject.end = new Date(date).hours().add(-1);
copiedEventObject._end = new Date(date).hours().add(-1);
copiedEventObject.allDay = allDay;

$('##calendar').fullCalendar('renderEvent', copiedEventObject, true);

saveEvent($(this).data("taskid"),'','',copiedEventObject.start,copiedEventObject.end,'');

}

This fixed the issue.

Full Calendar JS

Using full calendar js I was able to make a timesheet app. The app involved dragging tasks from a left on to a day view of calendar. Simliar to this drag and drop events demo. I added trash can on the top of calendar to drag and drop events to be removed. Here is the js I used to make it happen. All of which is in document.ready. We will break down after this code snippet.


var loadTasks = function(){

$('##external-events div.external-event').each(function() {

var self = $(this);

var eventObject = {
title: $.trim(self.text())
,id: $.trim(self.data("taskid"))
,taskID:$.trim(self.data("taskid"))
};

self.data('eventObject', eventObject);

self.draggable({
zIndex: 999,
revert: true,
revertDuration: 0
});
});

};

var saveEvent = function(taskID,orgStartObj,orgEndObj,newStartObj,newEndObj){

var format = "yyyy-MM-dd HH:mm:ss";

$.ajax({
url:'/timesheet/saveEvent'
,type:'POST'
,data:{
orginalStartTime:orgStartObj.toString(format)
,orginalEndTime:orgEndObj.toString(format)
,newStartTime:newStartObj.toString(format)
,newEndTime:newEndObj.toString(format)
,taskID:taskID
}
,dataType:'json'
});

};

//---Check if inside the trashcan div
var draggedOverTrashCan = function(draggedItem, dropArea) {
var itemOffset = draggedItem.offset;
var trashCanOffset = $(dropArea).offset();

itemOffset.right = $(draggedItem.helper).outerWidth() + itemOffset.left;
itemOffset.bottom = $(draggedItem.helper).outerHeight() + itemOffset.top;

trashCanOffset.right = $(dropArea).outerWidth() + trashCanOffset.left;
trashCanOffset.bottom = $(dropArea).outerHeight() + trashCanOffset.top;

// Compare
if (itemOffset.right >= trashCanOffset.left
&& itemOffset.bottom >= trashCanOffset.top
&& itemOffset.top <= trashCanOffset.bottom
&& itemOffset.left <= trashCanOffset.right
){
return true;
}else{
return false;
}
};


$('##calendar').fullCalendar({
header: {left: 'prev,next today',center: 'title',right: 'month,agendaWeek,agendaDay'}
, editable: true
, firstHour: 6
, slotMinutes: 15
, defaultView: "agendaDay"
, aspectRatio: "1.60"
, year: #datePart("yyyy", now())#
, month: #datePart("m", now())-1#
, date: #datePart("d", now())#
, events: #serializeJSON(viewBag.events)#
, droppable: true
, drop: function(date, allDay, jsEvent, ui) {

var originalEventObject = $(this).data('eventObject');
var copiedEventObject = $.extend({}, originalEventObject);
copiedEventObject.id = uid();
copiedEventObject.start = date;
copiedEventObject.end = new Date(date).hours().add(-1);
copiedEventObject._end = new Date(date).hours().add(-1);
copiedEventObject.allDay = allDay;

$('##calendar').fullCalendar('renderEvent', copiedEventObject, true);

saveEvent($(this).data("taskid"),'','',copiedEventObject.start,copiedEventObject.end,'');


}
, eventResize: function(event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view){

var newStart = event.start;
var newEnd = event.end;
var orgStart = new Date(newStart);
var orgEnd = new Date(newEnd).addMinutes(minuteDelta * -1);

saveEvent(event.taskID,orgStart,orgEnd,newStart,newEnd);

}
, eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view ){

var newStart = event.start;
var newEnd = event.end;
var orgStart = new Date(newStart).addDays(dayDelta * -1);
orgStart.addMinutes(minuteDelta * -1);
var orgEnd = new Date(newEnd).addDays(dayDelta * -1);
orgEnd.addMinutes(minuteDelta * -1);

saveEvent(event.taskID,orgStart,orgEnd,newStart,newEnd);

}
,eventDragStop:function(event, jsEvent, ui, view){


if (draggedOverTrashCan(ui, $('div##trash-can'))) {

var format = "yyyy-MM-dd HH:mm:ss";

$.ajax({
url:'/timesheet/deleteEvent'
,type:'POST'
,data:{
startTime:event.start.toString(format)
,endTime:event.end.toString(format)
,taskID:event.taskID
,userID:'#viewBag.userID#'
}
,dataType:'json'
,success:function(){
$("##calendar").fullCalendar('removeEvents', event._id);
}
});


}
}
});

//--- add a trash can div to the top of the calendar
$('##calendar').children('.fc-content').prepend('<div id="trash-can" style="border: 2px solid ##C1454B;padding:15px 100px 15px;border-radius:5px;background:##DD9094;text-align:center;">Drag Events Here To Remove</div>');

//---make the tasks available.
loadTasks();


loadTasks() - is used to make the tasks in the left nav drag and droppable for the calendar. The task are created in ColdFusion like this.

<div id="external-events">
<ul>
<cfloop query="viewBag.tasks">
<li>
<div class="external-event ui-draggable" data-taskID="#viewBag.tasks.taskID#">
<div><strong>###viewBag.tasks.taskID# #viewBag.tasks.name#</strong></div>
<div><strong>Status: </strong>#viewBag.tasks.taskStatusName#</div>
</div>
</li>
</cfloop>
</ul>
</div>


Then document.ready I do this:

var loadTasks = function(){

//--- loop through each draggable task
$('##external-events div.external-event').each(function() {

var self = $(this);

//---set the event Object's title which will be seen on the calendar, set the id so that each event on the calendar is unique, and set any other values you want to be carried along with that event. in the case below taskID is an extra value I want. It will be used later to saveACalendarEvent.
var eventObject = {
title: $.trim(self.text())
,id: $.trim(self.data("taskid"))
,taskID:$.trim(self.data("taskid"))
};

self.data('eventObject', eventObject);

//---make the task draggable
self.draggable({
zIndex: 999,
revert: true,
revertDuration: 0
});
});

};

saveEvent() - is used to make an ajax call with the taskID, orginal start time, orginal end time, new start time, and new end time. I send the orginal and new dates so if I am resizing a task or moving the task on calendar I want to make sure I delete the old record.


var saveEvent = function(taskID,orgStartObj,orgEndObj,newStartObj,newEndObj){

var format = "yyyy-MM-dd HH:mm:ss";

$.ajax({
url:'/timesheet/saveEvent'
,type:'POST'
,data:{
orginalStartTime:orgStartObj.toString(format)
,orginalEndTime:orgEndObj.toString(format)
,newStartTime:newStartObj.toString(format)
,newEndTime:newEndObj.toString(format)
,taskID:taskID
}
,dataType:'json'
});

};

draggedOverTrashCan() - checks to see if the user drags a calendar event over the trash can. The trash can code gets prepended later in code.


var draggedOverTrashCan = function(draggedItem, dropArea) {
var itemOffset = draggedItem.offset;
var trashCanOffset = $(dropArea).offset();

itemOffset.right = $(draggedItem.helper).outerWidth() + itemOffset.left;
itemOffset.bottom = $(draggedItem.helper).outerHeight() + itemOffset.top;

trashCanOffset.right = $(dropArea).outerWidth() + trashCanOffset.left;
trashCanOffset.bottom = $(dropArea).outerHeight() + trashCanOffset.top;

// Compare
if (itemOffset.right >= trashCanOffset.left
&& itemOffset.bottom >= trashCanOffset.top
&& itemOffset.top <= trashCanOffset.bottom
&& itemOffset.left <= trashCanOffset.right
){
return true;
}else{
return false;
}
};

invoking the full calendar js
drop() - handles dragging new events from off the calendar onto the calendar.

eventResize(),eventDrop() - handles just that. Full calendar give you back the deltas from where the event used to be on the calendar. With those we can create the orginal date.

eventDragStop() - used when event has stopped being dragged. For my use case, I want to know if they drug the task over the tash can so I can do an ajax call and delete the task.

$('##calendar').fullCalendar({
header: {left: 'prev,next today',center: 'title',right: 'month,agendaWeek,agendaDay'}
, editable: true
, firstHour: 6
, slotMinutes: 15
, defaultView: "agendaDay"
, aspectRatio: "1.60"
, year: #datePart("yyyy", now())#
, month: #datePart("m", now())-1#
, date: #datePart("d", now())#
, events: #serializeJSON(viewBag.events)#
, droppable: true
, drop: function(date, allDay, jsEvent, ui) {

var originalEventObject = $(this).data('eventObject');
var copiedEventObject = $.extend({}, originalEventObject);
copiedEventObject.start = date;
copiedEventObject.end = new Date(date).hours().add(-1);
copiedEventObject._end = new Date(date).hours().add(-1);
copiedEventObject.allDay = allDay;

$('##calendar').fullCalendar('renderEvent', copiedEventObject, true);

saveEvent($(this).data("taskid"),'','',copiedEventObject.start,copiedEventObject.end,'');


}
, eventResize: function(event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view){

var newStart = event.start;
var newEnd = event.end;
var orgStart = new Date(newStart);
var orgEnd = new Date(newEnd).addMinutes(minuteDelta * -1);

saveEvent(event.taskID,orgStart,orgEnd,newStart,newEnd);

}
, eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view ){

var newStart = event.start;
var newEnd = event.end;
var orgStart = new Date(newStart).addDays(dayDelta * -1);
orgStart.addMinutes(minuteDelta * -1);
var orgEnd = new Date(newEnd).addDays(dayDelta * -1);
orgEnd.addMinutes(minuteDelta * -1);

saveEvent(event.taskID,orgStart,orgEnd,newStart,newEnd);

}
,eventDragStop:function(event, jsEvent, ui, view){

if (draggedOverTrashCan(ui, $('div##trash-can'))) {

var format = "yyyy-MM-dd HH:mm:ss";

$.ajax({
url:'/timesheet/deleteEvent'
,type:'POST'
,data:{
startTime:event.start.toString(format)
,endTime:event.end.toString(format)
,taskID:event.taskID
,userID:'#viewBag.userID#'
}
,dataType:'json'
,success:function(){
$("##calendar").fullCalendar('removeEvents', event._id);
}
});


}
}
});

This piece prepends a div to the top of the calendar to be used as a trash can.

$('##calendar').children('.fc-content').prepend('<div id="trash-can" style="border: 2px solid ##C1454B;padding:15px 100px 15px;border-radius:5px;background:##DD9094;text-align:center;">Drag Events Here To Remove</div>');

Railo Annotations

I've spent some time using Railo Coldfusion and recently purchased a home. I haven't had much time to write so I am catching up now.

I got a message that Railo is finally going to support annotations. Why is this a good thing? Because I can now use ColdMVC.

Tuesday, July 5, 2011

cfsavecontent vs CSS when wanting to add styles.

I ran into some confrontation when I decided to use a cfsavecontent and write some styles in <style/> tags and do a <cfhtmlhead/>. Another developer prefers everything in a .css file. While, yes all styles are in one place in the app, unneeded styles are being loaded on pages that don't need them.

I prefer the other route of doing styles in style tags on the page I am working in and then adding them to the head section. Don't get me wrong I still have some global styles set in .css files. Most of the pages I am working on are all custom interfaces and the .css file(s) would be unnecessary large between pages. Not to mention lots and/or large .css files bring down browser load time.

Thoughts?

Submit button name when posting form in JS

I am sure plenty of people out there have had this issue. So I had a submit button named "submit" and tried to do form submit in js by doing $("#theform").submit(); and I kept getting this error saying that .submit() wasn't a function. After some digging apparently if you name a submit button in a form "submit" it overwrites the submit() with submit button element. Not cool.

Thursday, June 23, 2011

Developer Phases

1. How do I do that? (Entry)
2. Yeah, I can do that. (Mid)
3. Sigh. What was I thinking when I wrote this? (Adv)

Wednesday, June 15, 2011

Hover Bike

I am a big fan of hover technology and want to share this break through with a hover bike.

http://gearpatrol.com/blog/2011/06/09/bmw-powered-twin-rotorhoverbike/

It kind of reminds of a speeder bike from Star Wars.