calendarEvents.js
Summary
No overview generated for 'calendarEvents.js'
| Class Summary | |
| CalendarEvent | |
| CalendarEvents | |
/* * Copyright (c) 2006, Opera Software ASA * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Opera Software ASA nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY OPERA SOFTWARE ASA AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL OPERA SOFTWARE ASA AND CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * Class for calendarEvents * * This class handles calendar events that can have several occurences * calendarEvent are stored in a public list. * * @constructor * @author Mathieu HENRI, Opera Software ASA * @version 1.0.1 */ function CalendarEvents() { calendarEventsSelf = this; /** dictionnary with the Id of the calendarEvent **/ this.calendarEventIdList = {}; /** array of the calendarEvent **/ this.calendarEventArray = []; /********* Interface methods ***********/ /** * convert the array of calendarEvent into a JSON string * @return a JSONString */ this.toString = function() { var eventsList = [] for( var i=0,e; e=this.calendarEventArray[i]; i++ ) eventsList.push( { id :e.id, start :e.start.valueOf(), end :e.end.valueOf(), description :e.description, type :e.type, url :e.url, reminderCount :e.reminderCount, reminderInterval :e.reminderInterval, remindersMaxLate :e.remindersMaxLate, occurenceCount :e.occurenceCount, occurenceInterval :e.occurenceInterval, occurenceIntervalUnit :e.occurenceIntervalUnit }) return eventsList.toJSONString() } /** * parse a JSON string to populate the array of calendarEvent instance * @return a boolean indicating the success of the method */ this.parse = function( str ) { // wipe all the events first for( var i=0,e; e=this.calendarEventArray[i]; i++ ) this.deleteEvent( e.id ); var eventsList = str.parseJSON() for( var i=0,e; e=eventsList[i]; i++ ) if( !this.setEvent( e.id, new Date( e.start ), new Date( e.end ), e.description, e.type, e.url, e.reminderCount, e.reminderInterval, e.remindersMaxLate, e.occurenceCount, e.occurenceInterval, e.occurenceIntervalUnit ) ) return false; return true } /** * set the ReminderCallback function * see the reminderFallback method for the list of arguments * the ReminderCallback function should accept * @param fct reminder function * @return a boolean indicating the success of the method */ this.setReminderCallback = function( fct ) { if( typeof(fct)!='function' ) return false this.reminderCallback = fct; return true; } /** * reset the ReminderCallback function */ this.resetReminderCallback = function() { delete this.reminderCallback; } /** * call the reminderCallback function for one occurence of a calendarEvent * @param escapedId escapedId of the calendarEvent * @param occurence * @param reminderIndex the index of the reminder in the range [ -reminderCount ; remindersMaxLate ] */ this.callReminder = function( escapedId, occurence, reminderIndex ) { var e = this.getEvent( unescape( escapedId ) ) if( !e ) return false; if( e.nextOccurenceStart<=new Date() ) e.nextOccurence( true ); return (this.reminderCallback?this.reminderCallback:this.reminderFallback)( e, occurence, reminderIndex ); } /** * the reminder fallback method * @param evt a calendarEvent * @param occurence the occurence related to this reminder * @param reminderIndex the index of the reminder in the range [ -reminderCount ; remindersMaxLate ] */ this.reminderFallback = function( evt, occurence, reminderIndex ) { if( !confirm( '____calendarEvents.reminderFallback____\n\nREMINDER #'+ reminderIndex +' for the occurence #'+ occurence +' of\n\n'+ evt.id +'\n\ndo you want to keep the reminders for this occurence ?' ) ) calendarEventsSelf.stopEventReminder( evt.id, occurence ); return true; } /** * stops the reminder for one occurence of a calendarEvent * @param id id of the calendarEvent * @param occurence the index of the occurence of the calendarEvent to stop the reminders of * @return a boolean indicating if reminder have been stopped correctly */ this.stopEventReminder = function( id, occurence ) { var e = this.getEvent( id ) if( !e ) return false; return e.clearReminderTimeout( occurence ); } /** * dismiss the next occurence of a calendarEvent and move on * to the following occurence * @param id id of the calendarEvent * @return a */ this.dismissEvent = function( id ) { var e = this.getEvent( id ); if( !e ) return false; e.dismiss(); this.sortEvents(); return true; } /** * get all the instances of calendarEvent * @return an array with all the instances of calendarEvent */ this.getAllEvents = function() { return this.calendarEventArray; } /** * get a calendarEvent * @param id id of the desired calendarEvent * @return the calendarevent or null if it does not exist */ this.getEvent = function( id ) { if( !this.calendarEventIdList[ id ] ) return null; for( var i=0,e; e=this.calendarEventArray[i]; i++ ) if( e.id==id ) return e; } /** * get all the instances of calendarEvent of a given type * @param type the desired type of calendarEvent * @return an array of calendarEvent */ this.getEventsByType = function( type ) { var events = []; for( var i=0,e; e=this.calendarEventArray[i]; i++ ) if( e.type==type ) events.push( e ); return events; } /** * get the index of a calendarEvent in the array of calendarEvent * @param id id of the desired calendarEvent * @return the index of the desried calendarevent in the array or -1 if it does not exist */ this.getEventIndex = function( id ) { if( !this.calendarEventIdList[ id ] ) return -1; for( var i=0,e; e=this.calendarEventArray[i]; i++ ) if( e.id==id ) return i; } /** * delete a calendarEvent * @param id id of the desired calendarEvent * @return a boolean indicating the success of the method */ this.deleteEvent = function( id ) { if( !this.calendarEventIdList[ id ] ) return false; var eventIndex = this.getEventIndex( id ); this.calendarEventArray[ eventIndex ].clearAllReminderTimeout(); delete this.calendarEventIdList[ id ]; delete this.calendarEventArray[ eventIndex ]; this.sortEvents() return true; } /** * sort the array of calendarEvent by increasing date of next occurence */ this.sortEvents = function() { this.calendarEventArray.sort( function( a,b ) { return a.nextOccurenceStart-b.nextOccurenceStart; } ) } /** * create a calendarEvent * @param id id of the desired calendarEvent * @param start the start date of the first occurence * @param end the end date of the first occurence, or null if the calendarEvent has no duration * @param description the description of the calendarEvent * @param type the type of the calendarEvent * @param url a URL related to the calendarEvent * @param reminderCount the number of reminder before the calendarEvent * @param reminderInterval the interval between the reminders (in ms) * @param remindersMaxLate the number of reminder after the beginning of the calendarEvent * @param occurenceCount the number of occurence or null if there is only occurence * @param occurenceInterval the amount of occurenceIntervalUnit between the occurences of the calendarEvent * @param occurenceIntervalUnit the time unit of occurenceInterval ( see the isValidUnit method ) * @see #isValidUnit * @return the calendarEvent or null if its creation failled */ this.setEvent = function( id, start, end, description, type, url, reminderCount, reminderInterval, remindersMaxLate, occurenceCount, occurenceInterval, occurenceIntervalUnit ) { if( this.calendarEventIdList[ id ] ) return null; var occurenceCount = occurenceCount||1, occurenceInterval = occurenceInterval||0, occurenceIntervalUnit = occurenceIntervalUnit||'milliseconds' if( occurenceInterval<0 ) // events do not occurs back in time ... do they ? return null; if( !isValidUnit( occurenceIntervalUnit ) ) return null; var now = new Date(); this.calendarEventIdList[ id ] = true; this.calendarEventArray.push( new CalendarEvent( id, start, end, description, type, url, reminderCount, reminderInterval, remindersMaxLate, occurenceCount, occurenceInterval, occurenceIntervalUnit ) ); this.sortEvents(); return this.getEvent( id ); } /** * increments a date by a given quantiy of time units * @param date the base date * @param quantity the quantiy of time units * @param unit the unit of time to add ( see the isValidUnit method ) * @see #isValidUnit * @return return the incremented date or quantity if quantity is +/-Infinity */ this.incrementDate = function( date, quantity, unit ) { var unit = (unit||'milliseconds').toLowerCase(); if( Math.abs(quantity)==Infinity ) return quantity; if( unit=='milliseconds' ) return new Date( date.valueOf()+quantity ); if( unit=='seconds' ) return new Date( date.valueOf()+quantity*1000 ); if( unit=='minutes' ) return new Date( date.valueOf()+quantity*1000*60 ); if( unit=='hours' ) return new Date( date.valueOf()+quantity*1000*60*60 ); if( unit=='days' ) return new Date( date.valueOf()+quantity*1000*60*60*24 ); if( unit=='weeks' ) return new Date( date.valueOf()+quantity*1000*60*60*24*7 ); var retDate = new Date( date ); if( unit=='years' ) quantity *= 12; retDate.setMonth( retDate.getMonth()+quantity ) return retDate; } /** * get the calendarEvent between two dates * @param rangeBegin the lower limit date * @param rangeEnd the upper limit date * @return an array with all the calendarEvent that might occur between the two dates */ this.getEventsInPeriod = function( rangeBegin, rangeEnd ) { var eventsInPeriodArray = [], rangeBegin = rangeBegin.valueOf(), rangeEnd = rangeEnd.valueOf(); if( rangeBegin>rangeEnd ) // invalid date range return eventsInPeriodArray; for( var i=0,e; e=this.calendarEventArray[i]; i++ ) if( e.getOccurencesStartingBefore( rangeEnd ).length > e.getOccurencesEndingBefore( rangeBegin ).length ) eventsInPeriodArray.push( e ); return eventsInPeriodArray; } /** * get the number of ms from a given date to the closest calendarEvent * @param dateStart the reference date * @param inFuture a boolean ( true by default ) indicating if the interval must be looked in the future or in the past * @return the number of ms from a given date to the closest calendarEvent */ this.getFreeRange = function( dateStart, inFuture ) { var inFuture = inFuture==undefined?true:(inFuture?true:false), timestampClosestOccurence = inFuture?Infinity:-Infinity, dateStart = dateStart.valueOf() // no calendarEvent in the array -> +/-Infinity if( !this.calendarEventArray.length ) return timestampClosestOccurence; for( var i=0,e; e=this.calendarEventArray[i]; i++ ) { var previousOccurences = inFuture?e.getOccurencesEndingBefore( dateStart ):e.getOccurencesStartingBefore( dateStart ), usefulOccurenceDates = { start:e.start, end:e.end } if( previousOccurences.length ) { usefulOccurenceDates = inFuture?e.getOccurenceDates( previousOccurences.length+1 ):previousOccurences.pop(); if( !usefulOccurenceDates ) continue; } // right during the occurence of a calendarEvent -> 0 if( usefulOccurenceDates.start<=dateStart && usefulOccurenceDates.end>=dateStart ) return 0; if( inFuture && usefulOccurenceDates.start>dateStart ) timestampClosestOccurence = Math.min( timestampClosestOccurence, usefulOccurenceDates.start ); else if( !inFuture && usefulOccurenceDates.end<dateStart ) timestampClosestOccurence = Math.max( timestampClosestOccurence, usefulOccurenceDates.end ); } return timestampClosestOccurence-dateStart; } /** * shift a calendarEvent by a given quantiy of time units * @param id id of the calendarEvent * @param time the quantiy of time units * @param timeUnit the unit of time to add ( see the isValidUnit method ) * @see #isValidUnit * @return a boolean indicating the success of the method */ this.shiftEvent = function( id, time, timeUnit, overlap ) { var e = this.getEvent( id ); overlap = overlap==undefined?true:(overlap?true:false) if( !e || !isValidUnit( timeUnit ) || typeof( time )!='number' ) return false; var candidateStart = this.incrementDate( e.nextOccurenceStart, time, timeUnit ); candidateEnd = this.incrementDate( e.nextOccurenceEnd, time, timeUnit ); if( !overlap ) { var eventsInCandidatePeriod = this.getEventsInPeriod( candidateStart, candidateEnd ); if( eventsInCandidatePeriod.length>1 || (eventsInCandidatePeriod.length==1 && eventsInCandidatePeriod[0].id!=id) ) return false; } this.getEvent( id ).shiftDates( time, timeUnit ); this.sortEvents(); return true; } /** * get the closest date to a given date when a desired amount ms of will not overlap some calendarEvent * @param dateStart the reference date * @param desiredLength the amount of ms desired * @param inFuture a boolean ( true by default ) indicating if the date must be looked in the future or in the past * @return the closest date the desired amount of ms is available or null */ this.findFreeRange = function( dateStart, desiredLength, inFuture ) { var inFuture = inFuture==undefined?true:(inFuture?true:false), timestampClosestOccurence = inFuture?Infinity:-Infinity, timestampStart = dateStart.valueOf() if( !this.calendarEventArray.length ) return timestampStart; // check if the intervals could allow desiredLength var maxInterval = 0, maixmizedIntervalPerUnit = { 'milliseconds': 1, 'seconds': 1000, 'minutes': 1000*60, 'hours': 1000*60*60, 'days': 1000*60*60*24, 'weeks': 1000*60*60*24*7, 'months': 1000*60*60*24*31, 'years': 1000*60*60*24*366 }; for( var i=0,e; e=this.calendarEventArray[i]; i++ ) if( e.occurenceCount>1 ) maxInterval = Math.max( maxInterval, maixmizedIntervalPerUnit[ e.occurenceIntervalUnit ]*e.occurenceInterval ); if( maxInterval<desiredLength ) return null; // search the closest date available var attempts = 0; while( attempts<99 ) { var timestampStartOld = timestampStart, candidateDateStart = new Date( timestampStart ), candidateDateEnd = new Date( timestampStart+desiredLength ), eventsInPeriod = this.getEventsInPeriod( candidateDateStart, candidateDateEnd ); if( !eventsInPeriod.length ) return timestampStart; for( var i=0,e; e=eventsInPeriod[i]; i++ ) { if( inFuture ) { var usefulOccurences = e.getOccurencesStartingBefore( candidateDateEnd ) if( usefulOccurences.length ) timestampStart = Math.max( timestampStart, usefulOccurences.pop().end.valueOf() ) } else { var usefulOccurences = e.getOccurencesEndingBefore( candidateDateEnd ) if( usefulOccurences.length ) timestampStart = Math.min( timestampStart, usefulOccurences.pop().start.valueOf()-desiredLength ) } } if( timestampStartOld == timestampStart ) return timestampStart; // no change == looping through the last occurences attempts++; } return null; } /** * check the validity of a time unit * @member CalendarEvents * @private * @param unit a time unit provided by the user or a calendarEvent * @return true if the time unit is valid, false otherwise */ function isValidUnit( unit ) { switch( (unit||'').toLowerCase() ) { case 'milliseconds': case 'seconds': case 'minutes': case 'hours': case 'days': case 'weeks': case 'months': case 'years': return true; } return false; } } /** * Class representing one calendarEvent * @constructor * @author Mathieu HENRI, Opera Software ASA * @version 1.0 */ function CalendarEvent( _id, _start, _end, _description, _type, _url, _reminderCount, _reminderInterval, _remindersMaxLate, _occurenceCount, _occurenceInterval, _occurenceIntervalUnit ) { calendarEventSelf = this; this.id = _id; this.start = _start; this.end = _end||_start; this.description = _description; this.type = _type; this.url = _url; // reminder(s) this.reminderCount = _reminderCount||0; this.reminderInterval = _reminderInterval||0; this.remindersMaxLate = _remindersMaxLate||0; // occurence(s) this.occurenceCount = _occurenceCount||1; this.occurenceInterval = _occurenceInterval||0; this.occurenceIntervalUnit = _occurenceIntervalUnit||0; this.occurenceDates = []; var currentOccurenceIndex = 1, dismissedOccurences = [], reminderTimeoutArray = [] /********* Interface methods ***********/ /** * dismiss the current occurence and move on to the next one */ this.dismiss = function() { dismissedOccurences[ currentOccurenceIndex ] = true; this.nextOccurence(); } /** * set the timeout for the reminders of a given occurence * @param occurence default to the currenceOccurence * @return a boolean indicating of the desired occurence exist or false if it has been dismissed */ this.setReminderTimeout = function( occurence ) { var occurence = occurence!=undefined?occurence:currentOccurenceIndex, occurenceDates = this.getOccurenceDates( occurence ) if( !occurenceDates ) return false; var timestampDelta = occurenceDates.start-(new Date().valueOf()); if( !reminderTimeoutArray[occurence] ) reminderTimeoutArray[occurence] = [] this.clearReminderTimeout( occurence ); if( dismissedOccurences[ occurence ] ) return false; for( var i=-this.reminderCount; i<=this.remindersMaxLate; i++ ) { var delay = timestampDelta+i*this.reminderInterval if( delay>0 ) reminderTimeoutArray[occurence].push( setTimeout( 'calendarEventsSelf.callReminder( "'+ escape( this.id ) +'", '+ occurence +', '+ i +' )', delay ) ) } return true; } /** * clear the timeout for the reminders of a given occurence * @param occurence default to the currenceOccurence */ this.clearReminderTimeout = function( occurence ) { var occurence= occurence!=undefined?occurence:currentOccurenceIndex if( reminderTimeoutArray[occurence] ) while( reminderTimeoutArray[occurence].length ) clearTimeout( reminderTimeoutArray[occurence].pop() ); } /** * clear ALL the timeout for the reminders */ this.clearAllReminderTimeout = function() { for( var occurence in reminderTimeoutArray ) if(typeof(occurence)=='number') this.clearReminderTimeout( occurence ); } /** * shift the dates of the calendar event by a a given quantity of time units and reset the reminder timeout and reset the reminder timeout * @param time the quantiy of time units * @param timeUnit the unit of time to add ( see the isValidUnit method ) * @see calendarEvents#isValidUnit */ this.shiftDates = function( time, timeUnit ) { this.start = calendarEventsSelf.incrementDate( this.start, time, timeUnit ); this.end = calendarEventsSelf.incrementDate( this.end, time, timeUnit ); // wipe the occurenceDates this.occurenceDates = []; // reset nextOccurenceDates var nextOccurenceDates = this.getOccurenceDates( currentOccurenceIndex ); this.nextOccurenceStart = nextOccurenceDates.start; this.nextOccurenceEnd = nextOccurenceDates.end; this.setReminderTimeout(); } /** * get the start and end dates of a given occurence * @param occurenceIndex the index of the desired occurence * @return an object with a start and end dates or null if the desired occurenceIndex is invalid */ this.getOccurenceDates = function( occurenceIndex ) { if( occurenceIndex<1 || occurenceIndex>this.occurenceCount || dismissedOccurences[ occurenceIndex ] ) return null; if( this.occurenceDates[ occurenceIndex ] ) return this.occurenceDates[ occurenceIndex ]; occurenceDates = { start: calendarEventsSelf.incrementDate( this.start, (occurenceIndex-1)*this.occurenceInterval, this.occurenceIntervalUnit ), end: calendarEventsSelf.incrementDate( this.end, (occurenceIndex-1)*this.occurenceInterval, this.occurenceIntervalUnit ) } this.occurenceDates[ occurenceIndex ] = occurenceDates; return occurenceDates; } /** * move on to the next occurence * @param maintainReminderTimeout an optionnal boolean ( false by default ) indicating if the reminder timeouts should be maintain for the current occurence * @return a boolean indicating if the next occurence exist */ this.nextOccurence = function( maintainReminderTimeout ) { var maintainReminderTimeout = maintainReminderTimeout!=undefined?maintainReminderTimeout:false if( !maintainReminderTimeout ) this.clearReminderTimeout(); if( currentOccurenceIndex<this.occurenceCount ) { var nextOccurenceDates = this.getOccurenceDates( currentOccurenceIndex ); this.nextOccurenceStart = nextOccurenceDates.start; this.nextOccurenceEnd = nextOccurenceDates.end; currentOccurenceIndex++; this.setReminderTimeout(); return true; } this.nextOccurenceStart = this.nextOccurenceEnd = Infinity; return false; } /** * get the dates of all the occurences ending before a given date * @param dateLimit the reference date * @return an array of objects with the start and end dates of the corresponding occurences */ this.getOccurencesEndingBefore = function( dateLimit ) { var occurenceDates = []; var occurenceCount = this.occurenceCount, j = 1, datesOccurenceJ = {start:this.start,end:this.end,occurenceIndex:1}; while( datesOccurenceJ.end<dateLimit && j<=occurenceCount ) { occurenceDates.push( datesOccurenceJ ) datesOccurenceJ = this.getOccurenceDates( ++j ); if( !datesOccurenceJ ) return occurenceDates; datesOccurenceJ.occurenceIndex = j; } return occurenceDates; } /** * get the dates of all the occurences starting before a given date * @param dateLimit the reference date * @return an array of objects in the form of {@link toString#utility} with the start and end dates of the corresponding occurences */ this.getOccurencesStartingBefore = function( dateLimit ) { var occurenceDates = []; var occurenceCount = this.occurenceCount, j = 1, datesOccurenceJ = {start:this.start,end:this.end,occurenceIndex:1}; while( datesOccurenceJ.start<dateLimit && j<=occurenceCount ) { occurenceDates.push( datesOccurenceJ ) datesOccurenceJ = this.getOccurenceDates( ++j ); if( !datesOccurenceJ ) return occurenceDates; datesOccurenceJ.occurenceIndex = j; } return occurenceDates; } // make sure the next occurence in not in the past var now = new Date(); this.nextOccurenceStart = this.start; this.nextOccurenceEnd = this.end; this.setReminderTimeout(); if( this.occurenceCount ) while( now>this.nextOccurenceEnd && currentOccurenceIndex<this.occurenceCount ) this.nextOccurence(); }
Documentation generated by JSDoc on Tue Oct 24 12:47:04 2006

