/*!
* LNK WCB Framework v2.5
* (c) 2010 Linkeo.com
* Use and redistribution are permitted. No warranty.
*
* Calendar core components
*/
var LnkLog, LnkWcb; // if already defined, log won't get clobbered just defining it
LnkLog = LnkLog || {};
LnkLog.log = LnkLog.log || function () {}; // if not defined, use a safe default value instead
LnkWcb = LnkWcb || {};
/**
* @module LnkWcb
*/
/**
* Registry instance used for registering calendars.
* @class Calendars
* @namespace LnkWcb
* @extends LnkWcb.Registry
* @static
* @private
*/
LnkWcb.Calendars = LnkWcb.core.createRegistry();
/**
* <p>
* Base Calendar. When using a calendar (or any subclass), you can chose one of three available flows.
* <p>
* <u><strong>Basic Flow</strong></u>
* <p>
* The basic flow (available by default) implements 2 states:
* <ul>
* <li><em>destroy</em> state: the initial state</li>
* <li><em>show</em> state: the calendar is displayed to the websurfer, and allows to select a date/time</li>
* </ul>
* To perform transitions from one basic state to another:
* <ul>
* <li>From <em>destroy</em> to <em>show</em>: call <a href="LnkWcb.Bouton.html#method_horaires"><code>LnkWcb.Bouton.horaires()</code></a></li>
* <li>From <em>show</em> to <em>destroy</em>: call <a href="#method_destroy"><code>LnkWcb.Calendar.destroy()</code></a></li>
* </ul>
* <p>
* The basic flow relies on the default value of <a href="#config_showOnInit"><code>showOnInit</code></a> config which is <code>true</code>.
* <p>
* <u><strong>Alternate Basic Flow</strong></u>
* <p>
* The alternate basic flow (also available by default) uses 3 states:
* <ul>
* <li><em>destroy</em> state: the initial state</li>
* <li><em>show</em> state: the calendar is displayed to the websurfer, and allows to select a date/time</li>
* <li><em>hide</em> state: the calendar is hidden from the websurfer</li>
* </ul>
* To perform transitions from one basic state to another:
* <ul>
* <li>After creation, to <em>show</em> the calendar, call <a href="#method_refresh"><code>LnkWcb.Calendar.refresh()</code></a></li>
* <li>From <em>show</em> to <em>hide</em>: call <a href="#method_destroy"><code>LnkWcb.Calendar.hide()</code></a></li>
* <li>From <em>hide</em> to <em>show</em>: call <a href="#method_refresh"><code>LnkWcb.Calendar.refresh()</code></a></li>
* </ul>
* <p>
* The alternate basic flow relies on the default value of <a href="#config_showOnInit"><code>showOnInit</code></a> config which is <code>true</code>.
* <p>
* <u><strong>Full Flow</strong></u>
* <p>
* When the <a href="#config_showOnInit"><code>showOnInit</code></a> config is set to <code>false</code>,
* the calendar instance then implement 4 successive states:
* <ul>
* <li><em>destroy</em> state: the initial state. No internal data is initialized (or all data is destroyed).</li>
* <li><em>init</em> state: the calendar is initialized with fresh open hours and closed days, received from the server.</li>
* <li><em>show</em> state: the calendar is displayed to the websurfer, and allows to select a date/time.</li>
* <li><em>hide</em> state: the calendar is hidden from the websurfer. All internal data is kept alive.</li>
* </ul>
* <p>
* At creation time (after <code>new LnkWcb.Calendar()</code> is executed), the created instance is in the <em>destroy</em> state.
* <p>
* Transitions in the full flow are:
* <ul>
* <li>From <em>destroy</em> to <em>init</em>: call <a href="LnkWcb.Bouton.html#method_horaires"><code>LnkWcb.Bouton.horaires()</code></a></li>
* <li>From <em>init</em> to <em>show</em>: call <a href="#method_show"><code>LnkWcb.Calendar.show()</code></a></li>
* <li>From <em>show</em> to <em>hide</em>: call <a href="#method_hide"><code>LnkWcb.Calendar.hide()</code></a></li>
* <li>From <em>hide</em> to <em>destroy</em>: call <a href="#method_destroy"><code>LnkWcb.Calendar.destroy()</code></a></li>
* </ul>
* @class Calendar
* @namespace LnkWcb
* @constructor
* @param cfg {Object} the initial configuration settings
*/
(function () {
var L = LnkWcb,
U = L.util,
T = L.Tests && L.Tests.testables,
C,
defaultSettings = {
/**
* The <a href="LnkWcb.Bouton.html"><code>LnkWcb.Bouton</code></a> instance on which this calendar depends.
* <p>
* You should not modify this setting after the calendar is created.
* Use the <a href="#method_setBouton"><code>setBouton</code></a> method instead.
* @config bouton
* @type LnkWcb.Bouton
* @default null
* @public
* @see setBouton()
*/
bouton: null,
/**
* Controls the feature that allows the calendar to be displayed as soon as possible.
* <p>
* This helps in simplifying the calendar 4 states.
* Which might then be reduced to <a href="#method_init"><code>init</code></a> and <a href="#method_destroy"><code>destroy</code></a> only.
* @config showOnInit
* @type Boolean
* @default true
* @protected
*/
showOnInit: true, // use the binary init/destroy state model instead of the 4 init/show/hide/destroy states
/**
* The step used when displaying available minutes.
* @config minutesStep
* @type Number
* @default 5
* @public
*/
minutesStep: 5,
/**
* The maximum days that can be selected ahead of now.
* @config maxDays
* @type Number
* @default 8
* @public
*/
maxDays: 8
},
defaultEvents = [
/**
* Fires right after the calendar is initialized.
* <p>
* When the <a href="#config_showOnInit"><code>showOnInit</code></a> config is <code>true</code>,
* it is fired just before the calendar is <a href="#method_show">showed</a>.
* @event onInit
* @param horaires {Object} the open hours (as received by the <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event)
* @protected
*/
'onInit',
/**
* Fires when the calendar is to be shown.
* <p>
* Subclasses should attach listeners to this event in order to actually display the calendar.
* @event onShow
* @protected
*/
'onShow',
/**
* Fires when the calendar is to be hidden.
* <p>
* Subclasses should attach listeners to this event in order to actually hide the calendar.
* @event onHide
* @protected
*/
'onHide',
/**
* Fires when the calendar is to be destroyed.
* <p>
* Subclasses should attach listeners to this event in order to
* destroy data that might have been initialized at the <a href="#method_init"><code>init</code></a> step.
* @event onDestroy
* @protected
*/
'onDestroy'
];
/**
* Parse the open hours (as received by the
* <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event)
* and split them into week days time period.
* <p>
* Time period are arrays containing these 3 numbers.
* <ol>
* <li>Start of the time period in the day (number of minutes from midnight, between 0 and 1440)</li>
* <li>End of the time period in the day (number of minutes from midnight, greater than the start of period)</li>
* <li>Day shift (usually <code>0</code>) to know which closed day (if any)
* in the channel time zone applies to this time period.</li>
* </ol>
* <p>
* The result is, for every day of week with open hours, an array of such
* time periods.
* @method parsePlagesHoraires
* @param plagesHoraires {Array<Object<String>>} the open hours
* @return {Object<Array<Array<Number>>>} the parsed open hours
* @private
*/
function parsePlagesHoraires(plagesHoraires) {
var dowTimeIntervals = {},
i, plage,
debut, fin, dayOfWeek1, dayOfWeek2, time1, time2, dayShift,
day;
for (i = 0; i < plagesHoraires.length; ++i) {
plage = plagesHoraires[i];
debut = plage.debut;
fin = plage.fin;
dayOfWeek1 = Number(debut.charAt(3));
dayOfWeek2 = Number(fin.charAt(3));
time1 = Number(debut.substring(5, 7)) * 60 + Number(debut.substring(8));
time2 = Number(fin.substring(5, 7)) * 60 + Number(fin.substring(8));
dayShift = Number(plage.debutHeureCanal.charAt(3)) - dayOfWeek1;
if (dayOfWeek1 === dayOfWeek2) {
if (!dowTimeIntervals[dayOfWeek1]) {
dowTimeIntervals[dayOfWeek1] = [];
}
dowTimeIntervals[dayOfWeek1].push([ time1, time2, dayShift ]);
}
else {
for (day = dayOfWeek1; day <= dayOfWeek2; ++day) {
if (!dowTimeIntervals[day]) {
dowTimeIntervals[day] = [];
}
if (day === dayOfWeek1) {
dowTimeIntervals[day].push([ time1, 24*60, dayShift ]);
}
else if (day === dayOfWeek2) {
dowTimeIntervals[day].push([ 0, time2, dayShift ]);
}
else { // should never happen for the moment
dowTimeIntervals[day].push([ 0, 24*60, dayShift ]);
}
--dayShift;
}
}
}
return dowTimeIntervals;
}
/**
* Dispatch the open time periods by day of week, hours of day, and minutes of hour.
* Example : <code>dowHoursMinutes[dow][hour][minutes]</code> is an array of the matching time periods for this instant.
* For hours and minutes arrays, sparse arrays are used.
* @method computeDowHoursMinutes
* @param dowTimeIntervals {Object<Array<Array<Number>>>} the parsed open hours, as computed by the <a href="#method_parsePlagesHoraires"><code>parsePlagesHoraires</code></a> method
* @param that {LnkWcb.Calendar} the <code>LnkWcb.Calendar</code> instance
* @return {Object<Array<Array<Array<Number>>>>} the time periods, dipatched by day of week, hours and minutes
* @private
*/
function computeDowHoursMinutes(dowTimeIntervals, that) {
var indexOf = U.indexOf,
dowHoursMinutes = {},
day, timeIntervals, hoursMinutes, time,
i, interval,
oldHour, hour, minutes, refIntervals;
for (day = 0; day < 7; ++day) {
timeIntervals = dowTimeIntervals[day];
if (!timeIntervals) {
continue;
}
hoursMinutes = []; // sparse array
hoursMinutes.refIntervals = [];
time = 0;
for (i = 0; i < timeIntervals.length; ++i) {
interval = timeIntervals[i];
time = interval[0];
time += that.cfg.minutesStep - 1;
time -= (time % 60) % that.cfg.minutesStep;
hour = Math.floor(time / 60);
minutes = time % 60;
oldHour = undefined;
while (time < interval[1]) {
if (oldHour !== hour) {
if (!hoursMinutes[hour]) {
hoursMinutes[hour] = []; // sparse array
hoursMinutes[hour].refIntervals = [];
}
refIntervals = hoursMinutes[hour].refIntervals;
if (indexOf(refIntervals, interval) < 0) {
refIntervals.push(interval);
}
oldHour = hour;
}
if (!hoursMinutes[hour][minutes]) {
hoursMinutes[hour][minutes] = [];
}
refIntervals = hoursMinutes[hour][minutes];
if (indexOf(refIntervals, interval) < 0) {
refIntervals.push(interval);
}
time += that.cfg.minutesStep;
hour = Math.floor(time / 60);
minutes = time % 60;
}
}
dowHoursMinutes[day] = hoursMinutes;
}
return dowHoursMinutes;
}
/*
function logDowTimeIntervals(dowTimeIntervals) {
var TPS = U.toPaddedString,
prop;
if (Array.prototype.map) {
for (var prop in dowTimeIntervals) {
//LnkLog.log('prop: '+prop);
if (!dowTimeIntervals.hasOwnProperty || dowTimeIntervals.hasOwnProperty(prop)) {
LnkLog.log(prop +' - timeIntervals: ['+dowTimeIntervals[prop].map(function(it) {
return '['+Math.floor(it[0]/60)+':'+TPS(it[0]%60,2)+','+Math.floor(it[1]/60)+':'+TPS(it[1]%60,2)+','+it[2]+']';
}).join(',')+']');
}
}
}
}
*/
/**
* Computes the days of week with no applicable open hours.
* @method computeDisabledDaysOfWeek
* @param dowTimeIntervals {Object<Array<Array<Number>>>} the parsed open hours, as computed by the <a href="#method_parsePlagesHoraires"><code>parsePlagesHoraires</code></a> method
* @return {Array<Number>} the list of days of week with no open hours
* @private
*/
function computeDisabledDaysOfWeek(dowTimeIntervals) {
var dow, disabledDaysOfWeek = [];
for (dow = 0; dow < 7; ++dow) {
if (!dowTimeIntervals[dow] || !dowTimeIntervals[dow].length) {
disabledDaysOfWeek.push(dow);
}
}
//LnkLog.log('disabledDaysOfWeek: ['+disabledDaysOfWeek.join(',')+']');
return disabledDaysOfWeek;
}
/**
* Parse closed days (as received by the
* <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event)
* and convert them to <code>Date</code> objects with the custom <code>yearlyRepeatable</code> member,
* which is a boolean indicating whether the year is applicable (<code>yearlyRepeatable===false</code>)
* or not (<code>yearlyRepeatable===true</code>).
* @method toLocalDate
* @param joursFeries {Array<Object<String>>} the closed days
* @param now {Date} the date to use as the current instant
* @return {Array<Date>} the array of (possibly yearly repeatable) dates
* @private
*/
function toLocalDate(joursFeries, now) {
var localDates = [],
i, partialDate, localDate,
year, month, day;
for (i = 0; i < joursFeries.length; ++i) {
partialDate = joursFeries[i].date;
localDate = new Date();
localDate.setHours(0, 0, 0, 0);
localDate.yearlyRepeatable = /^-/.test(partialDate);
if (localDate.yearlyRepeatable) {
year = null;
month = Number(partialDate.substring(2, 4));
day = Number(partialDate.substring(5, 7));
localDate.setMonth(month - 1, day);
if (localDate.getTime() < now.getTime()) {
localDate.setFullYear(localDate.getFullYear() + 1); // limits calendar validity to 1 year ahead
}
}
else {
year = Number(partialDate.substring(0, 4));
month = Number(partialDate.substring(5, 7));
day = Number(partialDate.substring(8, 10));
localDate.setFullYear(year, month - 1, day);
}
//log ('year: '+year+', month: '+month+', day: '+day+', localDate: '+localDate+'.');
localDates.push(localDate);
}
return localDates;
}
/*
function hasPartialIntervals(timeIntervals) {
var d;
if (!timeIntervals) {
return false;
}
d = 0;
while (d < timeIntervals.length && timeIntervals[d][2] === 0) {
++d;
}
return d < timeIntervals.length;
}
*/
/**
* Tells whether a <code>Date</code> instance is a worked date or not.
* @method isWorkedDate
* @param d {Date} the date to test
* @param joursFeries {Array<Object<String>>} the closed days (as received by the <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event)
* @return {Boolean} <code>true</code> if the date is not a closed day, <code>false</code> otherwise
* @private
*/
function isWorkedDate(d, joursFeries) {
var suffix, testDateMD, testDateYMD,
TPS = U.toPaddedString, i;
if (!d) {
return true;
}
suffix = "-" + TPS(d.getMonth() + 1, 2) + "-" + TPS(d.getDate(), 2);
testDateMD = "-" + suffix;
testDateYMD = TPS(d.getFullYear(), 4) + suffix;
i = 0;
while (i < joursFeries.length &&
!(testDateYMD === joursFeries[i].date ||
testDateMD === joursFeries[i].date)) {
++i;
}
return i >= joursFeries.length;
}
/*
function findTimeIntervalAfter(timeIntervals, time, date, matching) {
var i, interval, dateFuseauCanal;
for (i = 0; i < timeIntervals.length; i++) {
interval = timeIntervals[i];
if (interval && time < interval[1] && (!matching || time >= interval[0])) {
dateFuseauCanal = new Date(date.getTime() + 24*60*60*1000*interval[2]);
if (isWorkedDate(dateFuseauCanal)) {
return interval;
}
}
}
return null;
}
*/
/*
function findMatchingTimeInterval(timeIntervals, time, date) {
return findTimeIntervalAfter(timeIntervals, time, date, true);
}
*/
/*
/ * *
* @param timeIntervals : ordered intervals so that {@code timeIntervals[n][1] < timeIntervals[n+1][1]} ?
* No.
* What we have is {@code timeIntervals[n][0] <= timeIntervals[n+1][0]} for all {@code n}.
* So if there is a next time interval, it is the one with lower {@code interval[0]} value,
* so it is the one most likely to match.
* /
function findNextTimeInterval(timeIntervals, time, date) {
return findTimeIntervalAfter(timeIntervals, time, date, false);
}
*/
/**
* Tells if a <code>Date</code> instance is worked or not,
* taking all information into account (open hours and closed days).
* @method hasWorkedInterval_Internal
* @param refIntervals {Array<Array<Array<Number>>>} the dispatched time periods (see the <a href="#method_computeDowHoursMinutes"><code>computeDowHoursMinutes</code></a>) of the day of week of the <code>date</code> argument
* @param date {Date} the date to test
* @param joursFeries {Array<Object<String>>} the closed days (as received by the <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event)
* @return {Boolean} <code>true</code> if the date is worked, <code>false</code> otherwise
* @private
*/
function hasWorkedInterval_Internal(refIntervals, date, joursFeries/*, cache*/) {
var i, dateFuseauCanal/*,
cache = arguments[3] || {},
cached*/;
for (i = 0; i < refIntervals.length; ++i) {
/*
cached = cache[refIntervals[i][2]];
if (cached !== undefined) {
if (cached === true) {
return true;
}
continue;
}
*/
dateFuseauCanal = new Date(date.getTime() + 24*60*60*1000*refIntervals[i][2]);
if (isWorkedDate(dateFuseauCanal, joursFeries)) {
return true;
}
}
return false;
}
/**
* Parse closed days (as received by the <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event)
* and return a list of non worked local dates, in the websurfer time zone.
* @method parseJoursFeries
* @param joursFeries {Array<Object<String>>} the closed days (as received by the <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event)
* @param dowTimeIntervals {Object<Array<Array<Number>>>} the parsed open hours, as computed by the <a href="#method_parsePlagesHoraires"><code>parsePlagesHoraires</code></a> method
* @param now {Date} the date to use as the current instant
* @return {Array<Date>} a list of non worked local dates, in the websurfer time zone
* @private
*/
function parseJoursFeries(joursFeries, dowTimeIntervals/*, now*/) {
var nonWorkedLocalDates = [],
now = arguments[2] || new Date(),
localDates,
i, localDate, timeIntervals;
localDates = toLocalDate(joursFeries, now);
for (i = 0; i < localDates.length; ++i) {
localDate = localDates[i];
timeIntervals = dowTimeIntervals[localDate.getDay()];
if (!timeIntervals || !hasWorkedInterval_Internal(timeIntervals, localDate, joursFeries)) {
nonWorkedLocalDates.push(localDate);
}
}
return nonWorkedLocalDates;
}
function resetInternalState() {
this.initialized = false;
this.showOnNextInit = false;
this.displayed = false;
this.dowTimeIntervals = {};
this.dowHoursMinutes = {};
this.disabledDaysOfWeek = [];
this.joursFeries = [];
this.nonWorkedLocalDates = [];
this.startDate = null;
this.endDate = null;
}
if (T) {
// LnkLog.log('registering testables');
T.parsePlagesHoraires = parsePlagesHoraires;
T.computeDowHoursMinutes = computeDowHoursMinutes;
T.computeDisabledDaysOfWeek = computeDisabledDaysOfWeek;
T.toLocalDate = toLocalDate;
//T.hasPartialIntervals = hasPartialIntervals;
T.isWorkedDate = isWorkedDate;
//T.findNextTimeInterval = findNextTimeInterval;
T.hasWorkedInterval = hasWorkedInterval_Internal;
T.parseJoursFeries = parseJoursFeries;
T.resetInternalState = resetInternalState;
}
C = L.Calendar = function (cfg) {
var that = this;
this.id = L.Calendars.register(that);
/**
* The current applicable configuration settings.
* @property cfg
* @type Object
* @public
*/
that.cfg = U.cloneObject(cfg, defaultSettings);
that.events = L.events.create(that, defaultEvents);
resetInternalState.call(that);
that.onOpenHoursListener = function () {
that.init.apply(that, arguments);
}
if (that.cfg.bouton) {
that.cfg.bouton.onOpenHours(this.onOpenHoursListener);
}
C.onCreate.fire(that);
};
/**
* Class event. Fires when an <code>LnkWcb.Calendar</code> instance is just created.
* @event onCreate
* @param bouton {Object} the new instance
* @static
* @protected
*/
C.onCreate = new L.Event(C);
// class method
//C.findNextTimeInterval = findNextTimeInterval;
L.events.register(C.prototype, defaultEvents);
U.putAll(C.prototype, {
/**
* Get the default <code>LnkWcb.Calendar</code> configuration settings.
* @method getDefaultSettings
* @return {Object} a clone of the default settings
* @protected
*/
getDefaultSettings: function () {
return U.cloneObject(defaultSettings);
},
/**
* Set the <a href="LnkWcb.Bouton.html"><code>LnkWcb.Bouton</code></a> instance
* on which this calendar depends.
* @method setBouton
* @return {LnkWcb.Bouton} an bouton instance
* @public
*/
setBouton: function (bouton) {
if (this.cfg.bouton) {
this.cfg.bouton.events.onOpenHours.unregister(this.onOpenHoursListener);
}
this.cfg.bouton = bouton;
if (bouton) {
bouton.onOpenHours(this.onOpenHoursListener);
}
},
/**
* Abstract method that should return the selected date and time.
* <p>
* The returned value should be formated according to the LnkWcb convention, as implemented
* in the <a href="LnkWcb.util.html#method_formatDateTime"><code>LnkWcb.util.formatDateTime</code></a> method.
* @method getWcbDate
* @return {String} the default implementation always returns <code>undefined</code>
* @public
*/
getWcbDate: function () { // actually abstract method
},
/**
* Abstract method. Not documented. Reserved for future use.
* @method getDateTime
* @return {Object} the selected date, hours and minutes
* @private
*/
getDateTime: function () { // actually abstract method
return {
date: null,
hours: null,
minutes: null
};
},
/**
* Computes actual open hours in a sparse Array, with values being sparse Arrays of open minutes.
* <p>
* When requesting the open hours of the current day, this method removes passed open hours.
* Those that are before now.
* @method computeHoursMinutes
* @param date {Date} a date instance (never <code>null</code>) that specifies the selected day (today or a future day, never a passed day) of which open hours will be computed
* @param now {Date} an optional date that specifies the value for the current instant
* @return {Array<Array<Array<Number>>>}
* @protected
*/
computeHoursMinutes: function (date/*, now*/) {
var now = arguments[1] || new Date(),
hoursMinutes, hour, minutes;
try {
hoursMinutes = this.dowHoursMinutes[date.getDay()];
if (date.getDate() === now.getDate() &&
date.getMonth() === now.getMonth() &&
date.getYear() === now.getYear()) {
hoursMinutes = U.cloneObject(hoursMinutes);
for (hour = 0; hour < now.getHours(); ++hour) {
if (hoursMinutes[hour]) {
delete hoursMinutes[hour];
}
}
if (hoursMinutes[now.getHours()]) {
hoursMinutes[now.getHours()] = U.cloneObject(hoursMinutes[now.getHours()]);
for (minutes = 0; minutes < now.getMinutes(); minutes += this.cfg.minutesStep) {
if (hoursMinutes[now.getHours()][minutes] !== undefined) {
delete hoursMinutes[now.getHours()][minutes];
}
}
}
}
return hoursMinutes;
}
catch (exc) {
LnkLog.log('LnkWcb.Calendar.computeHoursMinutes', exc);
}
},
/**
* Tells if a <code>Date</code> instance is worked or not,
* taking all information into account (open hours and closed days).
* @method hasWorkedInterval
* @param refIntervals {Array<Array<Array<Number>>>} the dispatched time periods (see the <a href="#method_computeDowHoursMinutes"><code>computeDowHoursMinutes</code></a>) of the day of week of the <code>date</code> argument
* @param date {Date} the date to test
* @return {Boolean} <code>true</code> if the date is worked, <code>false</code> otherwise
* @protected
*/
hasWorkedInterval: function (refIntervals, date/*, cache*/) {
return hasWorkedInterval_Internal(refIntervals, date, this.joursFeries/*, cache*/);
},
/**
* @method findFirstAvailableDate
* @return {Date}
* @protected
*/
findFirstAvailableDate: function () {
var date = (this.startDate && new Date(this.startDate.getTime())) || new Date(),
end = this.endDate || new Date(date.getTime() + 7 * 86400000), // ECMAScript dates have no leap seconds anyway
timeIntervals, hoursMinutes, h;
while (date.getTime() < end.getTime()) {
timeIntervals = this.dowTimeIntervals[date.getDay()];
if (timeIntervals && this.hasWorkedInterval(timeIntervals, date)) { // general case
hoursMinutes = this.computeHoursMinutes(date); // handle the 'today' case
for (h = 0; h < hoursMinutes.length; ++h) {
if (hoursMinutes[h] && U.countDefined(hoursMinutes[h]) > 0) {
date.setHours(0, 0, 0, 0);
return date;
}
}
}
date.setDate(date.getDate() + 1);
}
return null;
},
/**
* This is <u>the</u> all-in-one method that refreshes the calendar. It asks the bouton for fresh open hours and closed days.
* <p>
* Get through the following states:
* <em>hide</em> (if needed),
* <em>destroy</em> (won't do no harm is instance is not yet initialized),
* <em>init</em> (after the fresh open hours have been received from the <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event),
* <em>show</em> (if <a href="#config_showOnInit"><code>showOnInit</code></a> is <code>true</code>, which is the default).
* @method refresh
* @public
*/
refresh: function () {
this.destroy();
if (this.cfg.bouton) {
this.cfg.bouton.horaires();
}
},
/**
* Initialize the calendar with fresh open hours and closed days.
* <p>
* This method is called by default when the
* <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event
* of the <a href="#config_bouton">associated bouton</a> fires. Thus you should never need to call it directly.
* <p>
* Perform the transition from <em>destroy</em> state to <em>init</em> state.
* <p>
* If <a href="#config_showOnInit"><code>showOnInit</code></a> is <code>true</code> (the default),
* then the calendar is <a href="#method_show">shown</a> right after it is initialized.
* @method init
* @param horaires {Object} the open hours (as received by the <a href="LnkWcb.Bouton.html#event_onOpenHours"><code>onOpenHours</code></a> event)
* @protected
*/
init: function (horaires) { // may be called in any state (initialized or not, displayed or not)
var startDate, now, todayHoursMinutes, h;
try {
this.initialized = false;
//LnkLog.log('debut LnkWcb.Calendar.init');
this.dowTimeIntervals = parsePlagesHoraires(horaires.horairesOuverture);
// logDowTimeIntervals(this.dowTimeIntervals);
this.dowHoursMinutes = computeDowHoursMinutes(this.dowTimeIntervals, this);
this.disabledDaysOfWeek = computeDisabledDaysOfWeek(this.dowTimeIntervals);
this.joursFeries = horaires.joursFeries;
this.nonWorkedLocalDates = parseJoursFeries(this.joursFeries, this.dowTimeIntervals);
//LnkLog.log('fin LnkWcb.Calendar.init');
now = new Date();
startDate = new Date(now.getTime());
startDate.setHours(0, 0, 0, 0);
todayHoursMinutes = this.computeHoursMinutes(startDate, now);
h = 0;
while (h < todayHoursMinutes.length && !(todayHoursMinutes[h] && U.countDefined(todayHoursMinutes[h]) > 0)) {
++h;
}
if (h >= todayHoursMinutes.length) {
startDate.setDate(startDate.getDate() + 1); // properly handles calendar rolling: 2010-01-32 becomes 2010-02-01
}
this.startDate = startDate;
this.endDate = new Date(startDate.getTime());
this.endDate.setDate(this.endDate.getDate() + this.cfg.maxDays);
this.events.onInit.fire(horaires);
this.initialized = true;
if (this.showOnNextInit || this.cfg.showOnInit) {
this.showOnNextInit = false;
this.show();
}
}
catch (exc) {
LnkLog.log('LnkWcb.Calendar.init', exc);
}
},
/**
* Display the calendar to the websurfer.
* <p>
* Perform the transition from <em>init</em> state to <em>show</em> state.
* <p>
* If the calendar is not yet <a href="#method_init">initilazed</a>, then it will be shown
* right after it is initialized. Even if the <a href="#config_showOnInit"><code>showOnInit</code></a>
* config is <code>false</code>.
* <p>
* Would you create a calendar with <a href="#config_showOnInit"><code>showOnInit</code></a>
* being <code>false</code>, this feature would allow you to initialize the calendar with
* <a href="LnkWcb.Bouton.html#method_horaires"><code>LnkWcb.Bouton.horaires()</code></a> and then call
* <a href="#method_show"><code>show()</code></a> right after <code>horaires()</code> has returned,
* which is <u>before</u> the fresh open hours are received from the server,
* and thus <u>before</u> the calendar is actually initialized.
* @method show
* @public
*/
show: function () { // only when initialized and not displayed (when not initialized, activate the showOnNextInit)
try {
if (!this.initialized) {
//LnkLog.log('activating the showOnNextInit feature');
this.showOnNextInit = true;
return;
}
if (!this.displayed) {
this.events.onShow.fire();
}
this.displayed = true;
}
catch (exc) {
LnkLog.log('LnkWcb.Calendar.show', exc);
}
},
/**
* Hide the calendar.
* <p>
* Perform the transition from <em>show</em> state to <em>hide</em> state.
* @method hide
* @public
*/
hide: function () { // only when initialized and displayed
try {
if (this.displayed) {
this.events.onHide.fire(); // TODO: should fire in reverse order
}
this.displayed = false;
}
catch (exc) {
LnkLog.log('LnkWcb.Calendar.hide', exc);
}
},
/**
* Destroy the calendar.
* <p>
* Perform the transition from <em>hide</em> state to <em>destroy</em> state.
* <p>
* If the calendar is not yet hidden, then the <a href="#method_hide"><code>hide</code></a> method is called.
* @method destroy
* @public
*/
destroy: function () { // may be called in any state (initialized or not, displayed or not)
try {
if (this.displayed) {
this.hide();
}
this.events.onDestroy.fire(); // TODO: should fire in reverse order
resetInternalState.call(this);
}
catch (exc) {
LnkLog.log('LnkWcb.Calendar.destroy', exc);
}
}
});
})();
/*
(function () {
var CAL_CMPT,
defaultEvents = [ 'onInit', 'onShow', 'onHide', 'onDestroy' ];
CAL_CMPT = C.Component = function () {
this.events = L.events.create(this, defaultEvents);
};
L.events.register(CAL_CMPT.prototype, defaultEvents);
U.putAll(CAL_CMPT.prototype, {
init: function () {
this.events.onInit.fire();
},
show: function () {
this.events.onShow.fire();
},
hide: function () {
this.events.onHide.fire();
},
destroy: function () {
this.events.onDestroy.fire();
}
});
})();
*/