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.

Writing text over an image

I know alot of people have already done this, but I thought I would share writing text over image as well. Only because the first time you do it, it's really cool.

Read in the image

<cfimage name = "local.image" action="read" source="test.jpg"/>


Turn on anti-aliasing ("softens jagged edges")

<cfset ImageSetAntiAliasing(local.image)/>


Set the text color

<cfset ImageSetDrawingColor(local.image, "000000")/>


Set extra attributes

<cfset local.attrs = {
Font = 'Arial',
Size = 36,
Style = 'bold'
}/>


Draw over the image (image,text,x-position,y-position,attributes)

<cfset ImageDrawText(local.image, "Hello World!", 10, 10, local.attrs)/>

SQL replace() blank stuff

Will never return 0

select replace('','',0)


Will return zero if the value is ''

select case when '' = '' then 0 else 1 end

SQL Selects ='s

Instead of writing a select in a query like this


select
[user].id,
[user].name,
snowboard.name as snowboard
from [user]
inner join snowboard on snowboard.id = [user].snowboard_id


try this


select
[user].id,
[user].name,
snowboard = snowboard.name
from [user]
inner join snowboard on snowboard.id = [user].snowboard_id


It helps all the columns line up and read nicer.

Wednesday, April 20, 2011

HQL vs. SQL

I've been playing with HQL for awhile now and want to share my thoughts of it against SQL. Some developers are hesitant to switch to HQL because of the querying. I don't blame them. Writing HQL can be tougher than SQL. After fighting threw some HQL queries I have found that SQL still has its place in an HQL application. Here are some of my findings:

1. List pages and aggregates are faster and easier to write in SQL. Most of the time the data points that list pages and aggregates are going to be displayed are already hashed out and don't need to be changed much.

2. HQL is great for getting and setting simple data quickly. SQL takes too much energy and time to insert or get a piece of data. The SQL queries you write are not going to be flexible enough for the new fields people are going to request.

3. Some routines are more efficient to be ran in SQL. HQL can be slow and it brings back too much data sometimes.

With the above findings I have organized my code to do saves and single entity gets with HQL and lists and routines with SQL. With this combo I have found it to be quick to get and set the data I need. It does take knowledge of both HQL and SQL to do this, but the languages are very similar and the learner curve isn't too bad. Here is an earlier post about the similarities. I hope this brings down the hesitation for the SQL people to try and use HQL for what it's good for.

I would like to hear peoples thoughts on this matter. It seems to come up a lot in HQL conversations, so comment away.

ColdMVC: Quick start now available

After a long wait, ColdMVC finally has a quick start guide. Check it out here: http://www.coldmvc.com/quickstart.

Monday, March 28, 2011

ColdMVC: get an object's parents

Awhile back I posted on flattening an array of objects with Parent-Child relationship (link) and have been using it alot. But one issue I had with it was when I was looping through the array of objects I didn't know who my parents were for the current object I was working with. I already knew how deep thanks to the treeDepth property. I made this function to recursively go up the tree of objects and bring back an array of parent objects for an object.


<cffunction name="getObjectsParents" access="public" output="false" returntype="array">
<cfargument name="object" required="true"/>
<cfargument name="result" required="false" default="#[]#"/>

<cfif isObject(arguments.object.parent())>
<cfset arrayPrepend(arguments.result,arguments.object.parent())/>
<cfreturn getObjectsParents(arguments.object.parent(),arguments.result)/>
<cfelse>
<cfreturn arguments.result/>
</cfif>

</cffunction>

Thursday, February 3, 2011

ColdMVC: Number Tag

I've playing around with mobile web apps lately. ColdMVC has been a big help in getting them done quickly and "in the cloud".

I've been looking for a house since the market is so good right now and one thing I noticed when searching for houses online is the when you type in a price range only the keypad shows up on my phone. No letters. Just numbers. It's really nice. I viewed the source when I got back to my laptop and found out they were doing it with <input type="number" >. I decided to hack a ColdMVC tag together to hanlde this.

Here is how you call the tag...

<c:number name="miles" value="#service.miles()#">


Here is my hacked in logic...

<cfparam name="attributes.class" default="input"/>
<cfparam name="attributes.value" default=""/>
<cfif thisTag.executionMode eq "end">
<cfoutput>
<cfsavecontent variable="attributes.field">
<input type="number" name="#attributes.name#" title="#attributes.name#" value="#attributes.value#" class="#attributes.class#"/>
</cfsavecontent>
</cfoutput>

<cfset thisTag.generatedContent = coldmvc.form.field(argumentCollection=attributes) />
</cfif>


The above logic doesn't supporting binding, but it works to use. It would be cool if this tag was managed my ColdMVC HTMLHelper.cfc then I would have to worry about the attributes.

Wednesday, February 2, 2011

ColdMVC: Binding

I really like the binding option in the form tag of ColdMVC. It takes an object from the param scope, prefixes all the elements within it, with model's name (ex. user.first_name), and sets the value from the object. It's very handy. Here is an example of what it currently does:

<c:form controller="user" action="save" bind="user">
<c:hidden name="id"/>

<c:input name="first_name"/>
<c:input name="last_name"/>

<c:buttons>
<c:submit label="save"/>
</c:buttons>

</c:form>

If you wanted to bind the form to 2 or more objects it would be cool if you could use a bind tag and group a set of elements within a form.

<c:form controller="student" action="save">

<c:bind key="user">
<c:hidden name="id"/>

<c:input name="first_name"/>
<c:input name="last_name"/>
</c:bind>

<c:bind key="student">

<c:input name="student_number"/>

</c:bind>

<c:buttons>
<c:submit label="save"/>
</c:buttons>

</c:form>

Or even better on the tag itself. (I think this available already, but I am not sure).

<c:form controller="student" action="save">

<c:hidden name="id" bind="user"/>

<c:input name="first_name" bind="user"/>
<c:input name="last_name" bind="user"/>
<c:input name="student_number" bind="student"/>

<c:buttons>
<c:submit label="save"/>
</c:buttons>

</c:form>

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.

Tuesday, January 4, 2011

CF9 computed properties

I wanted to make a computed property in CF9 just like you can on a sql table (computed column). Dan Vega had a nice how-to awhile back but it didn't show a complex example.

In my project I had a "project" class that had many ratings on it (Example: rating of 1-5, 1 being bad and 5 being great) and I wanted to have a computed property that gave me the average rating of a project. Here is what my hib file looks like for the "project" class.


<class entity-name="Project" lazy="true" name="cfc:myproject.app.model.Project" table="`Project`">
<id name="id" type="int">
<column length="10" name="ID" />
<generator class="identity" />
</id>
<property name="name" type="string">
<column name="Name" sql-type="varchar(max)" />
</property>
<property name="description" type="string">
<column name="Description" sql-type="varchar(max)" />
</property>
<property type="float"
formula="(select (sum(r.rank)*100.0) / (count(pr.id) *100.0) from project as p inner join project_rating as pr on pr.project_id = p.id inner join rating as r on r.id = pr.rating_id where pr.project_id = id)"
name="rating"/>
</class>

Some key things to point out:
1. The formula is a sql query not a hql query.
2. I wrapped my entire statement in (), don't know why but it worked.
3. You always start the sql statement with the table of the class you are working on. Reason being is when you get to the "where" clause notice this piece "pr.project_id = id". "id" is not aliased because it grabs the current id of the object that is running the formula against. This is how the query will only run for single object and not all of the other db records in the project table.
4. Alias every thing except the "where id" part.

I struggled through these above. Hope this helps someone.