Showing posts with label Javascript. Show all posts
Showing posts with label Javascript. Show all posts

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>');

Wednesday, January 19, 2011

Type Ahead Searches with jQuery

I was working on a mobile app and wanted to try a type ahead search to easy the user typing on a phone keyboard. I was able to make the ajax calls using jQuery to get the data, but I ran into some race conditions. As the user typed I made an ajax call but sometimes the first ajax call would take longer than the second. Thus updating the results with the first ajax calls data, because it finished later.

Example:
I starting typing "b". Then it would fire off an ajax call to get all the data with the letter "b".

Next I typed "a". So my search input box looked like this "ba". And firing off another ajax call to get all the data with the letters "ba".

I didn't know this but you can put the ajax request in a variable and kill it. Check it out.

<script type="text/javascript">
var ajaxCall;
typeAheadSearch = function(){
if(ajaxCall != undefined){
ajaxCall.abort();
}

ajaxCall = jQuery.ajax({
url: "http://www.somedomain.com/typeAheadSearch/_results.cfm",
data:{search:jQuery("##search").val()},
success:function(data){
jQuery("##results").html(data);
}
});
};
</script>

I started a variable called "ajaxCall" to store the request in. The first thing I do in the typeAheadSearch() is kill the current request by calling abort(). The next thing that I didn't know you could is that jQuery's ajax() returns the request. This is so handy.

Thursday, December 30, 2010

Getting all the URL params in Javascript

I've been updating a lot of href's lately and found these functions to be very handy. They basically take all the pars in a string and put them in a JS object. There is other functions out there that get you 1 url param, but I wanted them all. Reason being, is I need to see if that param exists and do some more checks on it for business logic, then take all the pars and turn then into a querystring and add them back to href again.


getURLParameters = function(url){

var results = {};

if(url == ""){
var url = window.document.URL.toString();
}
if (url.indexOf("?") > 0){
var splitURL = url.split("?");
params = splitURL[1].split("&");

for (var i=0;i var param = params[i].split("=");
if (param[1] != "")
results[param[0]] = unescape(param[1]);
else
results[param[0]] = "";
}

}

return results;
};
getURL = function(url){

if(url == ""){
var url = window.document.URL.toString();
}

if(url.indexOf("?") > 0){
var splitURL = url.split("?");
url = splitURL[0];
}

return url;
};


getURL() actually serves two purposes. If a url isn't passed in get the current url. And it also cleans off all of params so all you get the base url.

ColdMVC: AJAX call

Trying to make ajax calls with ColdMVC was a little difficult for me because I struggle with routes, but I found a way. Here is what I did to make an AJAX call with ColdMVC.

First I created a function on a Controller so I can return "hi" back from the AJAX call. Notice I have @view json.cfm above the function. Since ColdMVC uses routes I need to send the data to a view and cfoutput it on the page.

TestController

/**
* @accessors true
* @extends coldmvc.controller
*/
component {

/**
@view json.cfm
*/
function testMethod(){
params.json = "hi";
}

}


Here is json.cfm. All that I do with it is output the variable "json".

json.cfm

<cfoutput>#params.json#</cfoutput>


And lastly I preform my AJAX call using jQuery. In the url setting of jQuery.ajax() I just link to the controller. In my case, my controller's name is TestController, so my route with just be "test" and then I can change out the method. In my case I am calling the "testMethod" on the controller.

ajaxCall = function(){

jQuery.ajax({
url:"#coldmvc.link.to('/test')#/test?returnformat=json",
type:"GET",
success:function(data){
jQuery("body").html(data);
}
});
};


If you want to return json back just go to json.cfm and wrap params.json in SerializeJSON(). Then in your ajax call, do jQuery.parseJSON(). This is the only way I can think of right now to make an ajax call with ColdMVC, if there are any other techniques I would be more then happy to listen.

Monday, March 23, 2009

javascript for each loop

I ran into an issue today with the javascript for each loop. The for each loop would run one extra time and return the value "each" for the variable "field" in example below.

Example 1
for(var field in array){
//some code
}

To solve this I switched up the for each loop to do it this way instead.

Example 2
array.each(function(field){
//some code
});

Not sure what the difference is between Example 1 and Example 2.