1 /**
  2  * Copyright (C) 2005-2010 Alfresco Software Limited.
  3  *
  4  * This file is part of Alfresco
  5  *
  6  * Alfresco is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU Lesser General Public License as published by
  8  * the Free Software Foundation, either version 3 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * Alfresco is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU Lesser General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU Lesser General Public License
 17  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 18  */
 19 
 20 /**
 21  * CalendarView base component.
 22  * Provides common functionality for all Calendar views.
 23  *
 24  * @namespace Alfresco
 25  * @class Alfresco.CalendarView
 26  */
 27 (function()
 28 {
 29    /**
 30     * YUI Library aliases
 31     */
 32    var Dom = YAHOO.util.Dom,
 33       Event = YAHOO.util.Event,
 34       Sel = YAHOO.util.Selector,
 35       fromISO8601 = Alfresco.util.fromISO8601,
 36       toISO8601 = Alfresco.util.toISO8601,
 37       dateFormat = Alfresco.thirdparty.dateFormat;
 38 
 39    /**
 40     * CalendarView constructor.
 41     *
 42     * @param {String} htmlId The HTML id of the parent element
 43     * @return {Alfresco.CalendarView} The new CalendarView instance
 44     * @constructor
 45     */
 46    Alfresco.CalendarView = function CalendarView_constructor(htmlId)
 47    {
 48       this.id = htmlId;
 49       Alfresco.CalendarView.superclass.constructor.call(this, "Alfresco.CalendarView", htmlId, ["calendar", "button", "resize", "datasource", "datatable", "history"]);
 50 
 51       return this;
 52    };
 53 
 54    YAHOO.extend(Alfresco.CalendarView, Alfresco.component.Base,
 55    {
 56       /**
 57        * Object container for storing YUI widget instances.
 58        *
 59        * @property widgets
 60        * @type object
 61        */
 62       widgets: {},
 63 
 64       /**
 65        * Object container for storing module instances.
 66        *
 67        * @property modules
 68        * @type object
 69        */
 70       modules: {},
 71 
 72       /**
 73        * Object container for storing YUI pop dialog instances.
 74        *
 75        * @property popups
 76        * @type object
 77        */
 78       popups: {},
 79 
 80       /**
 81        * Object container for storing event handlers
 82        *
 83        * @property handlers
 84        * @type object
 85        */
 86       handlers: {},
 87 
 88       /**
 89        * Object container for data
 90        *
 91        * @property data
 92        * @type object
 93        */
 94       data: {},
 95 
 96       /**
 97        * View type - must be overridden by subclasses
 98        *
 99        * @property calendarView
100        * @type string
101        */
102       calendarView: '',
103 
104       /**
105        * Initialises event handling All events are handled through event
106        * delegation via the onInteractionEvent handler
107        *
108        * @method initEvents
109        */
110       initEvents: function CalendarView_initEvents()
111       {
112          Event.on(this.id, 'click', this.onInteractionEvent, this, true);
113          Event.on(this.id, 'dblclick', this.onInteractionEvent, this, true);
114 
115          YAHOO.Bubbling.on("eventEdited", this.onEventEdited, this);
116          YAHOO.Bubbling.on("eventEditedAfter", this.onAfterEventEdited, this);
117          YAHOO.Bubbling.on("eventSaved", this.onEventSaved, this);
118          YAHOO.Bubbling.on("eventSavedAfter", this.onAfterEventSaved, this);
119          YAHOO.Bubbling.on("eventDeleted", this.onEventDeleted, this);
120          YAHOO.Bubbling.on("eventDeletedAfter", this.onAfterEventDeleted, this);
121 
122          YAHOO.Bubbling.on("tagSelected", this.onTagSelected, this);
123          YAHOO.Bubbling.on("viewChanged", this.onViewChanged, this);
124          YAHOO.Bubbling.on("dateChanged", this.onCalSelect, this);
125          YAHOO.Bubbling.on("formValidationError", this.onFormValidationError, this);
126          if (this.calendarView == Alfresco.CalendarView.VIEWTYPE_DAY | this.calendarView == Alfresco.CalendarView.VIEWTYPE_WEEK)
127          {
128             YAHOO.Bubbling.on("eventResized", this.onEventResized, this);
129          }
130       },
131 
132       /**
133        * Retrieves events from server
134        * 
135        * @method getEvents
136        *  
137        */
138       getEvents : function CalendarView_getEvents()
139       {
140          Alfresco.util.Ajax.request(
141          {
142             url: Alfresco.constants.PROXY_URI + "calendar/events/" + this.options.siteId + "/user",
143             dataObj:
144             {
145                from: toISO8601(this.options.startDate).split('T')[0],
146                to: toISO8601(this.options.endDate).split('T')[0],
147                repeating: "all"
148             },
149             //filter out non relevant events for current view            
150             successCallback: 
151             {
152                fn: this.onEventsLoaded,
153                scope: this
154             },
155                failureMessage: Alfresco.util.message("load.fail", "Alfresco.CalendarView")
156            });
157       },
158       
159       /**
160        * Renders view
161        *
162        * @method render
163        *
164        */
165       render: function CalendarView_render()
166       {
167          if (this.calendarView === Alfresco.CalendarView.VIEWTYPE_AGENDA ) {
168 
169             // initialise DOM Event registration
170             this.initEvents();
171             // Load events. Rest of init is handled by a call back from the event loading.
172             this.getEvents(dateFormat(this.options.startDate, 'yyyy-mm-dd'));
173          } else {
174             // FullCalendar handles event loading and callbacks, so call the function that triggers that.
175             this.renderEvents();
176          }
177       },
178 
179       displayMessage: function CalendarView_displayMessage(message, name)
180       {
181          Alfresco.util.PopupManager.displayMessage(
182          {
183             text: Alfresco.util.message(message, name || this.name)
184          });
185       },
186 
187       /**
188        *Returns the Event Data object associated with the element passed in.
189        * 
190        * @param data {object} either the HTML node of the event or the event data
191        */
192       
193       getEventObj: function CalendarView_getEventObj(data)
194       {
195          // If we've got the HTML node, it'll have a rel attr we can use to get the event data
196          if (typeof(data.rel) !== "undefined" && data.rel !== "")
197          {
198             return this.parseRel(data);
199          }
200          // Otherwise, assume it's the event object, so just send that back.
201          else
202          {
203             return data;
204          }
205       },
206       
207       /**
208        * builds up the relationship string to store the event reference in the DOM.
209        * 
210        * @param {Object} data
211        */
212       getRel: function CalendarView_getRel(data) 
213       {
214          //Just stores the ISO yyyy-mm-dd string and will use href from link to identify data
215          return data.from.split("T")[0];
216       },
217       
218       /**
219        * 
220        * retrieves the event object based on the data in the rel string.
221        * 
222        * @param {HTML element} element with a relationship to an event.
223        */
224       parseRel: function CalendarView_parseRel(element) 
225       {
226          var relationship,
227             data = "",
228             date = "",
229             result = false;
230          
231          // If the passed in is a datatable container, it won't have a rel, so get the first a tag in it.
232          if (Sel.test(element, 'div.yui-dt-liner')) 
233          {
234             element = Dom.getElementsByClassName("summary", "a", element.parentNode.parentNode)[0]
235          }
236          
237          // check the element has a rel tag supplied.
238          if (element.rel !== "" && element.rel !== undefined) 
239          {
240             date = element.rel;
241             data = this.widgets.Data[date].events;
242             for (var i = 0; i < data.length; i++) 
243             {
244                if (data[i].uri === "/calendar/event/" + element.href.split("/calendar/event/")[1]) // element.href needs hostname and port stripping.
245                {
246                   result = data[i];
247                }
248             }
249          }
250          return result;
251       },
252       
253       /**
254        * Shows/hides the early hours of day (midnight till 7am)
255        *
256        * @method toggleEarlyTableRows
257        *
258        */
259       toggleEarlyTableRows: function CalendarView_toggleEarlyTableRows()
260       {
261 
262          var triggerEl = YAHOO.util.Dom.get('collapseTrigger');
263          this.earlyEls = YAHOO.util.Dom.getElementsByClassName('early', 'tr', triggerEl.parentNode);
264          var displayStyle = (YAHOO.env.ua.ie) ? 'block' : 'table-row';
265          for (var i = 0; i < this.earlyEls.length; i++)
266          {
267             var el = this.earlyEls[i];
268             YAHOO.util.Dom.setStyle(el, 'display', (this.isShowingEarlyRows) ? 'none' : displayStyle);
269          }
270          this.isShowingEarlyRows = !this.isShowingEarlyRows;
271       },
272 
273       /**
274        * Handler for event retrieval when events are loaded from the server
275        * Used by agenda, day and week (i.e. not month view)
276        *
277        * @method onEventsLoaded
278        */
279       onEventsLoaded: function CalendarView_onEventsLoaded(o)
280       {
281          var data = YAHOO.lang.JSON.parse(o.serverResponse.responseText).events;
282          var siteEvents = [];
283          var events = [];
284          var comparisonFn = null;
285          var viewStartDate = this.options.startDate;
286          var viewEndDate = this.options.endDate;
287          var site = this.options.siteId;
288          
289          // Trigger Mini Calendar's rendering before filtering the events
290          YAHOO.Bubbling.fire("eventDataLoad",data);
291          
292          for (var i = 0; i < data.length; i++) 
293          {
294             var ev = data[i];
295 
296             // Escape User Input Strings to avoid XSS
297             ev.title = Alfresco.util.encodeHTML(ev.title);
298             ev.where = Alfresco.util.encodeHTML(ev.where);
299 
300             if (ev.site == site)
301             {
302                siteEvents.push(ev);
303             }
304          }
305          data = siteEvents;
306          // TODO: These take no account of timezone. E.g. day view of 6th June. server time = GMT+1000, event time = 06 JUN 2010 05:00GMT+1000, date returned from server is 05 JUN 2010 19:00GMT+0000. 05 JUN != 06 JUN.
307          // TODO: This would be better done on the server in the userevents webscript
308          comparisonFn = function()
309          {
310 
311             return function(eventDate, endDate)
312             {
313                // Event can: Start before and finish after display dates
314                var eventSurroundsView = (eventDate <= viewStartDate && viewEndDate <= endDate);
315                // or: start during
316                var startDuring = (eventDate >= viewStartDate && eventDate < viewEndDate);
317                // or: finish during
318                var endDuring = (endDate >= viewStartDate && endDate < viewEndDate);
319                return (eventSurroundsView || startDuring || endDuring);
320             };
321          }.apply(this);
322 
323          for (var i = 0; i < data.length; i++)
324          {
325             // TODO: Make this format consistent across calendar views and API.
326             var ev = data[i];
327             var date = fromISO8601(ev.startAt.iso8601);
328             var endDate = fromISO8601(ev.endAt.iso8601);
329             if (comparisonFn(date, endDate))
330             {
331                var datum = {};
332 
333                // Legacy properties (to be factored out or rolled up over time)
334                datum.desc = ev.description || '';
335                datum.name = ev.title;
336                datum.isoutlook = ev.isoutlook == "true" ? "isoutlook" : "";
337                datum.contEl = 'div';
338                datum.from = ev.startAt.iso8601;
339                datum.to = ev.endAt.iso8601;
340                datum.uri = '/calendar/event/' + this.options.siteId + '/' + ev.name + '?date=' + ev.startAt.iso8601;
341                datum.hidden = '';
342                datum.allday = '';
343                datum.isMultiDay = (!Alfresco.CalendarHelper.isSameDay(date, endDate));
344                datum.isAllDay = (ev.allday == "true") ? true : false;
345                datum.el = 'div';
346 
347                datum.key = datum.from.split(":")[0] + ':00';
348 
349                // Merge in standard event properties - allowing legacy values to override standards
350                datum = YAHOO.lang.merge(ev, datum)
351 
352                events.push(datum);
353             }
354          }
355 
356          this.renderEvents(events);
357       },
358 
359       /**
360        * Adds events to view
361        *
362        * @method add
363        * @param {String} id Identifier of event
364        * @param {Object} o Event Object
365        * @return {Boolean} Status of add operation
366        */
367       add: function CalendarView_add(id, o)
368       {
369          this.add(id, o);
370       },
371 
372       /**
373        * Removes events from view
374        *
375        * @method remove
376        * @param {String} id Identifier of event
377        * @return {Boolean} Status of removal operation
378        */
379       remove: function CalendarView_remove(id)
380       {
381          this.remove(id);
382       },
383 
384       /**
385        * Updates specified event
386        *
387        * @method update
388        *
389        * @param {String} id Identifier of event
390        * @param {Object} o Event Object
391        * @return {Boolean} Status of update operation
392        */
393       update: function CalendarView_update(id, o)
394       {
395          this.data.update(o);
396       },
397 
398       /**
399        * Filters the array of events for multiday events
400        * For each Multiday event, it:
401        *    - Creates an event for every day in the period.
402        *    - If not All day:
403        *       - the first day's display end time is set to: 00:00
404        *       - the middle days are marked as multiday
405        *       - the last day's start time is: 00:00
406        *    - Adds cloned tag.
407        *       
408        * TODO: Currently this is only used by the Agenda view - this needs rolling out across the other views when they get refactored.
409        * 
410        * @method filterMultiday
411        * @param events {Array} Array of event objects
412        */
413       filterMultiday: function CalendarView_filterMultiday(events) 
414       {
415          var DateMath = YAHOO.widget.DateMath;
416          
417          for (var i=0, numEvents=events.length;i<numEvents;i++) 
418          {
419             var event = events[i];
420             // check if event is multiday
421             if (event.isMultiDay) 
422             {
423                var from = event.from.split("T"),
424                   to = event.to.split("T"),
425                   startDay = fromISO8601(from[0]),
426                   endDay = fromISO8601(to[0]),
427                   iterationDay = new Date(startDay + 86400000);
428                
429                // if not all day event, end time on first day needs to be midnight.
430                if (!event.isAllDay) 
431                {
432                   event.displayEnd = "00:00";
433                }
434                
435                for (var j=0, iterationDay=DateMath.add(startDay, DateMath.DAY, 1); iterationDay.getTime() <= endDay.getTime(); iterationDay=DateMath.add(iterationDay, DateMath.DAY, 1)) 
436                {
437                   var clonedEvent = YAHOO.lang.merge(event);
438                   
439                   // Mark as cloned and provide a marker to locate the original
440                   clonedEvent.isCloned = true;
441                   clonedEvent.clonedFromDate=event.from;
442                   
443                   // Sort out the display time.
444                   if (!event.isAllDay)   
445                   {
446                      // If event is not the last day of the repeating sequence, it lasts all day.
447                      if (!Alfresco.CalendarHelper.isSameDay(iterationDay, endDay)) 
448                      {
449                         clonedEvent.isAllDay = true;
450                      } else 
451                      {
452                         // if it is the same day, we need to set the finish time, by removing the displayEnd time.
453                         clonedEvent.displayStart="00:00";
454                         delete clonedEvent.displayEnd;
455                      }
456                      
457                   }
458                   // set the DisplayDates for the cloned object to the current day of the loop:
459                   clonedEvent.displayFrom = toISO8601(iterationDay);
460                   events.push(clonedEvent);
461                } 
462             }
463          }
464          
465          return events
466       },
467       
468       /**
469        * Remove dom elements that represent multiple day events
470        *
471        * @method removeMultipleDayEvents
472        */
473       removeMultipleDayEvents: function CalendarView_removeMultipleDayEvents(srcEl)
474       {
475          var els = Dom.getElementsByClassName(srcEl.id + '-multiple');
476          for (var i = 0, len = els.length; i < len; i++)
477          {
478             els[i].parentNode.removeChild(els[i]);
479          }
480       },
481       /**
482        * Returns the current date that the user clicked on
483        *
484        * @method getClickedDate
485        * @param el {DOM Element} the element that was clicked on
486        * @returns {Date}
487        */
488       getClickedDate: function CalendarView_getClickedDate(el)
489       {
490          var date = this.options.titleDate; // set a default incase we can't find the element
491          if (el.nodeName.toUpperCase() !== 'TD') 
492          {
493             el = Dom.getAncestorByTagName(el, 'td');
494          }
495          if (el) 
496          {
497             date =  fromISO8601(el.id.replace('cal-', ''));
498          }
499          return date;
500       },
501 
502       /**
503        * Displays add dialog
504        *
505        * @method showAddDialog
506        * @param date {Date object} Javascript date object containing the start date for the new event.
507        *
508        */
509       showAddDialog: function CalendarView_showAddDialog(date)
510       {
511          var displayDate;
512          // if from toolbar add event
513          if (YAHOO.lang.isUndefined(date))
514          {
515             this.currentDate = displayDate = (Alfresco.util.getQueryStringParameter('date')) ? fromISO8601(Alfresco.util.getQueryStringParameter('date')) : new Date();
516          }
517          else
518          {
519             // from cell
520             this.currentDate = displayDate = date;
521          }
522 
523          if (this.eventDialog)
524          {
525             this.eventDialog.dialog.destroy();
526             delete this.eventDialog;
527          }
528 
529          var editInfo = new Alfresco.EventInfo(this.id);
530 
531          this.eventDialog = editInfo.initEditDialog(
532          {
533             actionUrl: Alfresco.constants.PROXY_URI + "calendar/create",
534             ajaxSubmitMethod: Alfresco.util.Ajax.POST,
535             displayDate: displayDate,
536             templateRequestParams:
537             {
538                site: this.options.siteId
539             },
540             onSuccess:
541             {
542                fn: this.onEventSaved,
543                scope: this
544             },
545             onFailure:
546             {
547                fn: this.onEventSaveFailed,
548                scope: this
549             }
550          });
551          this.eventDialog.show();
552       },
553 
554       /**
555        * shows edits or add dialog depending on source of event
556        *
557        * @method showDialog
558        * @param e {object} Event object
559        * @param elTarget {object} Element in which event occured
560        *
561        */
562       showDialog: function(e, elTarget)
563       {
564          var event = this.getEventObj(elTarget);
565          // Set up the dialog box
566          this.setUpDialog(e, elTarget, event);
567          
568          // if the edit window isn't already showing, show it!
569          if (!this.eventInfoPanel.isShowing) 
570          {
571             this.eventInfoPanel.show(event);            
572          }
573 
574          Event.preventDefault(e);
575       },
576 
577       /**
578        * Uses the EventInfo delete method to delete the event after showing a confirmation dialogue.
579        * 
580        * @method deleteDialog
581        * 
582        * @param {Object} e
583        * @param {Object} elTarget
584        */
585       deleteDialog: function(e, elTarget)
586       {
587          var event = this.getEventObj(elTarget);
588          // Set up the dialog box
589          this.setUpDialog(e, elTarget, event);
590          
591          //call delete function
592          this.eventInfoPanel.onDeleteClick();
593          
594          Event.preventDefault(e);
595       },
596       
597       /**
598        * Uses the EventInfo edit method to jump straight to the event edit screen.
599        * 
600        * @method editDialog
601        * 
602        * @param {Object} e
603        * @param {Object} elTarget
604        */
605       editDialog: function(e, elTarget)
606       {
607          var event = this.getEventObj(elTarget);
608          // Set up the dialog box
609          this.setUpDialog(e, elTarget, event);
610          
611          //call edit function
612          this.eventInfoPanel.onEditClick();
613          
614          Event.preventDefault(e);
615       },
616       
617       /**
618        * Does the grunt work of setting up the dialogue box for info, edit and delete methods.
619        * 
620        * @method setUpDialog
621        * 
622        * @param {Object} e
623        * @param {Object} elTarget
624        */
625       setUpDialog: function(e, elTarget, event) 
626       {
627          var div = document.createElement('div');
628          
629          div.id = 'eventInfoPanel';
630          document.body.appendChild(div);
631          this.eventInfoPanel = new Alfresco.EventInfo(this.id);
632          this.eventInfoPanel.event = event;
633          
634          if (!this.eventInfoPanel.isShowing) 
635          {
636             this.eventInfoPanel.setOptions(
637             {
638                siteId: this.options.siteId,
639                eventUri: event.uri.substring(1,event.uri.length), // strip off leading '/'
640                displayDate: this.currentDate,
641                event: event,
642                permitToEditEvents: this.options.permitToCreateEvents
643             });
644          }         
645       },
646 
647       /**
648        * Tests if event is valid for view must be within startdate and (enddate-1 second) of current view
649        *
650        * @method date {object} Date to validate
651        *
652        * @return true|false
653        *
654        */
655       isValidDateForView: function(date)
656       {
657          return (date.getTime() >= this.options.startDate.getTime()) && (date.getTime() < this.options.endDate.getTime());
658       },
659 
660       // HANDLERS
661 
662       /**
663        * Handler for cancelling dialog
664        *
665        * @method onCancelDialog
666        *
667        */
668       onCancelDialog: function CalendarView_onCancelDialog()
669       {
670          this.eventDialog.hide();
671       },
672 
673       /**
674        * Updates date field in dialog when date in selected in popup calendar
675        *
676        * @method onDateSelected
677        * @param e {object} Event object
678        * @param args {object} Event argument object
679        */
680       onDateSelected: function CalendarView_onDateSelected(e, args, context)
681       {
682          if (this.currPopUpCalContext)
683          {
684             // ugly
685             for (var i = 1; i < args[0][0].length; i++)
686             {
687                args[0][0][i] = Alfresco.CalendarHelper.padZeros(args[0][0][i]);
688             }
689             Dom.get(this.currPopUpCalContext).value = args[0][0].join('-');
690             // add one hour as default
691             if (this.currPopUpCalContext === 'dtend')
692             {
693                Dom.get(this.currPopUpCalContext + 'time').value = YAHOO.widget.DateMath.add(fromISO8601(Dom.get('dtstart').value + 'T' + Dom.get('dtstarttime').value), YAHOO.widget.DateMath.HOUR, 1).format(dateFormat.masks.isoTime);
694 
695             }
696          }
697       },
698       // HANDLERS
699 
700       /**
701        * Fired by YUI when parent element is available for scripting.
702        * Component initialisation, including instantiation of YUI widgets and event listener binding.
703        *
704        * @method onReady
705        */
706       onReady: function CalendarView_onReady()
707       {
708          this.calendarView = this.options.view;
709          this.startDate = (YAHOO.lang.isString(this.options.startDate)) ? fromISO8601(this.options.startDate) : this.options.startDate;
710          this.container = Dom.get(this.id);
711          this.containerRegion = Dom.getRegion(this.container);
712          this.isShowingEarlyRows = true;
713          this.titleEl = Dom.get('calTitle');
714 
715          // Patch YAHOO.widget.DateMath to support Hours, mins and seconds
716          if (!YAHOO.widget.DateMath.HOUR)
717          {
718             YAHOO.widget.DateMath.add = function()
719             {
720                var origAddFunc = YAHOO.widget.DateMath.add;
721                YAHOO.widget.DateMath.HOUR = 'H';
722                YAHOO.widget.DateMath.SECOND = 'S';
723                YAHOO.widget.DateMath.MINUTE = 'Mn';
724                return function(date, field, amount)
725                {
726 
727                   switch (field)
728                   {
729                      case YAHOO.widget.DateMath.MONTH:
730                      case YAHOO.widget.DateMath.DAY:
731                      case YAHOO.widget.DateMath.YEAR:
732                      case YAHOO.widget.DateMath.WEEK:
733                         return origAddFunc.apply(YAHOO.widget.DateMath, arguments);
734                         break;
735                      case YAHOO.widget.DateMath.HOUR:
736                         var newHour = date.getHours() + amount;
737                         var day = 0;
738                         if (newHour < 0)
739                         {
740                            while (newHour < 0)
741                            {
742                               newHour += 24;
743                               day -= 1;
744 
745                            }
746                         // newHour = 23;
747                         }
748                         if (newHour > 24)
749                         {
750                            while (newHour > 24)
751                            {
752                               newHour -= 24;
753                               day += 1;
754 
755                            }
756                         }
757                         YAHOO.widget.DateMath._addDays(date, day);
758                         date.setHours(newHour);
759                         break;
760                      case YAHOO.widget.DateMath.MINUTE:
761                         date.setMinutes(date.getMinutes() + amount);
762                         break;
763                      case YAHOO.widget.DateMath.SECOND:
764                         date.setMinutes(date.getSeconds() + amount);
765 
766                   }
767                   return date;
768                };
769             }();
770          }
771 
772          this.render();
773       },
774 
775       /**
776        * Event Delegation handler. Delegates to correct handlers using CSS selectors
777        *
778        * @method onInteractionEvent
779        * @param o{object} DomEvent
780        * @param args {array} event arguments
781        */
782       onInteractionEvent: function CalendarView_onInteractionEvent(o, args)
783       {
784          // TODO: refactor this if/else list into an event trigger with listeners.
785          
786          var elTarget, e;
787          // if loop added for DataTable event trigger which passes event and target as single object in 1st param
788          if (typeof(o.event) === "object" && typeof(o.target) === "object") 
789          {
790             e = o.event;
791             elTarget = o.target;
792          }
793          else //old style (non DataTable trigger), event as first object, target not included.
794          {
795             e = o;
796             elTarget = Event.getTarget(e);
797          }
798          
799          // Check for event type.
800          // repeated if loops are now a series of else if loops to prevent all selectors being attempted. Matching of multiple selectors is not recommended.
801          if (e.type === 'mouseover') 
802          {
803             if (Sel.test(elTarget, 'div.' + this.dragGroup))
804             {
805                Dom.addClass(elTarget, 'highlight');
806                if (this.options.permitToCreateEvents === 'true')
807                {
808                   if (!Dom.hasClass(elTarget, 'disabled'))
809                   {
810                      elTarget.appendChild(this.addButton);
811                   }
812                }
813             }
814          }
815          else if (e.type === 'mouseout')
816          {
817             if (Sel.test(elTarget, 'div.' + this.dragGroup))
818             {
819                Dom.addClass(elTarget, 'highlight');
820             }
821          }
822          else if (e.type === 'click') 
823          {
824             // Show or hide wee hours?
825             if (Sel.test(elTarget, 'a#collapseTriggerLink')) 
826             {
827                this.toggleEarlyTableRows();
828                Event.preventDefault(e);
829             }
830             // are we adding a new event?
831             else if (Sel.test(elTarget, 'button#addEventButton') || Sel.test(elTarget.offsetParent, 'button#addEventButton') || Sel.test(elTarget, 'a.addEvent')) 
832             {
833                this.showAddDialog();
834                Event.preventDefault(e);
835             }
836             // a.summary = a click on the event title. Therefore into Event Info mode.
837             else if (Sel.test(elTarget, 'a.summary') || Sel.test(elTarget, 'div.yui-dt-liner') ) 
838             {
839                this.showDialog(e, elTarget);
840             }
841             // Someone clicked the 'show more events in Month View' link.
842             else if (Sel.test(elTarget, 'li.moreEvents a')) 
843             {
844                this.onShowMore(e, args, elTarget);
845             }
846             //Agenda View show more
847             else if (Sel.test(elTarget, 'a.showMore'))
848             {
849                this.expandDescription(elTarget);
850                Event.preventDefault(e);
851             }
852             else if (Sel.test(elTarget, 'a.showLess'))
853             {
854                this.collapseDescription(elTarget);
855                Event.preventDefault(e);
856             }
857             // Delete this event link in Agenda DataTable
858             else if (Sel.test(elTarget, "a.deleteAction"))
859             {
860                this.deleteDialog(e, elTarget);
861             }
862             // Edit event link in Agenda DataTable.
863             else if (Sel.test(elTarget, "a.editAction"))
864             {
865                this.editDialog(e, elTarget);
866             }
867          }
868       },
869 
870       /**
871        * Handler for when today button is clicked
872        *
873        * @method onTodayNav
874        *
875        */
876       onTodayNav: function CalendarView_onTodayNav()
877       {
878          var today = new Date();
879          var params = Alfresco.util.getQueryStringParameters();
880          params.date = today.getFullYear() + '-' + Alfresco.CalendarHelper.padZeros((~ ~ (1 * (today.getMonth()))) + 1) + '-' + Alfresco.CalendarHelper.padZeros(today.getDate());
881          window.location = window.location.href.split('?')[0] + Alfresco.util.toQueryString(params);
882       },
883 
884       /**
885        * Handler for when calendar view is changed (agenda button is clicked)
886        *
887        * @method onViewChanged
888        *
889        */
890       onViewChanged: function CalendarView_onViewChanged()
891       {
892 
893          var params = Alfresco.util.getQueryStringParameters(),
894             hash = window.location.hash;
895             dateBookmark = hash.substring(hash.indexOf("date=") + 5).split("&")[0];
896          params.view = Alfresco.util.ComponentManager.findFirst("Alfresco.CalendarToolbar").enabledViews[arguments[1][1].activeView];
897          if (dateBookmark !== "")
898          {
899             params.date = dateBookmark;
900          }
901          // Remove both current parameters and current bookmarks.
902          window.location = window.location.href.split('?')[0].split('#')[0] + Alfresco.util.toQueryString(params);
903 
904       },
905 
906       /**
907        * Handler for when date mini calendar is selected
908        *
909        * @method onNav
910        * @param e {object}
911        *
912        */
913       onCalSelect: function CalendarView_onCalSelect(e, args)
914       {
915          var date = args[1].date;
916          var params = Alfresco.util.getQueryStringParameters();
917          params.date = dateFormat(date, 'yyyy-mm-dd');
918          var newLoc = window.location.href.split('?')[0] + Alfresco.util.toQueryString(params);
919          window.location = newLoc;
920       },
921       /**
922        * Handler for when a tag is selected
923        *
924        * @method onTagSelected
925        *
926        */
927       onTagSelected: function CalendarView_onTagSelected(e, args)
928       {
929          var tagName = arguments[1][1].tagname,
930             showAllTags = false;
931          
932          // all tags
933          if (tagName == Alfresco.util.message('label.all-tags', 'Alfresco.TagComponent'))
934          {
935             this.options.tag = null;
936          }
937          else
938          {
939             this.options.tag = tagName;
940          }
941          this.updateTitle();
942          this.getEvents();
943       },
944 
945       /**
946        *
947        * @method onFormValidationError
948        *
949        * @param e {object} Event object
950        * @param args {object} Value object referencing elements that are invalid
951        */
952       onFormValidationError: function CalendarView_onFormValidationError(e, args)
953       {
954          args = args[1];
955          Alfresco.util.PopupManager.displayMessage(
956          {
957             text: args.msg
958          });
959       },
960 
961       /**
962        * Handler for eventEdited event. Updates event in DOM in response to updated event data.
963        *
964        * @method  onEventEdited
965        *
966        * @param e {object} event object
967        * @param o {object} new event data
968        */
969       onEventEdited : function CalendarView_onEventEdited(e,o)
970       {
971          this.getEvents()
972          YAHOO.Bubbling.fire("eventEditedAfter");
973       },
974 
975       /**
976        * Handler for when event is saved
977        *
978        * @method onEventSaved
979        *
980        * @param o {object} response object
981        */
982       onEventSaved : function CalendarView_onEventSaved(o)
983       {
984          this.getEvents();
985          var result = YAHOO.lang.JSON.parse(o.serverResponse.responseText);
986          if (!result.error)
987          {
988             YAHOO.Bubbling.fire("eventSavedAfter");
989             this.displayMessage('message.created.success',this.name);
990          }
991          else
992          {
993             this.onEventSaveFailed();
994          }
995       },
996 
997       /**
998        * Triggered when an event can't be created
999        *
1000        * @method: onEventSaveFailed
1001        */
1002       onEventSaveFailed: function CalendarView_onEventSaveFailed()
1003       {
1004          Alfresco.util.PopupManager.displayMessage(
1005          {
1006             text: Alfresco.util.message('message.created.failure', this.name)
1007          });
1008       },
1009 
1010       /**
1011        * Handler for when an event is deleted
1012        *
1013        * @method  onEventDeleted
1014        */
1015       onEventDeleted : function CalendarView_onEventDeleted()
1016       {
1017          this.getEvents();
1018          YAHOO.Bubbling.fire("eventDeletedAfter");
1019          this.msg('message.deleted.success',this.name);
1020       },
1021 
1022       onAfterEventSaved: function CalendarView_onAfterEventSaved(e, args)
1023       {
1024          // Refresh the tag component
1025          this.refreshTags();
1026          
1027          // Confirm success to the user
1028          this.displayMessage('message.created.success', this.name);
1029       },
1030 
1031       onAfterEventDeleted: function CalendarView_onAfterEventDeleted(e, args)
1032       {
1033          this.refreshTags();
1034          this.displayMessage('message.deleted.success', this.name);
1035       },
1036 
1037       onAfterEventEdited: function CalendarView_onAfterEventDeleted(e, args)
1038       {
1039          // Refresh the tag component
1040          this.refreshTags();
1041       },
1042 
1043       refreshTags: function CalendarView_refreshTags()
1044       {
1045          YAHOO.lang.later(500, YAHOO.Bubbling, 'fire', 'tagRefresh');
1046       },
1047 
1048       /**
1049        * Stub function - to be overridden on the view level (e.g. by CalendarAgendaView_updateTitle)
1050        */
1051       updateTitle: function CalendarView_updateTitle()
1052       {
1053          return;
1054       },
1055       
1056       /**
1057        *
1058        * takes the event list and removes any items that aren't tagged with the currently selected tag.
1059        * 
1060        * @method tagFilter 
1061        * 
1062        * @param {Object} events
1063        */
1064       tagFilter: function CalendarView_tagFilter(events) 
1065       {
1066          var filteredEvents = [],
1067             tagName = this.options.tag;
1068          
1069          // early exit if there is no selected tagName
1070          if (!tagName) 
1071          {
1072             return events;
1073          } else 
1074          {
1075             for (var i = 0, l = events.length; i < l; i++) 
1076             {
1077                var eventTags = events[i].tags
1078                // TODO: Remove this check once we have a consistent event object
1079                if (typeof(eventTags) === "string")
1080                {
1081                   eventTags = eventTags.split(" "); // TODO: Tags with spaces are not supported in the Calendar Event input field.
1082                }
1083                if (Alfresco.util.arrayContains(eventTags, tagName)) 
1084                {
1085                   filteredEvents.push(events[i]); 
1086                }
1087             }
1088             return filteredEvents;
1089          }
1090       }
1091    });
1092    Alfresco.CalendarView.VIEWTYPE_WEEK = 'week';
1093    Alfresco.CalendarView.VIEWTYPE_MONTH = 'month';
1094    Alfresco.CalendarView.VIEWTYPE_DAY = 'day';
1095    Alfresco.CalendarView.VIEWTYPE_AGENDA = 'agenda';
1096 })();
1097 
1098 /**
1099  * Alfresco.CalendarHelper. Helper object consisting of useful helper methods
1100  *
1101  * @constructor
1102  */
1103 Alfresco.CalendarHelper = (function Alfresco_CalendarHelper()
1104 {
1105    var Dom = YAHOO.util.Dom,
1106        Event = YAHOO.util.Event,
1107        Sel = YAHOO.util.Selector,
1108        fromISO8601 = Alfresco.util.fromISO8601,
1109        toISO8601 = Alfresco.util.toISO8601,
1110        dateFormat = Alfresco.thirdparty.dateFormat;
1111    var templates = [];
1112 
1113    return {
1114       /**
1115        * Calculates end date depending on specified duration, in ISO8601 format
1116        *
1117        * @method getEndDate
1118        * @param dateISO {String} startDate in ISO8601 format
1119        * @param duration {object} Duration object
1120        */
1121       getEndDate: function Alfresco_CalendarHelper_getEndDate(dateISO, duration)
1122       {
1123          var newDate = Alfresco.util.fromISO8601(dateISO);
1124          for (var item in duration)
1125          {
1126             newDate = YAHOO.widget.DateMath.add(newDate, (item === 'M') ? YAHOO.widget.DateMath.MINUTE : item, (~ ~ (1 * duration[item])));
1127          }
1128          return Alfresco.util.toISO8601(newDate).split('+')[0];// newDate.toISO8601String(5);
1129       },
1130 
1131       /**
1132        * Correctly determines which hour segment the event element is in. Returns the hour
1133        *
1134        * @method determineHourSegment
1135        * @param ePos {object} Object containing XY position of element to test
1136        * @param el {object} Event element
1137        * @return {string} Hour
1138        */
1139       determineHourSegment: function Alfresco_CalendarHelper_determineHourSegment(ePos, el)
1140       {
1141          var r = Dom.getRegion(el);
1142          var y = ePos[1];
1143          var threshold = Math.round((r.bottom - r.top) / 2);
1144          var inFirstHalfHour = (!Dom.getPreviousSibling(el)); // first half of
1145          // hour
1146 
1147          var hour = Dom.getAncestorByTagName(el, 'tr').getElementsByTagName('h2')[0].innerHTML;
1148          if (inFirstHalfHour === true)
1149          {
1150             hour = (y - r.top < threshold) ? hour : hour.replace(':00', ':15');
1151 
1152          }
1153          else
1154          {
1155             hour = (y - r.top < threshold) ? hour.replace(':00', ':30') : hour.replace(':00', ':45');
1156 
1157          }
1158          return hour;
1159       },
1160 
1161       /**
1162        * calculates duration based on specified start and end dates
1163        *
1164        *
1165        * @method getDuration
1166        * @param dtStartDate {Date} start date
1167        * @param dtEndDate {Date} end date
1168        * @return {String} Duration in ical format eg PT2H15M
1169        */
1170       getDuration: function Alfresco_CalendarHelper_getDuration(dtStartDate, dtEndDate)
1171       {
1172          var diff = dtEndDate.getTime() - dtStartDate.getTime();
1173          var dateDiff = {};
1174          var duration = 'P';
1175          var diff = new Date();
1176          diff.setTime(Math.abs(dtEndDate.getTime() - dtStartDate.getTime()));
1177          var timediff = diff.getTime();
1178 
1179          dateDiff[YAHOO.widget.DateMath.WEEK] = Math.floor(timediff / (1000 * 60 * 60 * 24 * 7));
1180          timediff -= dateDiff[YAHOO.widget.DateMath.WEEK] * (1000 * 60 * 60 * 24 * 7);
1181 
1182          dateDiff[YAHOO.widget.DateMath.DAY] = (Math.floor(timediff / (1000 * 60 * 60 * 24)));
1183          timediff -= dateDiff[YAHOO.widget.DateMath.DAY] * (1000 * 60 * 60 * 24);
1184 
1185          dateDiff[YAHOO.widget.DateMath.HOUR] = Math.floor(timediff / (1000 * 60 * 60));
1186          timediff -= dateDiff[YAHOO.widget.DateMath.HOUR] * (1000 * 60 * 60);
1187 
1188          dateDiff[YAHOO.widget.DateMath.MINUTE] = Math.floor(timediff / (1000 * 60));
1189          timediff -= dateDiff[YAHOO.widget.DateMath.MINUTE] * (1000 * 60);
1190 
1191          dateDiff[YAHOO.widget.DateMath.SECOND] = Math.floor(timediff / 1000);
1192          timediff -= dateDiff[YAHOO.widget.DateMath.SECOND] * 1000;
1193 
1194          if (dateDiff[YAHOO.widget.DateMath.WEEK] > 0)
1195          {
1196             duration += dateDiff[YAHOO.widget.DateMath.WEEK] + YAHOO.widget.DateMath.WEEK;
1197          }
1198          if (dateDiff[YAHOO.widget.DateMath.DAY] > 0)
1199          {
1200             duration += dateDiff[YAHOO.widget.DateMath.DAY] + YAHOO.widget.DateMath.DAY;
1201          }
1202          duration += 'T';
1203          if (dateDiff[YAHOO.widget.DateMath.HOUR] > 0)
1204          {
1205             duration += dateDiff[YAHOO.widget.DateMath.HOUR] + YAHOO.widget.DateMath.HOUR;
1206          }
1207          if (dateDiff[YAHOO.widget.DateMath.MINUTE] > 0)
1208          {
1209             duration += dateDiff[YAHOO.widget.DateMath.MINUTE] + 'M';
1210          }
1211          if (dateDiff[YAHOO.widget.DateMath.SECOND] > 0)
1212          {
1213             duration += dateDiff[YAHOO.widget.DateMath.SECOND] + YAHOO.widget.DateMath.SECOND;
1214          }
1215          return duration;
1216       },
1217 
1218       /**
1219        * Pads specified value with zeros if value is less than 10
1220        *
1221        * @method padZeros
1222        *
1223        * @param value {Object} value to pad
1224        * @return {String} padded value
1225        */
1226       padZeros: function Alfresco_CalendarHelper_padZeros(value)
1227       {
1228          return (value < 10) ? '0' + value : value;
1229       },
1230 
1231       /**
1232        * Converts a date string in the input field to a date object.
1233        *
1234        * @method getDateFromField
1235        *
1236        * @param field {DOM Object} input element
1237        * @return d {Date}
1238        */
1239       getDateFromField: function Alfresco_CalendarHelper_getDateFromField(field)
1240       {  
1241          var dateString = field.title;
1242          var d = (dateString !== "") ? fromISO8601(dateString) : new Date();
1243          return d;
1244       },
1245 
1246       /**
1247        * Formats the date
1248        *
1249        * @param field {DOM Object} DOM object of element
1250        */
1251       writeDateToField: function Alfresco_CalendarHelper_writeDateToField(date, field)
1252       {
1253          var formattedDate = dateFormat(date, Alfresco.util.message("date-format.fullDate"));
1254          field.value = formattedDate;
1255          field.title = toISO8601(date);
1256       },
1257 
1258       /**
1259        * Add an template using specified name as a reference
1260        */
1261       addTemplate: function Alfresco_CalendarHelper_addTemplate(name, template)
1262       {
1263          templates[name] = template;
1264       },
1265 
1266       /**
1267        * Retreives specified template
1268        *
1269        * @method getTemplate
1270        * @param name {string} Name of template to retrieve
1271        * @return {string} template
1272        */
1273       getTemplate: function Alfresco_CalendarHelper_getTemplate(name)
1274       {
1275          return templates[name];
1276       },
1277 
1278       /**
1279        * renders template as a DOM HTML element. Element is *not* added to document
1280        *
1281        * @param name Name of template to render
1282        * @param data Data to render template against
1283        * @return HTMLElement Newly created div
1284        */
1285       renderTemplate: function Alfresco_CalendarHelper_renderTemplate(name, data)
1286       {
1287          var el = document.createElement('div');
1288          if (templates[name] && el)
1289          {
1290             var el = YAHOO.lang.isString(el) ? Dom.get(el) : el;
1291             var template = templates[name];
1292             var div = document.createElement('div');
1293             if (data)
1294             {
1295                template = YAHOO.lang.substitute(template, data);
1296             }
1297 
1298             div.innerHTML = template;
1299             el.appendChild(div.firstChild);
1300 
1301             return el.lastChild;
1302          }
1303       },
1304 
1305       /**
1306        * Checks whether start date is earlier than end date.
1307        *
1308        * @method isValidDate
1309        * @param {Date} dtStartDate Start date
1310        * @param {Date} dtEndDate End date
1311        *
1312        * @return {Boolean} flag denoting whether date is valid or not.
1313        */
1314       isValidDate: function Alfresco_CalendarHelper_isValidDate(dtStartDate, dtEndDate)
1315       {
1316          return dtStartDate.getTime() < dtEndDate.getTime();
1317       },
1318 
1319       /**
1320        * Checks to see if the two dates are the same
1321        *
1322        * @method isSameDay
1323        * @param {Date|string} dateOne (either JS Date Object or ISO8601 date string)
1324        * @param {Date|string} dateTwo
1325        *
1326        * @return {Boolean} flag indicating if the dates are the same or not
1327        */
1328       isSameDay: function Alfresco_CalendarHelper_isSameDay(dateOne, dateTwo)
1329       {
1330          if (typeof(dateOne) === "string")
1331          {
1332             dateOne = fromISO8601(dateOne);
1333          }
1334          if (typeof(dateTwo) === "string")
1335          {
1336             dateTwo = fromISO8601(dateTwo);
1337          }
1338          return (dateOne.getDate() === dateTwo.getDate() && dateOne.getMonth() === dateTwo.getMonth() && dateOne.getFullYear() === dateTwo.getFullYear());
1339       },
1340       
1341       /**
1342        * Checks to see if dateOne is earlier than dateTwo or not
1343        *
1344        * @method isBefore
1345        * @param {Date|string} dateOne (either JS Date Object or ISO8601 date string)
1346        * @param {Date|string} dateTwo
1347        *
1348        * @return {Boolean}
1349        */
1350       isBefore: function Alfresco_CalendarHelper_isBefore(dateOne, dateTwo)
1351       {
1352          if (typeof(dateOne) === "string")
1353          {
1354             dateOne = fromISO8601(dateOne);
1355          }
1356          if (typeof(dateTwo) === "string")
1357          {
1358             dateTwo = fromISO8601(dateTwo);
1359          }
1360 
1361          return (dateOne < dateTwo);
1362 
1363       },
1364 
1365       /**
1366        * @method isAllDay
1367        * @param {Object} event data object
1368        *
1369        * @return {Boolean} flag indicating whether event is a timed event or not
1370        */
1371       isAllDay: function Alfresco_CalendarHelper_isTimedEvent(eventData)
1372       {
1373          var isSameDay = this.isSameDay(eventData.from, eventData.to);
1374          var isMidnight = (eventData.end == eventData.start && "00:00") ? true : false;
1375          return (!isSameDay && isMidnight);
1376       }
1377    };
1378 })();
1379 
1380 Alfresco.CalendarHelper.addTemplate('vevent', '<{el} class="vevent {allday} {hidden} {isoutlook} theme-bg-color-1 theme-border-2"> ' +
1381 '<{contEl}>' +
1382 '<p class="dates">' +
1383 '<span class="dtstart" title="{from}">{start}</span> - ' +
1384 '<span class="dtend" title="{to}">{end}</span>' +
1385 '</p>' +
1386 '<p class="description">{desc}</p>' +
1387 '<a class="summary theme-color-1" href="{uri}">{name}</a>' +
1388 '<span class="location">{where}</span>' +
1389 '<span class="duration" title="{duration}">{duration}</span>' +
1390 '<span class="category">{tags}</span>' +
1391 '</{contEl}>' +
1392 '</{el}>');
1393 Alfresco.CalendarHelper.addTemplate('agendaDay', '<h2>{date}</h2>');
1394 
1395 Alfresco.CalendarHelper.addTemplate('agendaDayItem', '<li class="vevent"><span>{start} - {end}</span>' +
1396 '<a href="{uri}" class="summary">{name}</a></li>');
1397 Alfresco.CalendarHelper.addTemplate('createEventButton', '<button id="addEventButton"><img src="{addEventUrl}" alt="{addEvent}" /></button>');
1398 Alfresco.CalendarHelper.addTemplate('taggedTitle', "<span class=\"tagged\">{taggedWith} <span class=\"theme-color-2\">'{tag}'</span></span>");