Skip to content

Commit 4613be1

Browse files
committed
Implemented example insert policy function
Refactored Event class to maintain start and end times in minutes as well as "07:30" format
1 parent 9d963bd commit 4613be1

File tree

5 files changed

+289
-137
lines changed

5 files changed

+289
-137
lines changed

js/jquery.pnotify.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
notice_icon: "ui-icon ui-icon-info",
2424
info: "",
2525
info_icon: "ui-icon ui-icon-info",
26-
success: "ui-state-default",
26+
success: "ui-state-success",
2727
success_icon: "ui-icon ui-icon-circle-check",
2828
error: "ui-state-error",
2929
error_icon: "ui-icon ui-icon-alert",

js/rc_calendar.js

Lines changed: 137 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
retrieve : function(){ return [] },
4242
remove : function(){},
4343

44-
// default insert_policy is simple - allow overlaps:
45-
insert_policy : function( event_list, evt ){event_list[evt.attr.id] = evt;},
44+
// default insert_policy is simple - do nothing, allow overlaps:
45+
insert_policy : function( event_list, evt, end_of_day ){},
4646

4747
min_time : '07:00', // 7am
4848
max_time : '20:00', // 8pm
@@ -131,28 +131,35 @@
131131
function Calendar( element, options )
132132
{
133133
var t = this;
134+
var global_timeformat;
134135

135136
//--- Exports ---
136137
t.element = element;
137138
t.options = options;
139+
t.options.baseminutes = convert_to_minutes(t.options.min_time); // used for event rendering
140+
141+
global_timeformat = t.options.min_time[-1] == 'm'; // true if times in am/pm format
142+
// almost certainly a localization
143+
// problem here
138144

139145
//
140146
// look for <resource> elements within the calendar entity,
141147
// create a resource object for each, and bind it to the calendar
142148
//
143149
t.resources = [];
144150
element.find('resource').each( function(i,element) {
145-
t.resources[$(this).attr('id')] = new rc_resource( $(this), t.options.insert_policy );
151+
t.resources[$(this).attr('id')] = new rc_resource( $(this), t.options.get_open_time );
146152
});
147-
t.resources['unassigned_event_resource'] = new rc_resource( $('#unassigned_event_resource'), function(){} );
153+
t.resources['unassigned_event_resource'] = new rc_resource( $('#unassigned_event_resource'), function(){return[t.options.min_time,t.options.max_time]} );
148154

149155
t.initialize_events = function(){
150156
t.eventmanager = new rc_EventManager( t.options.retrieve,
151157
t.options.persist,
152158
t.options.remove,
159+
t.options.insert_policy,
153160
t.resources,
154161
t[t.options.render_event] );
155-
}
162+
}
156163

157164

158165
//--- Public Methods ---
@@ -176,8 +183,8 @@ function Calendar( element, options )
176183
var dragged = $(ui.draggable[0]);
177184

178185
// event.target gets confused if the viewport is smaller than the droppable,
179-
// and the scrolled droppable 'virtually' overlaps another viewport containing
180-
// droppables. This function call ensures that the visible droppable is used
186+
// and the scrolled droppable 'virtually' overlaps droppables from another viewport.
187+
// This function call ensures that the visible droppable is used
181188
var target_id = findDroppable( ui );
182189

183190
// the other half to this problem is that this function gets called twice,
@@ -272,16 +279,8 @@ function Calendar( element, options )
272279
});
273280
}
274281

275-
var elapsed_mins= evt.attr.prep_time + evt.attr.duration + evt.attr.cleanup_time;
276-
277-
evt.attr.end = addMinutes_timeOfDay(
278-
evt.attr.start,
279-
elapsed_mins,
280-
evt.attr.start ).newtime;
281-
282-
283-
evt.attr.t_offset = ~~(diffMinutes_timeOfDay( t.options.min_time, evt.attr.start ) / t.options.interval * t.options.intervalpixels);
284-
evt.attr.t_height = ~~((elapsed_mins / t.options.interval) * t.options.intervalpixels) -2;
282+
evt.attr.t_offset = ~~(evt.attr.startmins - t.options.baseminutes) / t.options.interval * t.options.intervalpixels;
283+
evt.attr.t_height = ~~(((evt.attr.endmins-evt.attr.startmins) / t.options.interval) * t.options.intervalpixels) -2;
285284
evt.attr.t_prepad = ~~((evt.attr.prep_time / t.options.interval) * t.options.intervalpixels);
286285
evt.attr.t_postpad= ~~((evt.attr.cleanup_time / t.options.interval) * t.options.intervalpixels);
287286

@@ -290,8 +289,13 @@ function Calendar( element, options )
290289

291290
newev.find('.deleteevent').click(function(event){
292291
event.stopPropagation();
293-
confirm( "You are about to delete this event; this action can not be undone! Confirm deletion?",
294-
t.eventmanager.deleteEvent, evt.attr.id );
292+
293+
if( confirm("You are about to delete this event; this action can not be undone! Confirm deletion?") ){
294+
t.eventmanager.deleteEvent( evt.attr.id );
295+
}
296+
297+
298+
295299
});
296300

297301
if( !evt.attr.locked ){
@@ -319,7 +323,7 @@ function Calendar( element, options )
319323
var snapheight = ~~((ui.size.height+(t.options.intervalpixels/2))/t.options.intervalpixels);
320324
var total_mins = snapheight*t.options.interval;
321325

322-
evt.attr.duration = total_mins - (evt.attr.prep_time + evt.attr.cleanup_time);
326+
evt.set_duration( total_mins - (evt.attr.prep_time + evt.attr.cleanup_time) );
323327
ui.helper.css('height',snapheight * t.options.intervalpixels -2 + 'px');
324328
t.view_week_render_event(evt);
325329
t.options.persist(evt);
@@ -390,8 +394,6 @@ function Calendar( element, options )
390394
// Called during dragging and on drop, this function returns the id of a div beneath
391395
// the draggable object, if that div is visible and a droppable target.
392396
//
393-
// [TODO - problem dragging new event onto scrolled Narwhal, ends up in Orca]
394-
//
395397
function findDroppable( ui )
396398
{
397399
retval = false;
@@ -483,11 +485,36 @@ function Calendar( element, options )
483485
// -----------
484486
// Basic event characteristics that can be extended with application-specific code
485487
//
486-
488+
// [REVIEW]
489+
// Ambivalent about global_timeformat; could be eliminated here and managed
490+
// only in convert_to_time( t, ampm ) helper function.
491+
//
487492
function rc_Event( options )
488493
{
489494
var t = this;
490495

496+
t.set_start_time = function( s ) {
497+
if( "number" == typeof(s) ){
498+
t.attr.startmins = s;
499+
t.attr.start = convert_to_time( s, global_timeformat ) ;
500+
t.attr.ampm = global_timeformat;
501+
}
502+
else {
503+
t.attr.start = s;
504+
t.attr.startmins = convert_to_minutes( s );
505+
t.attr.ampm = ( 'm' == s[-1] ); // if true, show times in 12hr am/pm format
506+
}
507+
508+
t.attr.endmins = t.attr.startmins + t.attr.duration + t.attr.prep_time + t.attr.cleanup_time;
509+
t.attr.end = convert_to_time( t.attr.endmins, t.attr.ampm );
510+
}
511+
512+
t.set_duration = function( d ) {
513+
t.attr.endmins += ( d - t.attr.duration );
514+
t.attr.duration = d;
515+
t.attr.end = convert_to_time( t.attr.endmins, t.attr.ampm );
516+
}
517+
491518
var defaults = {
492519
id : new Date().getTime(),
493520
date : 1375416000000,
@@ -501,6 +528,11 @@ function rc_Event( options )
501528
};
502529

503530
t.attr = $.extend( true, {}, defaults, options );
531+
532+
// prepare internal representation of times
533+
t.set_start_time( t.attr.start );
534+
535+
return t;
504536
}
505537

506538

@@ -511,51 +543,35 @@ function rc_Event( options )
511543
// Manages updates to/from the server
512544
//
513545

514-
function rc_EventManager( retrieve_events, save_event, delete_event, resources, display )
546+
function rc_EventManager( retrieve_events, save_event, delete_event, insert_policy, resources, display )
515547
{
516-
var render = display;
517-
var persistEvent= save_event;
518-
var killEvent = delete_event;
548+
var render = display;
549+
var persistEvent = save_event;
550+
var killEvent = delete_event;
551+
var insert_policy = insert_policy;
552+
519553
var t = this;
520554

521-
t.Events = retrieve_events();
555+
t.Events = [];
556+
attrs = retrieve_events();
522557

523-
for( var i in t.Events ) {
524-
var evt = t.Events[i];
558+
for( var i in attrs ) {
559+
var evt = new rc_Event( attrs[i].attr );
560+
t.Events[evt.attr.id] = evt;
525561
if( undefined != resources[evt.attr.resource] ) {
526-
resources[evt.attr.resource].addEvent( evt, 'no_confirm' );
562+
resources[evt.attr.resource].addEvent( evt );
527563
render( evt );
528564
}
529-
else {
530-
// [TODO] - remove from list??
531-
}
532-
533-
}
534-
535-
t.createEvent = function( parent, resource, options ) {
536-
var new_event = new rc_Event( options );
537-
var id = new_event.attr.id;
538-
539-
t.Events[ id ] = new_event;
540-
541-
if( resource.addEvent( new_event ) ) {
542-
new_event.attr.resource = resource.id;
543-
new_event.attr.parent = parent.attr('id'); // div in which this event currently resides
544-
render( new_event );
545-
persistEvent( new_event );
546-
}
547-
548-
return new_event;
549565
}
550566

551567
t.moveEvent = function( evt, parent, old_resource, new_resource, options ) {
552568

553569
// have to update event attributes before attempting to move the event,
554570
// but have to be prepared to revert them if the new resource rejects the event
555571

556-
var stash = evt.attr;
572+
var stash = $.extend({},evt.attr);
557573

558-
evt.attr.start = options.start;
574+
evt.set_start_time( options.start );
559575
evt.attr.date = options.date;
560576
evt.attr.t_offset= options.t_offset;
561577

@@ -565,20 +581,66 @@ function rc_EventManager( retrieve_events, save_event, delete_event, resources,
565581
// lazy-evaluation of boolean tests ensures 'removeEvent' happens only for inter-resource moves
566582
//
567583
if(
568-
((old_resource.attr.id == new_resource.attr.id) && new_resource.addEvent( evt ))
569-
|| ((old_resource.attr.id != new_resource.attr.id) && new_resource.addEvent( evt ) && old_resource.removeEvent( evt ))
584+
((old_resource.id == new_resource.id) && new_resource.addEvent( evt ))
585+
|| ((old_resource.id != new_resource.id) && new_resource.addEvent( evt ) && old_resource.removeEvent( evt ))
570586
) {
571-
// event accepted
572-
evt.attr.resource = new_resource.id;
573-
evt.attr.parent = parent.attr('id');
574-
render( evt );
575-
persistEvent( evt );
576-
return true;
587+
588+
// event accepted, but may yet fail the insertion-overlap criteria test
589+
// unassigned event resource always accepts drop-ins
590+
if( 'unassigned_event_resource' == new_resource.id
591+
|| ! insert_policy( new_resource.listEvents( evt.attr.date ),
592+
evt,
593+
new_resource.get_open_time( evt.attr.date ),
594+
render,
595+
persistEvent ) ) {
596+
597+
evt.attr.resource = new_resource.id;
598+
evt.attr.parent = parent.attr('id');
599+
render( evt );
600+
persistEvent( evt );
601+
602+
if(isNaN(evt.attr.date)){
603+
rc_notify('Success',
604+
'Added '+evt.attr.ev_text+' to Unassigned Events list',
605+
'success');
606+
}
607+
else {
608+
var formatted_date = new moment( evt.attr.date ).format("dddd, Do MMM YYYY");
609+
rc_notify('Success',
610+
'Added '+evt.attr.ev_text+' to '+new_resource.attr.title+' at '+evt.attr.start+' on '+formatted_date,
611+
'success');
612+
}
613+
614+
return false;
615+
616+
}
617+
}
618+
619+
// failed to insert into resource, or failed to fit into calendar
620+
new_resource.removeEvent( evt );
621+
evt.attr = $.extend({},stash);
622+
623+
if( "none" == evt.attr.resource ) {
624+
killEvent( evt.attr.id ); // Was freshly dragged from the new events palette
577625
}
578626
else {
579-
evt.attr = stash;
580-
return false;
627+
old_resource.addEvent( evt );
628+
render( evt );
581629
}
630+
631+
return true;
632+
633+
}
634+
635+
t.createEvent = function( parent, resource, options ) {
636+
var new_event = new rc_Event( options );
637+
var id = new_event.attr.id;
638+
639+
t.Events[ id ] = new_event;
640+
641+
t.moveEvent( new_event, parent, resource, resource, options )
642+
643+
return new_event;
582644
}
583645

584646
t.deleteEvent = function( id ){
@@ -606,16 +668,7 @@ function rc_EventManager( retrieve_events, save_event, delete_event, resources,
606668
// 3: live Ajax + localStorage for offline working
607669

608670

609-
//
610-
// [TODO] - register per-day availability times for each resource
611-
// This accommodates different open/close times on different days, or
612-
// worker shifts, or holiday/vacation times.
613-
//
614-
// Implement regular hours function, and specific overrides
615-
//
616-
617-
618-
function rc_resource( resource_element, insert_policy ) {
671+
function rc_resource( resource_element, get_open_time ) {
619672
var t = this;
620673

621674
//
@@ -625,7 +678,8 @@ function rc_resource( resource_element, insert_policy ) {
625678
t.eventpool = {};
626679

627680
t.id = resource_element.attr('id');
628-
t.insert_policy = insert_policy;
681+
t.get_open_time = get_open_time;
682+
629683
t.attr = $.parseJSON(resource_element.attr('data-attr'));
630684

631685
//
@@ -634,8 +688,6 @@ function rc_resource( resource_element, insert_policy ) {
634688
// perform basic sanity checking such as ensuring that (for example)
635689
// a room can accommodate all of the attendees for a meeting
636690

637-
// [TODO - fix this so that Resources do not have to specify validation]
638-
639691
t.attr.validateFn = window[resource_element.attr('data-validate')];
640692
t.attr.validateParams = $.parseJSON(resource_element.attr('data-params'));
641693

@@ -660,35 +712,27 @@ function rc_resource( resource_element, insert_policy ) {
660712
// true : could add the event
661713
// false: unable to add the event
662714
//
663-
t.addEvent = function( event, no_confirm ){
664-
if( $.isFunction( t.attr.validateFn ) && (reason = t.attr.validateFn( event.attr, t.attr.validateParams )) ) {
715+
t.addEvent = function( evt ){
716+
if( $.isFunction( t.attr.validateFn ) && (reason = t.attr.validateFn( evt.attr, t.attr.validateParams )) ) {
665717
rc_notify('Unable to add the event','The '+reason+' requirement was not met', 'error');
666718
return false;
667719
}
668720
else {
669721

670-
if( "undefined" == typeof( t.eventpool[event.attr.date] ) ){
671-
t.eventpool[event.attr.date] = [];
722+
if( "undefined" == typeof( t.eventpool[evt.attr.date] ) ){
723+
t.eventpool[evt.attr.date] = {};
672724
}
673725

674-
//
675-
// Policy code for overlapping events, locked events
676-
//
677-
t.insert_policy( t.eventpool[event.attr.date], event );
678-
679-
if( undefined == no_confirm ){
680-
var formatted_date = new moment( event.attr.date ).format("dddd, Do MMM YYYY");
681-
682-
rc_notify('Success',
683-
'Added '+event.attr.ev_text+' to '+t.attr.title+' at '+event.attr.start+' on '+formatted_date,
684-
'success');
685-
}
726+
t.eventpool[evt.attr.date][evt.attr.id] = evt;
686727
}
687728
return true;
688729
}
689730

690731
t.removeEvent = function( evt ){
691-
t.eventpool[evt.attr.date][evt.attr.id] = undefined;
732+
if( !isNaN(evt.attr.date) && "undefined" != typeof(t.eventpool[evt.attr.date]) ) {
733+
delete t.eventpool[evt.attr.date][evt.attr.id];
734+
}
735+
return true;
692736
}
693737

694738
t.listEvents = function( date ){

0 commit comments

Comments
 (0)