/*
Copyright (c) 2010, Lamplight Database Systems Limited, http://www.lamplightdb.co.uk
Code licensed under the BSD License:
http://developer.yahoo.com/yui/license.html
version: 1.4
*/
/**
* YAHOO.widget.Diary produces a weekly diary showing events, and allows them to
* be editted.
*
* @module diary
* @requires yahoo, event, event-delegate, event-mouseenter, dom, datasource, selector, dragdrop, resize, datemath, calendar
* @optional anim, container
* @title Diary widget
*
* @description
* <p>Diary uses a YAHOO.util.Datasource with, minimally, start and end dates.
* Items in the diary can be rescheduled using drag and drop, or times changed using resize.
* New items can be added by click-and-dragging on the diary to provide the
* start and end times.</p>
*
*
* <strong>Changelog</strong>
* v 1.0 Initial release. Core data, UI, navigation and events.
* v 1.1 Single day view added
* v 1.2 - Month view added.
* - Bug fix on Opera to set height and scrollTop.
* - itemBeforeStartMove passed originEvent through to subscribers.
* v 1.3 Added footer and footerText config property.
* - Added dataRequest method
* - Minor fixes
* v 1.31 Items expand or contract in width when filters are added/removed
* v 1.32 Added a silent parameter to DiaryItem._handleDiaryEndDrag() to prevent events.
* v 1.4 Added year view. Fixed bug with multi-day items and with BST/GMT month view.
*
*/
(function () {
var Dom = YAHOO.util.Dom,
Ev = YAHOO.util.Event,
DM = YAHOO.widget.DateMath,
Lang = YAHOO.lang,
Selector = YAHOO.util.Selector,
CLASS_DIARY = "yui-diary",
CLASS_DIARY_ITEM_DETAILS = "yui-diary-item-details",
CLASS_DIARY_ITEM = "yui-diary-item",
CLASS_DIARY_DATACONTAINER = "yui-diary-datacontainer",
CLASS_DIARYDAY_CONTAINER = "yui-diaryday-container",
CLASS_DIARY_BACKGROUND = "yui-diary-background",
CLASS_DIARY_HOURBLOCK = "yui-diary-hourblock",
CLASS_DIARY_DAY = "yui-diary-day",
CLASS_DIARY_CONTAINER = "yui-diary-container",
CLASS_DIARY_TODAY = "yui-diary-today",
CLASS_DIARY_NAV = "yui-diary-nav",
CLASS_DIARY_TITLE = "yui-diary-title",
CLASS_DIARY_NAV_BUTTONS = "yui-diary-nav-buttons",
CLASS_DIARY_NAV_LEFT = "yui-diary-nav-left",
CLASS_DIARY_NAV_RIGHT = "yui-diary-nav-right",
CLASS_DIARY_NAV_TODAY = "yui-diary-nav-today",
CLASS_DIARY_NAV_CAL = "yui-diary-nav-cal",
CLASS_DIARY_NAV_CALBUTTON = "yui-diary-nav-calbutton",
CLASS_DIARY_NAV_VIEW = "yui-diary-nav-view",
CLASS_DIARY_COLLABEL_CONTAINER = "yui-diary-collabel-container",
CLASS_DIARY_COLLABEL = "yui-diary-collabel",
CLASS_DIARY_ITEM_HIDDEN = "yui-diary-item-hidden",
CLASS_DIARY_SELECTOR = "yui-diary-selector",
CLASS_DIARY_LOADING = "yui-diary-loading",
CLASS_DIARY_LOADING_HIDDEN = "yui-diary-loading-hidden",
CLASS_DIARY_DISPLAY = { MONTH: "yui-diary-view-month",
WEEK: "yui-diary-view-week",
DAY: "yui-diary-view-day",
YEAR: "yui-diary-view-year"},
CLASS_DIARY_ITEM_MONTHVIEW = "yui-diary-item-monthview",
CLASS_DIARY_ITEM_YEARVIEW = "yui-diary-item-yearview",
CLASS_DIARY_MONTHLABEL = "yui-diary-rowlabel-month",
CLASS_DIARY_GOTO_WEEK = "yui-diary-goto-weekview",
CLASS_DIARY_FOOTER = "ft",
// valid fields that a DiaryItem can contain
ITEM_FIELDS = ["UID", "DTSTART", "DTEND", "SUMMARY", "DESCRIPTION",
"URL", "CATEGORIES", "LOCATION", "backClass", "detailClass"],
/**
*
* <p>DiaryItem class for individual items in the Diary.</p>
*
* <p>Data and display for particular diary item.
* Extends resize (and includes drag drop). This class shouldn't be used
* directly; items to be added should be added by the Diary.addItem() method.
* </p>
*
* @class DiaryItem
* @extends YAHOO.util.Resize
* @param elContainer {HTMLElement | String} Container for the DiaryItem
* @param oCfg {Object} Object literal of configuration values. oCfg.DTSTART and oCfg.DTEND are required.
*
*/
DiaryItem = function (el, oCfg) {
// add some extra divs and things:
this.initContent(el);
oCfg.handles = [];
if (oCfg.resizeTop) {
oCfg.handles.push("t");
}
if (oCfg.resizeBottom) {
oCfg.handles.push("b");
}
oCfg.draggable = false;
oCfg.hiddenHandles = true;
oCfg.proxy = false;
oCfg.yTicks = parseInt(oCfg.pxPerHour / 4, 10);
oCfg.status = false;
// add a drag drop separately
if (oCfg.enableDragDrop) {
var dd = new YAHOO.util.DD(el, "default", {isTarget: false});
this.dragdrop = dd;
} else {
this.dragdrop = false;
}
this._multiDayChildren = [];
this._categories = [];
YAHOO.log("constructor for new DiaryItem ", "info");
DiaryItem.superclass.constructor.call(this, el, oCfg);
// default no animation initially
this.anim = false;
if (this.getDiary().get("display").format == "month") {
this.addClass(CLASS_DIARY_ITEM_MONTHVIEW);
} else if (this.getDiary().get("display").format == "year") {
this.addClass(CLASS_DIARY_ITEM_YEARVIEW);
} else if (this.dragdrop) {
this.dragdrop.setYConstraint(
this.calculateTop(),
parseInt((24 * oCfg.pxPerHour ) - (this.calculateTop() / 3600), 10),
oCfg.yTicks
);
}
this.initListeners();
return this;
};
/**
* DiaryItem extends Resize:
*/
Lang.extend(DiaryItem, YAHOO.util.Resize, {
/**
* String that separates hours and minutes
* @property TIME_SEPARATOR
* @type {String}
* @default ":"
*/
TIME_SEPARATOR: ':',
/**
* What vertical line is this one in?
* @property _line
* @type Int
* @private
* @default 1
*/
_line: 1,
/**
* Element holding the details
* @property _detailsEl
* @private
* @default: null
* @type HTMLElement
*/
_detailsEl: null,
/**
* The YAHOO.util.DD instance.
* We don't use the built-in one with Resize because some events don't
* come through properly.
* @property dragdrop
* @type YAHOO.util.DragDrop
* @default null
*/
dragdrop: null,
/**
* For animating movement on drag drop
* @property anim
* @default: false
* @type YAHOO.util.Anim
*/
anim: false,
/**
* Remembers where we start from
* @property _cacheDates
* @private
* @default: Array
* @type Array
*/
_cacheDates: {},
/**
* References to other elements in multi-day events
* @property _multiDayChildren
* @private
* @default: []
* @type Array
*/
_multiDayChildren: [],
/**
* Array of categories, set by CATEGORIES method which parses the string
* given in the config object.
* @property _categories
* @private
* @default: []
* @type Array
*/
_categories: [],
/**
* Implementation of Element's abstract method. Sets up config values.
*
* @method initAttributes
* @param oCfg {Object} Object literal definition of configuration values.
* @private
*/
initAttributes : function(oCfg){
DiaryItem.superclass.initAttributes.call(this, oCfg);
/**
* @attribute UID
* @description The unique id of the item. Write once.
* @default: ''
*/
this.setAttributeConfig('UID', {
value: '',
writeOnce: true
});
/**
* @attribute LOCATION
* @descrition The location of the Item
* @default: ''
* @type String
*/
this.setAttributeConfig('LOCATION', {
value: '',
validator: Lang.isString
});
/**
* @attribute useCssCategories
* @description If true, any CATEGORIES will be added as css classes
* to the container element for the item, allowing styling and filtering.
* Anything passed to backClass will also be added. Spaces in categories
* will be changed to hyphens.
* @type Boolean
* @default false
*/
this.setAttributeConfig('useCssCategories', {
validator: Lang.isBoolean,
value: false
});
/**
* @attribute CATEGORIES
* @description Comma separated string of categories for this Item. If
* useCssCategories is true these will be added as css classes (with
* hyphens replacing spaces.
* @type String
* @default ''
*/
this.setAttributeConfig('CATEGORIES', {
validator: Lang.isString,
method: function(v) {
var spaceToHyphen = function(str) {
return Lang.trim(str).replace(/ /g, "-");
},
cats = '',
i,
openQuote = false,
thisCategory = '';
// if there are quotes in the string, we'll need to parse it carefully
if (v.indexOf( '"' ) !== -1) {
for (i = 0; i < v.length; i++ ) {
if (v[i] == '"') {
openQuote = !openQuote;
// end of a string
if (!openQuote) {
this._categories.push( spaceToHyphen(thisCategory) );
thisCategory = '';
}
} else {
thisCategory += v[i];
}
}
} else if (v.indexOf(',')) {
// multiple items: split them up and tidy them up
cats = v.split(',');
for (i = 0; i < cats.length; i++) {
cats[i] = spaceToHyphen(cats[i]);
}
this._categories = cats;
} else {
// just one category
this._categories = [spaceToHyphen(v)];
}
// add categories as classes if needed:
if (this.get("useCssCategories") && this._categories.length > 0) {
Dom.addClass( this.get("element"), this._categories.join(" ") );
}
}
});
/**
* @attribute SUMMARY
* @description Summary text displayed on diary
* @type String
*/
this.setAttributeConfig('SUMMARY', {
value: '',
validator: Lang.isString
});
/**
* @attribute DESCRIPTION
* @description Full text displayed on diary
* @type String
* @default ""
*/
this.setAttributeConfig('DESCRIPTION', {
value: '',
validator: Lang.isString
});
/**
* @attribute useAnimation
* @description Whether to use animation after drag drops
* @type Boolean
* @default false
*/
this.setAttributeConfig('useAnimation', {
value: false,
validator: Lang.isBoolean
});
/**
* @attribute backClass
* @description Adds css class(es) to the background Item container
* @type String class name to add
* @default ""
*/
this.setAttributeConfig('backClass', {
value: '',
validator: Lang.isString,
method: function(v) {
Dom.addClass(this.get("element"), v);
}
});
/**
* @attribute detailClass
* @description Adds css class(es) to the text div container Item container
* @type String class name to add
* @default ""
*/
this.setAttributeConfig('detailClass', {
value: '',
validator: Lang.isString,
method: function(v) {
Dom.addClass(this._detailsEl, v);
}
});
/**
* @attribute column
* @description Reference to the column (ie day) this item is in
* @type Object DiaryDay
*/
this.setAttributeConfig('column', {
value: null,
validator: Lang.isObject
});
/**
* @attribute block
* @description Reference to the block (ie group of items) this item is in
* @type Object DiaryBlock
*/
this.setAttributeConfig('block', {
value: null,
validator: Lang.isObject
});
/**
* @attribute multiDayPosition
* @description Where this item falls if it's a multi-day event
* @type False | String May be "first", "last", or "mid"
* @private
*/
this.setAttributeConfig('multiDayPosition', {
value: false
});
/**
* @attribute multiDayParent
* @description parent DiaryItem for multi-day events
* @type DiaryItem
* @private
*/
this.setAttributeConfig('multiDayParent', {
value: false
});
/**
* @attribute DTSTART
* @description Start time/ date
* @type Object Date object
*/
this.setAttributeConfig('DTSTART', {
setter: function(v){
this.set("_displayDTSTART", v);
return v;
}
});
/**
* @attribute _displayDTSTART
* @descripiton Start time as displayed - may be different to DTSTART for multi-day items
* @private
* @type Object Date object
*/
this.setAttributeConfig('_displayDTSTART');
/**
* @attribute DTEND
* @description End time/ date
* @type Object Date object
*/
this.setAttributeConfig('DTEND', {
setter: function(v) {
this.set("_displayDTEND" , v );
return v;
}
});
/**
* @attribute _displayDTEND
* @descripiton Start time as displayed - may be different to DTEND for multi-day items
* @private
* @type Object Date object
*/
this.setAttributeConfig('_displayDTEND');
/**
* @attribute URL
* @description URL data for item
* @type String
*/
this.setAttributeConfig('URL', {
validator: Lang.isString
});
/**
* @attribute pxPerHour
* @description Number of pixels per hour
* @default: 20
* @type Number
*/
this.setAttributeConfig( "pxPerHour", {
validator: Lang.isNumber,
value: 20
});
/**
* @attribute visible
* @description Whether this should be displayed or not
* @default: 20
* @type Number
*/
this.setAttributeConfig( "visible", {
validator: Lang.isBoolean,
value:true,
method: function(v) {
if (!v) {
this.addClass(CLASS_DIARY_ITEM_HIDDEN);
}
}
});
},
/**
* @description Initializer, sets up the details Element
* @protected
* @method initContent
* @param el {HTMLElement|String} Element passed to constructor
*/
initContent: function(el) {
var detailsEl = document.createElement("div");
// add some classes
Dom.addClass(detailsEl, CLASS_DIARY_ITEM_DETAILS );
el.appendChild(detailsEl);
this._detailsEl = detailsEl;
},
/**
*
* @description Initialize listeners for this item.
* @protected
* @method initListeners
*/
initListeners: function() {
this.on("startResize",this._handleBeforeStartDrag, "resize", this);
this.on("startResize", this._handleDiaryStartDrag, "resize", this);
this.on("endResize", this._handleDiaryEndDrag, "resize", this);
this.on("resize", this._handleDiaryDragOver, this, true);
this.on("SUMMARYChange", this.renderDetails , this, true);
this.on("DTSTARTChange", this.renderDetails, this, true);
this.on("DTENDChange", this.renderDetails, this, true);
if (this.dragdrop) {
this.dragdrop.on("b4StartDragEvent" , this._handleBeforeStartDrag, "drag", this);
this.dragdrop.on("startDragEvent", this._handleDiaryStartDrag, "drag", this);
this.dragdrop.on("endDragEvent", this._handleDiaryEndDrag, "drag", this);
this.dragdrop.on("dragEnterEvent", this._handleDiaryDragEnter, this, true);
this.dragdrop.on("dragOverEvent" , this._handleDiaryDragOver , this, true);
}
},
/**
* @description Fires an event and stops the drag if subscriber returns false
* @method _handleBeforeStartDrag
* @param ev {HTMLEvent} The before start drag event
* @protected
*/
_handleBeforeStartDrag: function(ev, type) {
var that = this,
stopDrag = function () {
YAHOO.util.DDM.stopDrag();
YAHOO.util.DDM.stopEvent();
Ev.stopEvent(ev);
if (that._cacheDates.diaryDisplay.format == "month") {
that.addClass(CLASS_DIARY_ITEM_MONTHVIEW);
that.setStyle("position", "relative");
} else if (that._cacheDates.diaryDisplay.format == "year") {
that.addClass(CLASS_DIARY_ITEM_YEARVIEW);
that.setStyle("position", "relative");
}
return false;
};
this._cacheDates = {
startTimeDay: this.getStartTimeDay(),
DTSTART: new Date(this.get("DTSTART")),
DTEND: new Date(this.get("DTEND")),
diaryDisplay: this.getDiary().get("display")
};
if ((type == "resize" && this._locked) ||
(type == "drag" && this.dragdrop.locked)) {
return stopDrag();
}
/**
* @event itemBeforeStartMove
* @description Fired before everything starts moving. Return false to cancel move.
* @param oArgs.item DiaryItem that's about to be moved.
* @param oArgs.originEvent Original event from resize/dragdrop passed through.
*/
if (false === this.getDiary().fireEvent("itemBeforeStartMove", {item: this, originEvent: ev})) {
return stopDrag();
}
if (this._cacheDates.diaryDisplay.format == "month" || this._cacheDates.diaryDisplay.format == "year") {
this.removeClass(CLASS_DIARY_ITEM_MONTHVIEW);
this.removeClass(CLASS_DIARY_ITEM_YEARVIEW);
this.setStyle("position", "absolute");
Dom.setStyle(Dom.getAncestorByClassName(this.get("element"),CLASS_DIARYDAY_CONTAINER), "overflow", "visible");
}
},
/**
* Start dragging handler. Caches dates, sets Anim if needed.
* @method _handleDiaryStartDrag
* @protected
*/
_handleDiaryStartDrag: function (ev, type) {
if ((type == "resize" && this._locked) ||
(type == "drag" && this.dragdrop.locked)) {
return false;
}
Dom.setStyle(this.get("element"), "z-index", 2);
if (YAHOO.util.Anim !== undefined &&
this.anim === false &&
this.get("useAnimation") === true) {
this.anim = new YAHOO.util.Anim(this.get("element"), {}, 0.5);
}
},
/**
* While entering a day, update this item's current day
* @method _handleDiaryDragEnter
* @param ev {Event}
* @param String Id passed by dragenter event
* @protected
*/
_handleDiaryDragEnter: function(ev ,id) {
YAHOO.log(" DiaryItem._handleDiaryDragOver ", "info");
var dayTarget = YAHOO.util.DDM.getDDById(ev.info)._diaryDay.get("coldate");
this.setStartTimeDay(dayTarget);
this.setEndTimeDay(dayTarget);
},
/**
* While dragging over a day, update this item's current time and display it
* @method _handleDiaryDragOver
* @param ev Event
* @protected
*/
_handleDiaryDragOver: function(ev) {
var mdp;
if (this._cacheDates.diaryDisplay.format == "month" || this._cacheDates.diaryDisplay.format == "year") {
return;
}
mdp = this.get( "multiDayPosition" );
// change the start times if this is a one-day item,
//or the start of a multi-day item
if ( mdp === false || mdp == "first") {
this.getParentItem().setStartTimeSecs(this.calcStartFromPosition());
}
// change the end times if this is a one-day item, or the end of a multi-day item
if (mdp === false || mdp == "last") {
this.getParentItem().setEndTimeSecs(this.calcEndFromPosition());
}
// redisplay times
this.renderDetails(true);
},
/**
* At the end, update times and days and re-render
* @method _handleDiaryEndDrag
* @param ev Event
* @protected
*/
_handleDiaryEndDrag: function(ev, type, silent) {
var startTimeDay, element, endCol, el,
startCol = this.get("column"),
diary = this.getDiary(),
cache = this._cacheDates,
cacheStart = cache.DTSTART ,
cacheEnd = cache.DTEND,
beforeEvent = true,
multiDayPos,
silent = silent || false;
/**
* @event itemBeforeEndMove
* @description fired when an item is moved or resized (ie the times change).
* Return false to cancel the resize/move
* @param oArgs.from Object literal containing original DTSTART and DTEND
* @param oArgs.to Object literal containing final DTSTART and DTEND
* @param oArgs.item DiaryItem that's being moved
* @param oArgs.originEvent Original event from resize/dragdrop passed through.
*/
if (!silent) {
beforeEvent = diary.fireEvent("itemBeforeEndMove", {
from : { DTSTART: cacheStart ,
DTEND : cacheEnd
},
to: { DTSTART: this.get( "DTSTART" ),
DTEND : this.get( "DTEND" )
},
item: this,
originEvent: ev
});
}
if((type == "resize" && this._locked) ||
(type == "drag" && this.dragdrop.locked) ||
(false === beforeEvent)) {
// reset dates to where they started from:
this.set( "DTSTART" , cacheStart );
this.set( "DTEND", cacheEnd );
// and tidy up:
if (cache.diaryDisplay.format == "month") {
this.addClass(CLASS_DIARY_ITEM_MONTHVIEW);
this.setStyle("position", "relative");
Dom.setStyle(Dom.getAncestorByClassName(this.get("element"),CLASS_DIARYDAY_CONTAINER), "overflow", "auto");
} else if ( cache.diaryDisplay.format == "year") {
this.addClass(CLASS_DIARY_ITEM_YEARVIEW);
this.setStyle("position", "relative");
Dom.setStyle(Dom.getAncestorByClassName(this.get("element"),CLASS_DIARYDAY_CONTAINER), "overflow", "auto");
}
this._cacheDates = {};
Dom.setStyle( element , "z-index" , 1 );
return false;
} else {
if (!this.dragdrop.locked) {
this.dragdrop.lock();
}
if (this._cacheDates.diaryDisplay.format != "month" && this._cacheDates.diaryDisplay.format != "year") {
// change the times
multiDayPos = this.get( "multiDayPosition" );
if (multiDayPos === false || multiDayPos == "first") {
this.getParentItem().setStartTimeSecs( this.calcStartFromPosition() );
}
if (multiDayPos === false || multiDayPos == "last") {
this.getParentItem().setEndTimeSecs( this.calcEndFromPosition() );
}
}
startTimeDay = this.getStartTimeDay();
element = this.get("element");
// remove the node from the current col and stick it in the new one
// and then re-render the start and end columns
if( cache.startTimeDay !== startTimeDay ){
endCol = diary.getDiaryDay( startTimeDay );
el = element.parentNode.removeChild( element );
startCol.removeItemFromBlock( this );
endCol._dataEl.appendChild( el );
endCol._addItemToBlock( this );
this.set("column", endCol );
startCol._rebuildBlocks();
startCol._renderBlocks();
startCol = this.get("column");
}
}
// startCol may be where we ended up (not started)
startCol._rebuildBlocks();
startCol._renderBlocks();
/**
* @event itemEndMove
* @description fired when an item is moved or resized (ie the times change)
* @param oArgs.from Object literal containing original DTSTART and DTEND
* @param oArgs.to Object literal containing final DTSTART and DTEND
* @param oArgs.item DiaryItem that's being moved
* @param oArgs.originEvent Original event from resize/dragdrop passed through.
*/
if (!silent) {
diary.fireEvent( "itemEndMove" , {
from : { DTSTART: cacheStart ,
DTEND : cacheEnd
},
to: { DTSTART: this.get( "DTSTART" ),
DTEND : this.get( "DTEND" )
},
item: this,
originEvent: ev }
);
}
// final tidying up
if (cache.diaryDisplay.format == "month") {
this.addClass(CLASS_DIARY_ITEM_MONTHVIEW);
this.setStyle("position", "relative");
Dom.setStyle(Dom.getAncestorByClassName(this.get("element"),CLASS_DIARYDAY_CONTAINER), "overflow", "auto");
} else if (cache.diaryDisplay.format == "year") {
this.addClass(CLASS_DIARY_ITEM_YEARVIEW);
this.setStyle("position", "relative");
Dom.setStyle(Dom.getAncestorByClassName(this.get("element"),CLASS_DIARYDAY_CONTAINER), "overflow", "auto");
}
this._cacheDates = {};
if (!this.getDiary()._lockDragDrop) {
this.dragdrop.unlock();
}
Dom.setStyle( element , "z-index" , 1 );
},
/**
* @method getDiary
* @description Returns Diary instance we're in.
* @return {YAHOO.widget.Diary}
*/
getDiary: function(){
return this.get("column").get("diary");
},
/**
* @method hasCategory
* @description Checks if Item has category passed.
* @param category {String} Category string to check
* @param caseSensitive {Boolean} Whether to do case-sensitive match
* (default false)
* @return {Boolean}
*/
hasCategory : function (category, caseSensitive) {
var i,
cats = this._categories,
categoryLC = category.toLowerCase(),
matchFn = (caseSensitive === true ?
function (a) { return (a === category);} :
function (a) { return (a.toLowerCase() === categoryLC);});
for (i = 0; i < cats.length; i++) {
if (matchFn(cats[i])) {
return true;
}
}
},
/**
* @method addCategory
* @description Adds a category to the existing ones, if it's not there
* already (using default case insensitive match
* @param category {String} Category string to check
* @param caseSensitive {Boolean} Whether to do case-sensitive match
*/
addCategory : function (category, caseSensitive) {
if (!this.hasCategory(category, caseSensitive)) {
this.set("CATEGORIES", this.get("CATEGORIES") + "," + category);
}
},
/**
* @method setStartTimeDay
* @description Sets the start day/month/year of this item (but not times)
* @param {Object} Date to extract date/month/year from
*/
setStartTimeDay: function ( date ) {
var start = this.get("DTSTART");
start.setYear( date.getFullYear() );
start.setMonth( date.getMonth() );
start.setDate( date.getDate() );
},
/**
* @method setEndTimeDay
* @description Sets the end day/month/year of this item
* @param {Object} Date to extract date/month/year from
*/
setEndTimeDay: function ( date ) {
var end = this.get("DTEND");
end.setYear( date.getFullYear() );
end.setMonth( date.getMonth() );
end.setDate( date.getDate() );
},
/**
* @method getStartTimeDay
* @description Gets the start time ( milliseconds) of the day of this item (ie at 00:00)
* @return {Int}
*/
getStartTimeDay: function () {
return ( this.get("DTSTART").getTime() - (this.getStartTimeSecs()*1000) );
},
/**
* @method getStartTimeSecs
* @description Gets the start time as seconds from 00:00
* @return {Int}
*/
getStartTimeSecs: function(){
var s = this.get("DTSTART");
return (s.getHours() * 3600) + (s.getMinutes() * 60) + s.getSeconds();
},
/**
* @method getEndTimeSecs
* @description Gets the end time as seconds from 00:00
* @return {Int}
*/
getEndTimeSecs: function(){
var e = this.get("DTEND");
return (e.getHours() * 3600) + (e.getMinutes() * 60) + e.getSeconds();
},
/**
* @method getDisplayStartTimeSecs
* @description Gets the displayed start time as seconds from 00:00
* @protected
* @return {Int}
*/
getDisplayStartTimeSecs: function(){
var s = this.get( "_displayDTSTART" );
return (s.getHours() * 3600) + (s.getMinutes() * 60) + s.getSeconds();
},
/**
* @method getDisplayEndTimeSecs
* @description Gets the displayed end time as seconds from 00:00
* @protected
* @return {Int}
*/
getDisplayEndTimeSecs: function(){
var e = this.get( "_displayDTEND" );
return (e.getHours() * 3600 ) + (e.getMinutes() * 60) + e.getSeconds();
},
/**
* @method setStartTimeSecs
* @description Sets the start time (but not date) from seconds
* @param Int
*/
setStartTimeSecs: function( secs ) {
var h = Math.floor( secs / 3600 ),
m = Math.floor( (secs - ( h * 3600 ) ) / 60 );
this.get("DTSTART").setHours( h , (m - m%15) );
},
/**
* @method setEndTimeSecs
* @description Sets the end time (but not date) from seconds
* @param Int
*/
setEndTimeSecs: function( secs ) {
var h = Math.floor( secs / 3600 ),
m = Math.floor( (secs - ( h * 3600 ) ) / 60 );
if( m%15 < 8 ){
m = m - m%15;
} else {
m = m + ( 15 - m%15 );
}
this.get("DTEND").setHours( h , m );
},
/**
* @method calculateHeight
* @description Calculate the height (in pixels) of this based on times
* @return {Int}
*/
calculateHeight: function(){
return ( ( this.getDisplayEndTimeSecs() - this.getDisplayStartTimeSecs() ) * ( this.get("pxPerHour") /3600) - 2);
},
/**
* @method calculateTop
* @description Calculate the top (in pixels) of this based on times
* @return {Int}
*/
calculateTop: function(){
return ( ( this.getDisplayStartTimeSecs() ) * ( this.get("pxPerHour") / 3600) );
},
/**
* @method calcStartFromPosition
* @description Calculate the start time (seconds) based on position
* @return {Int}
*/
calcStartFromPosition: function( t, pxPerHour) {
if( t === undefined ) {
t = parseInt( this.getStyle("top") , 10 );
}
if(pxPerHour === undefined) {
pxPerHour = this.get("pxPerHour");
}
return parseInt( ( ( t * 3600 ) / pxPerHour), 10 );
},
/**
* @method calcEndFromPosition
* @description Calculate the end time (seconds) based on position
* @return {Int}
*/
calcEndFromPosition: function( h , t, pxPerHour) {
if( h === undefined ){
h = parseInt(this.getStyle("height"),10);
}
if( t === undefined ) {
t = parseInt( this.getStyle("top") , 10 );
}
if(pxPerHour === undefined) {
pxPerHour = this.get("pxPerHour");
}
return parseInt( ( ( ( h + t ) * 3600 ) / pxPerHour), 10 );
},
/**
* @method renderStartTime
* @description Gets start time as string hh:mm format
* @return {String}
*/
renderStartTime: function(){
var s = this.get( "DTSTART" ),
h = (s ? s.getHours() : 0 ),
m = (s ? s.getMinutes() : 0 );
return ( h < 10 ? '0' : '' ) + h + this.TIME_SEPARATOR + ( m < 10 ? '0' : '' ) + m ;
},
/**
* @method renderEndTime
* @description Gets end time as string
* @return {String}
*/
renderEndTime: function(){
var s = this.get( "DTEND" ),
h = (s ? s.getHours() : 0 ),
m = (s ? s.getMinutes() : 0 );
return ( h < 10 ? '0' : '' ) + h + this.TIME_SEPARATOR + ( m < 10 ? '0' : '' ) + m ;
},
/**
* @method render
* @description Render the item
* @param Object Containing some dimensions { width: Int, linesInBlock: Int}
*/
render: function( oDetails ) {
YAHOO.log( "DiaryItem.render()", "info");
// set the offset and width
var lineWidth,
w,
l,
t,
h;
if (!oDetails) {
oDetails = {
linesInBlock: this.get("block").getVisibleLines(),
width: this.get("column").get("width") || 150
};
}
if (this.getDiary().get("display").format == "month") {
w = oDetails.width - 20;
l = 10;
t = 0;
h = 20;
this.setStyle("position", "relative");
} else if (this.getDiary().get("display").format == "year") {
w = Math.max(oDetails.width - 20, 10);
l = 2;
t = false;
h = 5;
} else {
lineWidth = parseInt(((oDetails.width - 20 )/ Math.max(1,oDetails.linesInBlock)), 10);
w = (lineWidth - 4 );
l = parseInt((( this.get("block").getOffset(this._line)) * lineWidth ) + 20 , 10 );
t = parseInt( this.calculateTop() , 10 );
h = parseInt( this.calculateHeight() , 10 );
}
this.renderDetails( false );
if ( t>= 0) {
this.setStyle( "top" , t + "px");
}
this.setStyle( "left" , l + "px" );
if( this.anim === false ) {
this.setStyle( "width" , w + "px" );
this.setStyle( "height" , h + "px");
Dom.setStyle( this._detailsEl, "height", h + "px");
} else {
this.anim.attributes = { height: { to: h }, width: { to : w } };
this.anim.animate();
}
this.addClass( CLASS_DIARY_ITEM );
},
/**
* @method renderDetails
* @description Writes the text to the this._detailsEl element
* @param {Boolean} Whether to use the item's parent (if a multi-day item)
*/
renderDetails: function( useParent ){
var i, item = ( useParent ? this.getParentItem() : this ), shortText;
if( item === this ){
for( i =0; i < this._multiDayChildren.length; i++ ){
this._multiDayChildren[ i ].renderDetails( );
}
} else {
item.renderDetails();
}
// put the actual details
shortText = item.renderStartTime() + ' - ' + item.renderEndTime() + ': ' + item.get("SUMMARY") ;
this._detailsEl.innerHTML = shortText;
this._detailsEl.title = shortText;
},
/**
* @method renderFullDetails
* @description Returns a string showing full details of the item.
* @return {String}
*/
renderFullDetails: function(){
var item = this.getParentItem(),
// put the actual details
shortText = item.renderStartTime() + ' - ' + item.renderEndTime() + ': ' + item.get("SUMMARY");
shortText += "<br/>" + item.get("DESCRIPTION");
return shortText;
},
/**
* @method setLine
* @description Which 'line' (ie what offset) is this one in?
* @param Int
*/
setLine: function( line ) {
this._line = line;
},
/**
* @method addMultiDayChild
* @description Adds a linked child diary item to the stack:
* @param oDiaryItem {DiaryItem}
*/
addMultiDayChild: function( oDiaryItem ){
this._multiDayChildren.push( oDiaryItem );
},
/**
* @method getParentItem
* @description Provides access to the parent item. For single-day events, it's this,
* for multi-day events it's the parent, which will give proper dates/times etc
* and ids for editing and so on.
* @return {DiaryItem}
*/
getParentItem: function(){
if( this.get("multiDayParent") ){
return this.get("multiDayParent");
}
return this;
},
/**
* @method destroy
* @description Destroys the DiaryItem
*/
destroy : function() {
var i = 0,
numChildren = this._multiDayChildren.length,
el = this.get("element"),
parent = el.parentNode;
if (this.dragdrop) {
this.dragdrop.unreg();
delete this.dragdrop;
}
if (Lang.isObject(this._dds)) {
if (Lang.hasOwnProperty(this._dds, "b")) {
this._dds.b.unreg();
}
if (Lang.hasOwnProperty(this._dds, "t")) {
this._dds.t.unreg();
}
}
this._line = null;
this._detailsEl = null;
if (this.anim !== false) {
this.anim.destroy();
this.anim = false;
}
this._cacheDates = null;
if (numChildren> 0) {
for (i = 0; i < numChildren; i++) {
if (Lang.hasOwnProperty(this._multiDayChildren[i], "destroy")) {
this._multiDayChildren[i].destroy();
}
}
}
this._multiDayChildren = null;
DiaryItem.superclass.destroy.call(this);
if (parent) {
parent.removeChild(el);
}
/**
* @event destroy
* @description Fired after destruction process
*/
this.fireEvent("destroy");
}
});
/**
*
* A block of diary items.
* Holds diary items that overlap in time within a single day. You won't want
* to deal with these directly.
*
* @class DiaryBlock
*
*/
var DiaryBlock = function( ){
YAHOO.log( "new DiaryBlock called" , "info" );
this.init();
};
DiaryBlock.prototype = {
/**
* Array of DiaryItem in this block.
* @property _items
* @type Array
* @default []
* @protected
*/
_items:[],
/**
* Time of day, in seconds of the start of this block.
* @property _startSecs
* @type Int
* @default 86401
* @protected
*/
_startSecs: 86401,
/**
* Time, in seconds, of the end of this block.
* @property _endSecs
* @type Int
* @default -1
* @protected
*/
_endSecs: -1,
/**
* The number of vertical lines in the block.
* Array holding an objects for each line, of the form
* { startSecs: 123, endSecs: 456 , visible: true }
* @property _lines
* @type Array
* @default []
* @protected
*/
_lines: [],
/**
* @method init
* @description Initializer function.
* @protected
*/
init: function(){
this._items = [];
this._startSecs = 86401;
this._endSecs = -1;
this._lines = [];
},
/**
* Adds a DiaryItem to the block.
* @method addItem
* @param item {DiaryItem}
*/
addItem: function(item) {
// which vertical line will this sit in?
var line = this._findLineForItem(item);
// add a new one if needed
if( line === false ) {
this._lines.push({startSecs: item.getDisplayStartTimeSecs(),
endSecs : item.getDisplayEndTimeSecs(),
visible: item.get("visible")});
line = this._lines.length - 1;
} else {
this._lines[line].startSecs = Math.min(this._lines[line].startSecs,
item.getDisplayStartTimeSecs());
this._lines[line].endSecs = Math.max(this._lines[line].endSecs,
item.getDisplayEndTimeSecs());
this._lines[line].visible = item.get("visible") || this._lines[line].visible;
}
item.setLine( line );
// store the item
this._items.push( item );
item.set("block", this);
this._items.sort( function( a,b ){ return ( a.startSecs < b.startSecs ); } );
// update min/max times
this._startSecs = Math.min( this._startSecs, item.getDisplayStartTimeSecs() );
this._endSecs = Math.max( this._endSecs, item.getDisplayEndTimeSecs() );
},
/**
* @method removeItem
* @description Removes an item from the block
* @param {DiaryItem} DiaryItem to remove
* @return {Boolean} True if removed, false if not found
*/
removeItem: function( item ) {
var i, allItems = this._items;
for( i = 0; i < allItems.length ; i++ ){
if( allItems[ i ].get("id") == item.get("id") ) {
allItems.splice( i, 1 );
item.set("block", null);
return true;
}
}
return false;
},
/**
* @method _findLineForItem
* @description Will the item fit in an existing vertical line?
* @return {Int} || {False} - line number
* @protected
* @param {DiaryItem}
*/
_findLineForItem: function( item ) {
var i,
lines = this._lines;
// will it fit in this line?
for( i = 0; i < lines.length; i++ ) {
if( item.getDisplayEndTimeSecs() <= lines[ i ].startSecs || item.getDisplayStartTimeSecs() >= lines[ i ].endSecs ){
return i;
}
}
return false;
},
/**
* @method contains
* @description Does item overlap with this block?
* @param item {DiaryItem}
* @return {Boolean}
*/
contains: function(item) {
// starts before the end of the block, or ends after the start of the block:
return item.getDisplayStartTimeSecs() < this._endSecs && item.getDisplayEndTimeSecs() > this._startSecs;
},
/**
* @method render
* @description Renders the DiaryItems in the block
* @param Object Literal containing width property of entire column
* @return {Boolean}
*/
render: function( oCfg ){
var i,
numVisibleLines = this.getVisibleLines();
for( i = 0; i < this._items.length; i++ ) {
this._items[ i ].render( {linesInBlock: numVisibleLines,
width: oCfg.width || 150 });
}
},
getVisibleLines : function() {
var i, numVisibleLines = 0;
for (i = 0; i < this._lines.length; i++) {
if (this._lines[i].visible) {
numVisibleLines ++;
}
}
return numVisibleLines;
},
/**
* @method getOffset
* @description Gives the number of visible lines across the line passed is
*/
getOffset : function(line) {
var i, visibleLine = 0;
for (i = 0; i < this._lines.length; i++) {
if (this._lines[i].visible) {
if (i == line) {
return visibleLine;
}
visibleLine ++;
}
}
return 0;
},
/**
* @method toString
* @return {String}
*/
toString: function(){
return "DiaryBlock";
},
/**
* @method destroy
*/
destroy: function() {
this.init();
}
};
/**
*
* A days worth of diary items. Contains none or more DiaryBlocks.
* @class DiaryDay
* @param {HTMLElement || String} Html element to render to
* @param Object Configuration object literal
* @extends YAHOO.util.Element
*
*/
var DiaryDay = function( el, oCfg ){
DiaryDay.superclass.constructor.call( this, el, oCfg);
this._blocks = [];
// Add some elements
var dataEl = document.createElement( "div" );
Dom.addClass( dataEl , CLASS_DIARY_DATACONTAINER );
this.get("element").appendChild( dataEl );
this._dataEl = dataEl;
var backgroundEl = document.createElement( "div" );
backgroundEl.id = "bdy-" + el.id.substring( 4 );
this.get("element").appendChild( backgroundEl );
this._backgroundEl = backgroundEl;
if (this.get("coldate").getTime() === new Date().setHours(0,0,0,0)) {
this.addClass(CLASS_DIARY_TODAY);
}
// Make the background a DDTarget
var ddt = new YAHOO.util.DDTarget( backgroundEl , "default" );
// Store a ref to this day so we know on drag drop where we are easily
ddt._diaryDay = this;
this.ddTarget = ddt;
// render the columns
this._renderColumn();
};
DiaryDay.prototype.toString = function(){
return "DiaryDay for " + this.get("coldate").toString();
};
Lang.extend( DiaryDay, YAHOO.util.Element, {
/**
* @method initAttributes
* @param oCfg {Object} Object literal of config values
* @private
*/
initAttributes: function( oCfg ){
DiaryDay.superclass.initAttributes.call( this, oCfg );
/**
* @attribute diary
* @type YAHOO.util.Diary
* @description reference to the Diary object holding this DiayDay
*
*/
this.setAttributeConfig( 'diary' );
/**
* @attribute coldate
* @type {Object}
* @description Date object for this column
*
*/
this.setAttributeConfig( 'coldate' );
/**
* @attribute width
* @type {Int}
* @default 150
* @description Width of the column (as an integer) in pixels.
*/
this.setAttributeConfig( "width" , {
method: function(v){
Dom.setStyle( this.get("element"), "width" , v + "px" );
},
value : 150
}
);
},
/**
*
* @property _blocks
* @description Holds blocks of items
* @type Array
* @protected
*/
_blocks: [],
/**
*
* @property _dataEl
* @description HTMLElement holding the HTMLElements for the DiaryItems
* @type HTMLElement
* @protected
*/
_dataEl: null,
/**
* @method addItem
* @description Adds the item to the day
* @param {Object} Object literal of data for the new DiaryItem
* @return {Object} DiaryItem
*/
addItem: function( oData ) {
YAHOO.log( "DiaryDay.addItem ","info" );
var item, itemEl;
// create an element for the new item
itemEl = document.createElement( "div" );
this._dataEl.appendChild( itemEl );
// set the column of the new item
oData.column = this;
item = new DiaryItem( itemEl, oData );
// Work out how this new item fits among the other existing ones
this._addItemToBlock( item );
return item;
},
/**
* @method _addItemToBlock
* @description Works out which of existing blocks to add this item to, or
* create a new block if needed.
* @param {Object} DiaryItem
* @protected
*/
_addItemToBlock: function (item) {
var i;
// see if item fits in existing blocks:
for( i = 0; i < this._blocks.length ; i++ ) {
if( this._blocks[ i ].contains( item ) ){
this._blocks[ i ].addItem( item );
return;
}
}
// if not, create a new one
var newBlock = new DiaryBlock();
newBlock.addItem( item );
this._blocks.push( newBlock );
},
/**
* @method removeItemFromBlock
* @description Removes an item from its block
* @param item {DiaryItem}
* @return {Boolean} Whether it was successfully removed
*/
removeItemFromBlock: function( item ){
var i, blocks = this._blocks;
for( i = 0 ; i < blocks.length; i ++ ){
if( blocks[i].removeItem( item ) ){
return true;
}
}
return false;
},
/**
* @method _rebuildBlocks
* @description Gets current items and rebuilds blocks in case some have changed.
* @protected
*/
_rebuildBlocks: function(){
YAHOO.log("rebuilding", "info");
var i, j, allItems = [];
// Get the existing items into a temp array and get rid of the blocks
for( i = 0; i < this._blocks.length; i++ ){
for( j = 0; j < this._blocks[ i ]._items.length; j++ ) {
allItems.push( this._blocks[ i ]._items[ j ] );
}
this._blocks[ i ].destroy();
}
this._blocks = [];
// Sort the items
allItems.sort(this.get("diary")._itemSorter);
// Re-add the items
for( i = 0; i < allItems.length; i++ ) {
this._addItemToBlock( allItems[ i ] );
}
},
/**
* @method render
* @description Renders the day view.
*/
render: function() {
// render the columns
//this._renderColumn();
// render each block
this._renderBlocks();
},
/**
* @method _renderColumn
* @description Draws a column for the day
* @protected
*/
_renderColumn: function(){
YAHOO.log( "renderColumn" , "info" );
var h, // hour counter
parent = this.get("element"),
newEl,
coldate = this.get("coldate"),
dateFormat = "%e",
backgroundEl = this._backgroundEl,
containerEl = document.createElement( "div" ),
baseEl = document.createElement( "div" );
// container:
Dom.addClass(containerEl, CLASS_DIARYDAY_CONTAINER);
Dom.setStyle(containerEl, "line-height", null);
// container for background:
Dom.addClass(backgroundEl, CLASS_DIARY_BACKGROUND);
if (this.get("diary").get("display").format == "month") {
if (coldate.getDay() == 1 || coldate.getDate() == 1) {
dateFormat += " %b";
if (coldate.getMonth() === 0) {
dateFormat += ", %Y";
}
}
baseEl.innerHTML = YAHOO.util.Date.format(coldate,{format: dateFormat}, this.get("diary").get("locale"));
backgroundEl.appendChild(baseEl);
} else if (this.get("diary").get("display").format == "year") {
baseEl.innerHTML = YAHOO.util.Date.format(coldate,{format: dateFormat}, this.get("diary").get("locale"));
backgroundEl.appendChild(baseEl);
Dom.setStyle(containerEl, "line-height", "20px");
} else {
Dom.addClass( baseEl , CLASS_DIARY_HOURBLOCK);
Dom.setStyle( baseEl, "height" , (this.get("diary").get("pxPerHour") - 1) + "px");
// add times
for( h = 0; h < 24; h++ ){
newEl = baseEl.cloneNode( false );
newEl.innerHTML =( h <= 12 ? h + "am" : (h-12) + "pm" );
Dom.addClass( newEl, "h" + h );
backgroundEl.appendChild( newEl );
}
}
containerEl.appendChild(backgroundEl);
containerEl.appendChild(this._dataEl.parentNode.removeChild(this._dataEl));
parent.appendChild(containerEl);
},
/**
* @method _renderBlocks
* @description Loops through the items in the block and renders them
* @protected
*/
_renderBlocks: function(){
for( var i = 0; i < this._blocks.length; i++ ) {
this._blocks[i].render( { width: this.get("width") });
}
},
/**
* Navigate among columns
*
*/
/**
* @method next
* @description Get the next column, or null if it's the last.
* @return DiaryDay || null
*/
next: function(){
return this.get("diary").getDiaryDay( this.get("coldate").setHours(0,0,0,0) + 86400000 );
},
/**
* @method previous
* @description Gets the previous column, or null if it's the first
* @return DiaryDay || null
*/
previous: function(){
return this.get("diary").getDiaryDay( this.get("coldate").setHours(0,0,0,0) - 86400000 );
},
/**
* @method destroy
* @description Destructor
*/
destroy: function(){
var i = 0, j = 0, blocks = this._blocks;
for( i = 0; i < blocks.length; i++ ){
for( j = 0; j < blocks[i]._items.length; j++ ){
blocks[i]._items[j].destroy();
}
blocks[i].destroy();
}
this.ddt.unreg();
delete this.ddt;
this.ddt = {};
}
});
/**
*
* The main diary;
*
* <p>This is the main object. It:
* <ol style="margin-left:30px;">
* <li>gets the data</li>
* <li>does the main background display</li>
* <li>sets up the DiaryDay objects and calls their render methods</li>
* <li>holds delegated event listeners for the DiaryItems</li>
* </ol>
*
* @namespace YAHOO.widget
* @class Diary
* @extends YAHOO.util.Element
* @constructor
* @param el {HTMLElement} Container element for the Diary
* @param oDS {YAHOO.util.DataSource} DataSource instance. Each 'row' in the
* data will need a start date/time and end date/time, both as js Date objects.
* Use the fieldMap config attribute to map data fields to those expected
* by Diary - DTSTART and DTEND.
* @param oCfg {Object} Object literal of config values.
*
*/
var Diary = function( el, oDS, oCfg ){
Diary.superclass.constructor.call( this, el, oCfg);
this.setupDays();
this._renderCoreDiary();
this.initListeners();
this.initData(el, oCfg, oDS );
};
Lang.extend( Diary, YAHOO.util.Element, {
/**
* @property _colToDayMap
* @type Object
* @description provieds a map from columns id's to dates (in seconds)
* @protected
*/
_colToDayMap:{},
/**
* @property _selector
* @type Object
* @description Holds details of a click-and-drag to add new Items
* @protected
*/
_selector:{},
/**
* @property _colWidth
* @type Int
* @description Column widths
* @protected
*/
_colWidth: 200,
/**
* @property _ds
* @type YAHOO.util.DataSource
* @description Reference to the datasource passed in
* @protected
*/
_ds: null,
/**
* @property _navCalendar
* @type YAHOO.widget.Calendar
* @description Navigation calendar
* @protected
*/
_navCalendar: null,
/**
* @property _tooltip
* @type YAHOO.widget.Tooltip
* @description Re-usable tooltip, if included on page
* @protected
*/
_tooltip: false,
/**
* @property _diaryData
* @type Array
* @description Array holding currently displayed data
* @protected
*/
_diaryData: [],
/**
* @property _lastData
* @type Object
* @description Object with cache of last data, pre-parsed, from datasource
* @protected
*/
_lastData: {},
/**
* @property _itemHash
* @type Array
* @description Array holding DiaryItem element ids and DiaryItem refs
* @protected
*/
_itemHash: [],
/**
* @property _lockResize
* @type Boolean
* @description Whether resizing of DiaryItems is allowed
* @protected
*/
_lockResize: false,
/**
* @property _lockDragDrop
* @type Boolean
* @description Whether drag-dropping of DiaryItems is allowed
* @protected
*/
_lockDragDrop: false,
/**
* @property _filters
* @type Object
* @description Object of filters currently applied
* @protected
*/
_filters: {},
/**
* @property _loadingElId
* @type String
* @description Id of 'loading' element
* @protected
* @default ''
*/
_loadingElId: '',
/**
* @property _footerEl
* @type HTMLElement
* @description Footer element
* @protected
* @default undefined
*/
_footerEl: '',
/**
* @property _cacheStartEndTimes
* @type Object
* @description Caches start/end times for view changes
* @protected
* @default {startTime: 8; endTime: 20}
*/
_cacheStartEndTimes: {startTime: 8, endTime: 20},
/**
* @method initAttributes
* @param Object
* @description Object of filters currently applied
* @private
*/
initAttributes: function( oCfg ){
Diary.superclass.initAttributes.call( this, oCfg );
/**
* @attribute keepFirstDay
* @description Whether the first column should be restricted to
* a particular day of the week. If so, pass the day of the week
* (with 0 = Sunday).
* @default false
* @type {Boolean | Int}
*/
this.setAttributeConfig('keepFirstDay', {
value: false,
validator: function (v) {
return (Lang.isBoolean(v) || (Lang.isNumber(v) && v >= 0 && v <= 6));
}
});
/**
* @attribute locale
* @description Which locale to use when parsing dates. See
* <a href="http://developer.yahoo.com/yui/docs/YAHOO.util.Date.html">
* YAHOO.util.Date</a> and YAHOO.util.DateLocale. For more info.
* @type {String}
* @default "en-GB"
*/
this.setAttributeConfig('locale', {
value: "en-GB"
});
/**
* @attribute endDate
* @description Final date currently displayed on Diary
* @type {Date} (Optional)
* @default 7 days on from startDate
*/
this.setAttributeConfig('endDate', {
method: function(v){
if (v) {
v.setHours(0,0,0,0);
}
}
});
/**
* @attribute startDate
* @description When to start the diary display from
* @type {Date}
*/
this.setAttributeConfig('startDate', {
setter: function(v){
if (this.get("keepFirstDay") !== false &&
this.get("display") !== undefined &&
this.get("display").format !== "day") {
v = DM.getFirstDayOfWeek(v, this.get("keepFirstDay"));
}
v.setHours(0,0,0,0);
if (!this.get("endDate")) {
this.set("endDate", DM.add(v, DM.DAY, 7));
}
return v;
}
});
/**
* @attribute pxPerHour
* @description Number of pixels per hour. Write once.
* @default: 20
* @type Number
*/
this.setAttributeConfig( "pxPerHour", {
validator: Lang.isNumber,
value: 20,
writeOnce: true
});
/**
* @attribute display
* @description Display formats: object literal with format
* and start and end times (in 24-hour clock hours) displayed
* in the main window (the rest are above and below the scroll.
* <pre>{ format: "week", startTime: 8, endTime: 20 }</pre>.
* Possible formats are "day", "week", "month", and working on "year".
* @type Object
* @default <pre>{ format: "week", startTime: 8, endTime: 20 }</pre>
*/
this.setAttributeConfig( 'display' , {
value: { format: "week" , startTime: 8, endTime: 20 },
method: function( v ) {
var pxPerHour = this.get("pxPerHour");
if (v.startTime >= 0) {
this._cacheStartEndTimes.startTime = v.startTime;
} else {
v.startTime = this._cacheStartEndTimes.startTime;
}
if (v.endTime >= 0) {
this._cacheStartEndTimes.endTime = v.endTime;
} else {
v.endTime = this._cacheStartEndTimes.endTime;
}
// add some methods to the object:
switch (v.format) {
case "month":
v.getSeconds = 3024000000;
v.getDaysAcross = 7;
v.getDaysInView = 35;
v.getOnClickFormat = false;
v.getDiaryHeight = 100;
v.getDiaryScrollTop = 0;
v.renderDateLabel = function (oDate) {return oDate.toString().substring(0, 3);};
v.getNextFormat = "week";
break;
case "day":
v.getSeconds = 86400000;
v.getDaysAcross = 1;
v.getDaysInView = 1;
v.getOnClickFormat = {format: "week", startTime: v.startTime, endTime: v.endTime};
v.getDiaryHeight = parseInt((v.endTime - v.startTime ) * pxPerHour, 10);
v.getDiaryScrollTop = parseInt(v.startTime * pxPerHour, 10);
v.getNextFormat = "week";
break;
case "week":
v.getSeconds = 604800000;
v.getDaysAcross = 7;
v.getDaysInView = 7;
v.getOnClickFormat = {format:"day", startTime: v.startTime, endTime: v.endTime};
v.getDiaryHeight = parseInt((v.endTime - v.startTime ) * pxPerHour, 10);
v.getDiaryScrollTop = parseInt(v.startTime * pxPerHour, 10);
v.getNextFormat = "month";
break;
case "year":
v.getSeconds = 31536000000;
v.getDaysAcross =35;
v.getDaysInView = 460;
v.getOnClickFormat = false;
v.getDiaryHeight = 20;
v.getDiaryScrollTop = 0;
v.renderDateLabel = function (oDate) {return oDate.toString().substring(0, 1);};
v.getNextFormat = "month";
// move to start of current month
this.get("startDate").setDate(1);
this.set("startDate", this.get("startDate"));
break;
}
// add a class
this.addClass(CLASS_DIARY_DISPLAY[ v.format.toUpperCase() ]);
}
}
);
/**
* @attribute width
* @description Overall width of Diary (in pixels)
* @type Number (Optional) Will use element styled width if no value provided and it has one
*/
this.setAttributeConfig( "width" , {
method: function( v ){
if( !( Lang.isNumber( v ) ) ){
v = parseInt( Dom.getStyle( this.get("element"), "width" ) , 10 );
}
if( v > 0 ){
Dom.setStyle( this.get("element"), "width" , v + "px" );
}
}
}
);
/**
* @attribute scaleColumns
* @description Whether to scale columns to width. writeOnce
* @type Boolean
* @default true
*/
this.setAttributeConfig( 'scaleColumns' , {
validator: Lang.isBoolean,
value: true,
method: function(v){
if( v && this.get( "width" ) ){
// 7 days / week . 7 pixels to allow for scrollbars etc.
this._colWidth = parseInt(this.get( "width" ) / this.get("display").getDaysAcross, 10) - 4;
if (this.get("display").format == "month") {
this._colWidth -= 2;
}
} else {
this._colWidth = 200;
}
},
writeOnce: false
}
);
/**
* @attribute calenderNav
* @description Whether to use a YAHOO.widget.Calendar in the navigation.
* Write once.
* @type Boolean
* @default true
*/
this.setAttributeConfig( 'calenderNav', {
validator: Lang.isBoolean,
value: true,
writeOnce: true
});
/**
* @attribute fieldMap
* @description
* <p>Field map, mapping keys of DataSource to expected
* keys of data for DiaryItems. DiaryItem keys are the keys in the
* object passed; values are the names of the fields in the DataSource.
* backClass is the css class string applied to the background container of the
* DiaryItem; detailClass is the css class string applied to the element
* holding the text of the item. These can be used by addItemFilter
* to show or hide items by category.</p>
* <p>DTSTART and DTEND need to be strings; but other values may be
* functions. These functions are called on the Diary instance (i.e.
* this in your function is the Diary, and receive the raw data literal
* as their only argument.</p>
*
* Write once
*
* @type {Object}
* @default <pre>
{ UID: "UID",
DTSTART: "DTSTART",
DTEND: "DTEND",
SUMMARY: "SUMMARY",
DESCRIPTION: "DESCRIPTION",
CATEGORIES: "CATEGORIES",
LOCATION: "LOCATION",
URL: "URL",
backClass: "backClass",
detailClass: "detailClass" }</pre>
*/
this.setAttributeConfig( 'fieldMap' , {
value: { UID: "UID",
DTSTART: "DTSTART",
DTEND: "DTEND",
SUMMARY: "SUMMARY",
DESCRIPTION: "DESCRIPTION",
URL: "URL",
CATEGORIES: "CATEGORIES",
LOCATION: "LOCATION",
backClass: "backClass",
detailClass: "detailClass" },
setter: function( oMap ){
return Lang.merge( {
UID: "UID",
DTSTART: "DTSTART",
DTEND: "DTEND",
SUMMARY: "SUMMARY",
DESCRIPTION: "DESCRIPTION",
URL: "URL",
CATEGORIES: "CATEGORIES",
LOCATION: "LOCATION",
backClass: "backClass",
detailClass: "detailClass"
}, oMap
);
},
writeOnce: true
} );
/**
* @attribute titleString
* @description String to use as template for title. You can use
* strftime type identifiers. Write once.
* @type String
* @default "Diary w/c %A, %e %B %Y"
*/
this.setAttributeConfig( "titleString" , {
validator: Lang.isString,
value: "Diary w/c %A, %e %B %Y",
writeOnce: true
});
/**
* @attribute itemClickCreateNew
* @description Whether you can click and drag on an existing item
* to start creating a new one.
* @type Boolean
* @default false
*/
this.setAttributeConfig( "itemClickCreateNew", {
validator: Lang.isBoolean,
value: false
});
/**
* @attribute titleString
* @description String to put in footer element
* @type Object Keys: text, hideDelay (optional)
* @default {text: "", hideDelay: 5000}
*/
this.setAttributeConfig( "footerString");
/**
* @attribute allowCreateMultiDayItems
* @description Whether, when creating new items, they're allowed to span multi days
* @default: false
* @type Boolean
*/
this.setAttributeConfig( "allowCreateMultiDayItems", {
validator: Lang.isBoolean,
value: false
});
/**
* @attribute tooltip
* @description Whether to use tooltip for mouseover events to show details.
* Write once.
* @default: false
* @type Boolean
*/
this.setAttributeConfig( "tooltip", {
validator: Lang.isBoolean,
value: false,
writeOnce: true
});
/**
* @attribute animate
* @description Whether to use animation when moving items around.
* Write once.
* @default: false
* @type Boolean
*/
this.setAttributeConfig( "useAnimation", {
validator: Lang.isBoolean,
value: false,
writeOnce: true
});
/**
* @attribute useCssCategories
* @description If true, any CATEGORIES will be added as css classes
* to the container element for the DiaryItem, allowing styling and filtering.
* Anything passed to backClass will also be added. Spaces in categories
* will be changed to hyphens.
* @type Boolean
* @default false
*/
this.setAttributeConfig('useCssCategories', {
validator: Lang.isBoolean,
value: false
});
},
/**
* @method initListeners
* @description Set up delegated event listeners for rendering and mouse events
* @protected
*/
initListeners: function(){
this.on( "parseData" , this.renderItems, this );
// clicks just on days
Ev.delegate(this.get("element"), "mousedown", this.handleDayClick, "div." + CLASS_DIARY_DAY, this, true);
// click and drag new items
Ev.delegate(this.get("element"), "mousedown", this._startNewItem, "div." + CLASS_DIARY, this, true);
this.on("mouseup", this._endSelector, this, true);
// click on existing diary items
Ev.delegate(this.get("element"), "click", this.handleItemClick, "div." + CLASS_DIARY_ITEM, this, true);
// mouseover
Ev.delegate(this.get("element"), "mouseenter", this.handleItemMouseEnter, "div." + CLASS_DIARY_ITEM, this, true);
// listener for header clicks
Ev.delegate(this.get("element"), "click", this._handleColumnHeaderClick, "span." + CLASS_DIARY_COLLABEL, this, true);
// listend for month -> week view change clicks:
Ev.delegate(this.get("element"), "click", this._handleMonthToWeekClick, "div." + CLASS_DIARY_GOTO_WEEK, this, true);
// change display date
this.on("startDateChange", this._reDo, true, this);
// change display format
this.on("displayChange", this._reFormat, this);
// Change footer text
this.on("footerStringChange", this._setFooter, this);
},
/**
* @method setupDays
* @description Display days of the week holders
* @protected
*/
setupDays: function() {
YAHOO.log("Diary.setupDays" ,"info" );
var calHolder = document.createElement( "div" ),
i, j = 0,
parent = calHolder,
dayEl = document.createElement( "div" ),
monthLabelEl = document.createElement("div"),
newDayEl ,
zeroTime = parseInt( this.get("startDate").getTime(), 10 ),
day,
displayFormat = this.get("display"),
// default = week (604800000)
limitTime = zeroTime + displayFormat.getSeconds,
that = this,
dayCount = 0,
jumpToWeekEl;
Dom.addClass( calHolder, CLASS_DIARY );
Dom.addClass( monthLabelEl, CLASS_DIARY_MONTHLABEL);
this._calHolder = calHolder;
if (this._footerEl) {
this.get("element").insertBefore(calHolder, this._footerEl);
} else {
this.get("element").appendChild(calHolder);
}
dayEl.className = CLASS_DIARY_DAY;
if (displayFormat.format == "month") {
jumpToWeekEl = document.createElement("div");
Dom.addClass(jumpToWeekEl, CLASS_DIARY_GOTO_WEEK);
jumpToWeekEl.innerHTML = "><br/>>";
jumpToWeekEl.title = "View this week in week view";
}
day = new Date(zeroTime);
i = zeroTime ;
// loop through from start to end adding a new DiaryDay for each
while(i < limitTime) {
newDayEl = dayEl.cloneNode(true);
j = Dom.generateId( newDayEl , 'day-' );
this._diaryData[ i ] = new DiaryDay( newDayEl, { coldate: day , diary: this , width: this._colWidth } );
parent.appendChild( newDayEl );
this._colToDayMap[j] = i;
dayCount++;
if (dayCount%7 == 0 && displayFormat.format == "month") {
parent.appendChild(jumpToWeekEl.cloneNode(true));
}
day = DM.add(day, DM.DAY, 1);
i = day.getTime();
}
this._renderCoreDiary();
// set the scrollTop and position of the visible pane:
this._setDiaryPosition();
// set a timer to move the 'today' class to the next column at midnight.
(function(){
var now = new Date(),
timeToMidnight = that._getEndOfDay(now).getTime() - now.getTime() + 1000,
todayClassMover = function(){
var currentColEl = Dom.getElementsByClassName(CLASS_DIARY_TODAY, "div", that.get("element")),
i,
colmap = this._colToDayMap,
currentCol,
now = new Date().setHours(0,0,0,0);
// if the current col's in view, remove the today class,
// and add it to the next col if poss
if (currentColEl && currentColEl[0]) {
currentColEl = currentColEl[0];
currentCol = this._diaryData[colmap[currentColEl.id]];
Dom.removeClass(currentColEl, CLASS_DIARY_TODAY);
if (currentCol.next()) {
Dom.addClass(currentCol.next(), CLASS_DIARY_TODAY);
}
} else {
// see if today is now in view:
for (i in colmap) {
if (colmap[i] === now) {
Dom.addClass(i, CLASS_DIARY_TODAY);
break;
}
}
}
// and now add another timer for 24hours time:
Lang.later(86400000, this, todayClassMover, null, false);
};
// at midnight set the next class
Lang.later(timeToMidnight, that, todayClassMover, null, false);
})();
},
/**
* @method getDiaryDay
* @description Get the DiaryDay object based on date (in seconds)
* @param secsDate {Int} start time in seconds.
* @return {Object} DiaryDay
*/
getDiaryDay: function( secsDate ) {
YAHOO.log( "Diary.getDiaryDay " , "info");
return this._diaryData[ secsDate ];
},
/**
* @description Click and drag to add new DiaryItem
* @method _startNewItem
* @protected
* @param ev {Event}
*/
_startNewItem: function( ev ){
YAHOO.log( "Diary._startNewItem", "info");
var el, sel, dayEl, target = Ev.getTarget(ev) , x, y, div;
/**
* @event itemBeforeStartCreate
* @description Before starting to create a new item by click and dragging
* If subscribers return false the start is cancelled.
* @param oArgs.originEvent Event passed through
*/
if( false === this.fireEvent( "itemBeforeStartCreate", {originEvent: ev } ) ){
return;
}
if( ( Dom.hasClass( target, CLASS_DIARY_ITEM) ||
Dom.getAncestorByClassName(target, CLASS_DIARY_ITEM ) ) &&
!this.get( "itemClickCreateNew" ) ){
return;
}
// only start a new one if previous ones have finished:
if( this._selector.selectorDiv === undefined || this._selector.selectorDiv === null ){
el = this._calHolder;
sel = this._selector;
// column we're over:
dayEl = Dom.getAncestorByClassName( Ev.getTarget(ev), CLASS_DIARY_DAY);
if( dayEl === null || dayEl === undefined ){
return;
}
sel.dayNumber = dayEl.id;
Ev.addListener( el , 'mousemove', this._resizeSelectorDiv , this, true );
// Ev.addListener( el , 'mouseup' , this._endSelector , this, true );
x = Ev.getPageX( ev );// ev.clientX;
y = Ev.getPageY( ev ) - Dom.getDocumentScrollTop(); //ev.clientY;
sel.startX = x;
sel.startY = y;
div = document.createElement( 'div' );
Dom.addClass( div, CLASS_DIARY_SELECTOR);
Dom.setStyle( div, 'left', x + 'px' );
Dom.setStyle( div, 'top', y + 'px' );
Dom.setStyle( div, 'width', '0px' );
Dom.setStyle( div , 'height', '0px' );
sel.selectorDiv = div;
// append to the data el
Dom.getElementsByClassName( CLASS_DIARY_DATACONTAINER,
"div", dayEl,
function(n) {n.appendChild(div);} );
Ev.stopEvent(ev);
}
},
/**
* @description Resizes the selector when creating a new item
* @method _resizeSelectorDiv
* @protected
* @param ev {Event}
*/
_resizeSelectorDiv: function( ev ){
var x = Ev.getPageX( ev );// ev.clientX;
var y = Ev.getPageY( ev ) - Dom.getDocumentScrollTop();//ev.clientY;
var startX = this._selector.startX;
var startY = this._selector.startY;
var top, left, width, height;
// work out co-ords
if( x < startX ){
width = startX - x;
left = x;
}
else{
width = x - startX;
left = startX;
}
if( y < startY ){
top = y;
height = startY - y;
}
else{
top = startY;
height = y - startY;
}
//height -= Dom.getDocumentScrollTop();
var div = this._selector.selectorDiv;
Dom.setStyle( div, 'width' , width + "px" );
Dom.setStyle( div, 'height' , height + "px" );
Dom.setStyle( div, 'left' , left + "px" );
Dom.setStyle( div, 'top' , top + "px" );
Ev.stopEvent(ev);
},
/**
* @description Called at the end of selector - creating a new item by drag-drop
* @method _endSelector
* @protected
* @param ev {Event}
*/
_endSelector: function(ev){
YAHOO.log( "Diary._endSelector " , "info");
// start day of new item
var itemDay = this._colToDayMap[ this._selector.dayNumber ],
// date object of new item
itemStartDate = new Date( itemDay ),
// final day of new item
finalDayEl = Dom.getAncestorByClassName( Ev.getTarget(ev), CLASS_DIARY_DAY),
// final day id and date
finalDayNumber = ( finalDayEl ) ? finalDayEl.id : 0,
finalItemDay = this._colToDayMap[ finalDayNumber ],
// end date is either same day or where the mouse-upped
itemEndDate = new Date( ( this.get("allowCreateMultiDayItems" ) ? finalItemDay : itemDay ) ),
// work out times from mouse positions
regionT = parseInt( Dom.getRegion( this._diaryData[ itemDay ]._backgroundEl ).top , 10 ),
t = Math.abs( parseInt( Dom.getStyle( this._selector.selectorDiv, "top" ) , 10 ) -
regionT + Dom.getDocumentScrollTop()),
h = parseInt( Dom.getStyle( this._selector.selectorDiv, "height" ), 10 ),
// the new DiaryItem
newItem,
// tidies up after the item's been created
cleanUp = function( ob ){
Ev.purgeElement( ob._calHolder, false, 'mousemove' );
//Ev.purgeElement( ob._calHolder, false, 'mouseup' );
var div = ob._selector.selectorDiv;
div.parentNode.removeChild( div );
ob._selector.selectorDiv = null;
ob._selector.startX = null;
ob._selector.startY = null;
};
/**
* @event itemBeforeEndCreate
* @description Fired on the mouseup after drag-selecting to create a new Item.
* This is fired before the actual item is created and rendered.
* If subscribers return false the start is cancelled.
*/
if( false === this.fireEvent( "itemBeforeEndCreate", {
DTSTART: itemStartDate,
DTEND: itemEndDate,
originEvent: ev } ) ){
cleanUp( this );
return;
}
if( h > 0 ){
var itemCfg = { DTSTART: 0 , DTEND: 0 , SUMMARY: '' },
now,
startHours,
startMins,
endHours,
endMins,
tSecs,
hSecs;
// month view: set times to now and +1 hour
if (this.get("display").format == "month" || this.get("display").format == "year") {
now = new Date();
startHours = now.getHours();
startMins = now.getMinutes();
endHours = Math.min(startHours + 1, 23);
endMins = startMins;
} else {
// work out times from drag-drop
tSecs = DiaryItem.prototype.calcStartFromPosition( t ,this.get("pxPerHour"));
hSecs = DiaryItem.prototype.calcEndFromPosition(h, t, this.get("pxPerHour"));
startHours = Math.floor( tSecs / 3600 );
startMins = Math.floor( (tSecs - ( startHours * 3600 ) ) / 60 );
endHours = Math.floor( hSecs / 3600 );
endMins = Math.floor( ( ( hSecs ) - ( endHours * 3600 ) ) / 60 );
}
// to nearest 15 minutes
startMins = startMins - ( startMins % 15 );
endMins = endMins - ( endMins % 15 );
// set the times
itemStartDate.setHours( startHours , startMins );
itemEndDate.setHours( endHours , endMins );
itemCfg.DTSTART = itemStartDate;
itemCfg.DTEND = itemEndDate;
// the new DiaryItem
newItem = this.addItem( itemCfg, true );
cleanUp( this );
/**
* @event itemEndCreate
* @description Fired at the end of thee mouseup after drag-selecting to create a new Item.
* This is fired after the actual item is created and rendered.
*/
this.fireEvent( "itemEndCreate" , { item: newItem } );
} else {
cleanUp( this );
}
},
/**
* @description Add an item to the Diary
* @method addItem
* @param oCfg {Object} Data for the new item, minimally- DTSTART: oDate, DTEND: oDate
* @param redraw {Boolean} Whether to redraw once it's added
* @return {DiaryItem} The new item created
*/
addItem: function( oCfg , render ){
var itemDay,
newItem,
firstItem,
itemDayDate,
nextColumn,
newConfig,
that = this,
saveAndRender;
if( render === undefined || render === null ){
render = false;
}
/**
* @event beforeAddItem
* @description Before an item is added to the Diary
* @param Object Object literal containing data
*/
this.fireEvent("beforeAddItem", {data: oCfg});
// which column are we adding this to?
itemDay = this._findFirstItemDay(oCfg.DTSTART, oCfg.DTEND);
if (itemDay === false) {
return false;
}
// useful internal function to save and render a newly added item
saveAndRender = function (item) {
that._itemHash[item.get("element").id] = item;
if (render) {
that._diaryData[item.get("column").get("coldate").getTime()]._rebuildBlocks();
that._diaryData[item.get("column").get("coldate").getTime()]._renderBlocks();
}
that._applyFiltersToElement( item.get("element") );
// lock if necessary:
if (that._lockResize) {
item.lock();
}
if (that._lockDragDrop) {
item.dragdrop.lock();
}
};
// Create a date object for the column date
itemDayDate = new Date(itemDay);
// Alter the config passed to DiaryItem:
newConfig = Lang.merge(oCfg, {
resizeTop: true,
resizeBottom: true ,
enableDragDrop: true,
useAnimation: this.get("useAnimation"),
pxPerHour: this.get("pxPerHour"),
useCssCategories: this.get("useCssCategories")
});
// if it's a one-day item, just add it:
if (this._sameDay(oCfg.DTSTART, oCfg.DTEND)) {
firstItem = this._diaryData[itemDay].addItem(newConfig);
saveAndRender(firstItem);
}
// otherwise it extends over several days:
else {
nextColumn = this._diaryData[itemDay];
// loop through until we reach the end of the Diary view, or the end of the event
while (nextColumn !== undefined && !DM.after( nextColumn.get("coldate") , oCfg.DTEND)) {
// Alter the config passed to DiaryItem:
newConfig = Lang.merge(oCfg, {
resizeTop: true,
resizeBottom: true,
enableDragDrop: true,
useAnimation: this.get("useAnimation"),
pxPerHour: this.get("pxPerHour"),
useCssCategories: this.get("useCssCategories")
});
// Is the real start date of the item the same as the column
// we're adding it to?
if (this._sameDay(oCfg.DTSTART, nextColumn.get("coldate"))) {
// this is the first of a multi-day event:
// displayed start time == DTSTART
// top handle for resize
// no drag-drop
// no bottom handle for resize
newConfig = Lang.merge(newConfig, {
resizeBottom: false ,
enableDragDrop: false ,
_displayDTSTART: oCfg.DTSTART,
_displayDTEND: this._getEndOfDay(nextColumn.get("coldate")),
multiDayPosition: "first"
});
} else {
// is this the last day of the item?
if (this._sameDay(oCfg.DTEND, nextColumn.get("coldate"))) {
// displayed end time == DTEND
// display start tiem = midnight
// no top handle for resize
// no drag-drop
// bottom handle for resize
newConfig = Lang.merge(newConfig, {
resizeTop: false,
enableDragDrop: false,
_displayDTSTART: nextColumn.get("coldate"),
_displayDTEND: oCfg.DTEND,
multiDayPosition: "last"
});
} else {
// this is an intermediate item
// no dragdrop
// no resize
// runs from midnight to midnight
newConfig = Lang.merge(newConfig, {
resizeBottom: false ,
resizeTop: false,
enableDragDrop: false ,
_displayDTSTART: nextColumn.get("coldate"),
_displayDTEND: this._getEndOfDay(nextColumn.get("coldate")),
multiDayPosition: "mid"
});
}
} // end of setting up the config objects
// create and add the new Item:
newItem = nextColumn.addItem(newConfig);
if (firstItem === undefined || firstItem === null) {
firstItem = newItem;
} else {
// add parent/child references:
firstItem.addMultiDayChild(newItem);
newItem.set("multiDayParent", firstItem);
}
saveAndRender(newItem);
// go on to the next column:
nextColumn = nextColumn.next();
}// end of while loop
} // end of adding multi-day events
return firstItem;
},
/**
* @method getItem
* @description Returns the DiaryItem with id elId
* @param elId {String}
* @return {DiaryItem}
*
*/
getItem: function (elId) {
var el = this._itemHash[ elId ];
if (el) {
return el;
}
return false;
},
/**
* @method removeItem
* @description Removes an item from the diary and destroys the element
* @param item {Object} DiaryItem to remove
*/
removeItem : function (item) {
if (item === undefined) {
return false;
}
var elId = item.get("element").id,
col = item.get("column");
col.removeItemFromBlock(item);
item.destroy();
item = null;
delete this._itemHash[elId] ;
col._rebuildBlocks();
col._renderBlocks();
},
/**
*
* Event handlers
*
*
*/
/**
* @description When a DiaryItem is clicked
* @method handleItemClick
* @param ev {Event}
* @param el {HTMLElement}
* @param el {HTMLElement}
* @protected
*/
handleItemClick: function (ev, el, container) {
YAHOO.log( 'Diary.handleItemClick', "info");
/**
* @event itemClick
* @param oArgs.item The DiaryItem clicked on
* @param oArgs.ev The click event
* @param oArgs.el The element clicked on
* @param oArgs.container The container element (from delegate)
*/
this.fireEvent("itemClick", {
item: this.getItem(el.id),
ev: ev,
el: el,
container: container
});
},
/**
* @method handleItemMouseEnter
* @description When a DiaryItem is mouseenter-ed. Default behaviour
* is to show a tooltip (if this was enabled in the config). The
* itemMouseEnter event is fired first; return false to this to stop
* the default behaviour.
* @param ev {Event}
* @param el {HTMLElement}
* @param container {HTMLElement}
* @protected
*/
handleItemMouseEnter : function( ev, el, container ){
/**
* @event itemMouseEnter
* @description When a mouse enters a DiaryItem. Return false
* to cancel default behaviour (tooltip at the moment).
* @param oArgs.item The DiaryItem mouseentered
* @param oArgs.ev The mouseenter event
* @param oArgs.el The element
* @param oArgs.container The container element (from delegate)
*/
if( false !== this.fireEvent( "itemMouseEnter" ,
{ item: this.getItem( el.id ),
ev: ev,
el: el,
container: container } ) ){
// Default behaviour: show tooltip with full details:
if( this._tooltip ){
this.getItem(el.id)._detailsEl.title = "";
this._tooltip.setBody( this.getItem( el.id ).renderFullDetails() );
this._tooltip.cfg.setProperty( "context" , el );
this._tooltip.cfg.setProperty( "disabled" , false );
this._tooltip.show();
}
}
},
/**
* @method handleDayClick
* @description Handles clicks on the day container
* @public
* @param ev {Event}
* @param el {HTMLElement}
* @param container {HTMLElement}
*/
handleDayClick: function(ev, el, container) {
/**
* @event dayClick
* @description When a day container is clicked.
* @param oArgs.el The element clicked on
* @param oArgs.day A date object representing the day clicked
*/
this.fireEvent("dayClick", {
el: el,
day: new Date(this._colToDayMap[el.id])
});
//Ev.stopEvent(ev);
},
/**
* @method _handleColumnHeaderClick
* @description Handles column header clicks. Does different things
* depending on view: on week view, goes to day-to-view; on day view
* goes back to week view; on month view does nothing.
* @protected
*/
_handleColumnHeaderClick : function (ev, el, container) {
var i,
parent = el.parentNode,
newFormat = this.get("display").getOnClickFormat;
if (!newFormat) {
return;
}
this.set("display", newFormat, true);
// set the new date
for (i = 0; parent.childNodes.length; i++) {
if (el === parent.childNodes[i]) {
this.set("startDate", DM.add(this.get("startDate"), DM.DAY, i), true);
break;
}
}
// change the new format (hack to trigger change event on second one)
this.set("display", {format:"a"}, true);
this.set("display", newFormat, false);
},
/**
* @method _handleMonthToWeekClick
* @description Handles clicks on the right-hand 'go to week view'
* when in month view. Works out which week it is and then reformats.
* @protected
*/
_handleMonthToWeekClick: function (ev, el, container) {
// work out the date of the previous day: this will be the end of the week
var newEndDate = new Date(this._colToDayMap[ Dom.getPreviousSibling(el).id ]),
newStartDate = DM.subtract(newEndDate, DM.DAY, 6);
this.set("startDate", newStartDate, true);
this._changeView();
this.set("display", { format: "week" }, false);
},
/**
*
*
*
* Navigation
*
*
*
*
*/
/**
* @method _doPrevious
* @description Go to the previous day/week
* @protected
*
*/
_doPrevious : function() {
var newStartDate = DM.subtract( this.get("startDate"), DM.DAY, this.get("display").getDaysAcross);
this.set("startDate", newStartDate);
},
/**
* @method _doNext
* @description Go to the next day/week
* @protected
*/
_doNext : function() {
var newStartDate = DM.add(this.get("startDate"), DM.DAY, this.get("display").getDaysAcross);
this.set("startDate", newStartDate);
},
/**
* @method _doCalNav
* @description Go to any start date (set by calendar)
* @param ev {Event}
* @param selDate {Array} Selected date, as returned by YAHOO.util.Calendar
* (ie array [[[ yyyy, mm, dd]]])
* @protected
*/
_doCalNav : function(ev, selDate) {
this._navCalendar.hide();
this.set( "startDate" ,new Date( selDate[0][0][0], selDate[0][0][1] - 1 , selDate[0][0][2] ) );
},
/**
* @method _doFirstDayOfTodaysWeek
* @description Goes to the start of this week
* @protected
*/
_doFirstDayOfTodaysWeek: function() {
YAHOO.log("Diary._doFirstDayOfWeek " , "info");
var startOfWeek = DM.getFirstDayOfWeek(new Date(), 1);
// do we need to change?
if( DM.between(startOfWeek, this.get("startDate"), this.get("endDate"))) {
return;
}
this.set("startDate", startOfWeek);
},
/**
* @method _changeView
* @description Changes between views (day/week/month)
* @protected
*/
_changeView : function (ev, el, newDisplay) {
YAHOO.log("Diary._changeView", "info");
var display = this.get("display"),
newDisplay = newDisplay || {
format: display.getNextFormat,
startTime : display.startTime,
endTime : display.endTime
};
this.set("display", newDisplay);
},
/**
* @method _reDo
* @description Redraws calendar. If getData is true, it will delete
* DiaryItems and data currently held first
* @param getData {Boolean}
* @protected
*/
_reDo: function (getData) {
this.set("endDate", DM.add(this.get("startDate"), DM.DAY, this.get("display").getDaysInView));
/**
* @event beforeReDo
* @description Fired before the Diary is redrawn, which happens
* on navigation (onStartDateChange)
*
*/
this.fireEvent( "beforeReDo" );
this._destroyDays();
this._destroyData();
this.setupDays();
if (getData) {
this.initData( this.get("element"), {}, this._ds );
} else {
this._parseData(this._ds, this._lastData);
}
this._renderTitle();
this._renderColumnLabels();
this._applyFilters();
},
/**
*
* Data methods
*
*
*/
/**
* @method initData
* @description Store a reference to the data and get it
* @param el {HTMLElement}
* @param oCfg {Object}
* @param oDS {YAHOO.util.DataSource}
* @protected
*/
initData: function( el, oCfg, oDS ){
YAHOO.log( "Diary.initData" ,"info");
this._ds = oDS;
this._getData( oDS );
},
/**
* @method _getData
* @description Gets data from the data source
* @param oDS {YAHOO.util.DataSource}
* @protected
*/
_getData: function( oDS ){
/**
* Fired before data requested
* @event dataRequest
* @description Fired before data requested
* @param oArgs.DataSource DataSource
* @param oArgs.target Diary
*/
this.fireEvent("dataRequest", {DataSource: oDS, target: this});
this._renderLoading();
oDS.sendRequest( '' , { success: this._parseData,
failure: this._dataFailed,
scope: this
});
},
/**
* Parses the data when it comes
* @param req {Object} Request object
* @param data {Object} Data returned by DataSource
* @protected
*/
_parseData: function ( req, data ){
YAHOO.log( "Diary._parseData starting" , "info" );
// sort the raw data:
//data.results.sort( this._rawItemSorter );
var num = data.results.length,
itemDate,
itemDay,
i,
startDate = this.get("startDate"),
endDate = this.get("endDate" ),
tempVal,
newData = {},
currentData = {},
fieldMap = this.get("fieldMap");
this._lastData = data;
for( i = 0; i < num; i++ ){
currentData = data.results[i];
if (currentData[fieldMap.DTSTART] !== undefined) {
// make sure it ends after it starts: swap if not
if( DM.before( currentData[ fieldMap.DTEND ], currentData[fieldMap.DTSTART] ) ){
tempVal = currentData[fieldMap.DTSTART];
currentData[fieldMap.DTSTART] = currentData[ fieldMap.DTEND ];
currentData[ fieldMap.DTEND ] = tempVal;
}
// Get a 'zero' start date to group by day
itemDate = currentData[fieldMap.DTSTART];
// check item is in current date range:
if( !( DM.after( itemDate, endDate ) ||
DM.before( currentData[ fieldMap.DTEND ] , startDate ) ) ){
itemDay = this._findFirstItemDay( itemDate, currentData[ fieldMap.DTEND ] );
if( itemDay === false ){
YAHOO.log( "ERROR Data parsed not in range", "warn" );
} else {
// Add the diary item for relevant day
newData = this._parseDataUsingFieldmap(currentData);
this.addItem(newData, true);
}
} else {
YAHOO.log("data no tin range", "warn");
}
} else {
YAHOO.log("data no start date", "warn");
}
}
this._renderLoading();
/**
* Fired when data parsed and ready
* @event parseData
* @description Fired when data's been parsed
* @param oArgs.data Parsed data
* @param oArgs.type "parseData"
* @param oArgs.target Diary
*/
this.fireEvent( "parseData" , {
type: "parseData" ,
data: this._diaryData,
target: this
});
YAHOO.log("end of parse" , "info");
},
/**
* @method _parseDataUsingFieldMap
* @description Uses fieldMap given in config to extract from raw data
* to format expected by DiaryItems. Values in the fieldMap may be strings
* or functions, so this applies them as appropriate.
* @protected
* @param oData {Object} Object literal containing raw data to be parsed
* @return {Object} Object literal containing parsed data.
*/
_parseDataUsingFieldmap : function (oData) {
var data = {},
fieldMap = this.get("fieldMap"),
i = 0,
field,
fieldKey,
that = this;
// Loop through valid fields, and get data from oData as necessary.
for (i; i < ITEM_FIELDS.length; i++) {
field = ITEM_FIELDS[i];
fieldKey = fieldMap[field];
if (fieldKey !== undefined) {
if (YAHOO.lang.isString(fieldKey)) {
data[field] = oData[fieldKey];
} else if (YAHOO.lang.isFunction(fieldKey)) {
data[field] = fieldKey.call(that, oData);
}
}
}
return data;
},
/**
* @description Looks for the first day between startDate and endDate that has a column
* in the diary; multi-day items may not start in range but may go into it.
* @param startDate {Date}
* @param endDate {Date}
* @return {Date|false} Date or first if it doesn't fall in range.
* @private
*/
_findFirstItemDay: function( startDate, endDate ){
var testDate = startDate,
testZeroDay = new Date( testDate.getFullYear(), testDate.getMonth(), testDate.getDate() , 0 , 0 , 0 , 0 ).setHours(0,0,0,0);
while( !DM.after( testDate, endDate ) ){
if( this._diaryData[ testZeroDay ] !== undefined ){
return testZeroDay;
}
testDate = DM.add( testDate, DM.DAY, 1 );
testZeroDay = testDate.setHours(0,0,0,0);
}
return false;
},
/**
* @method _getDay
* @description Returns a Date object with times set to 0
* @param date {Date}
* @return {Date}
* @private
*/
_getDay: function ( date ){
return new Date( date.getFullYear(), date.getMonth(), date.getDate() , 0 ,0,0,0);
},
/**
* @method _getEndOfDay
* @description Returns a Date object with times set to 23:59:59
* @param date {Date}
* @return {Date}
* @private
*/
_getEndOfDay: function ( date ){
if( Lang.isNumber( date ) ){
return new Date( date ).setHours( 23,59,59,0 );
}
return new Date( date.getFullYear(), date.getMonth(), date.getDate() , 23 ,59,59,0);
},
/**
* @method _sameDay
* @description Are date1 and date2 the same day?
* @param date1 {Date}
* @param date2 {Date}
* @return {Boolean}
* @private
*/
_sameDay: function ( date1, date2 ){
return ( date1.getFullYear() == date2.getFullYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate() );
},
/**
* @method _dataFailed
* @description Called if sendRequest fails on the data
* @method _dataFailed
* @param req {object} Request object that failed
* @private
*/
_dataFailed: function( req ){
this._renderLoading();
YAHOO.log( "Failed to get data" );
/**
* @event dataFailure
* @description Fired when data has failed
* @param oArgs.request Request object from DataSource
*/
this.fireEvent( "dataFailure" , { request: req } );
},
/**
* @method _itemSorter
* @description Sorting function for arranging items in ascending date/time order
* @param oItem1 {DiaryItem}
* @param oItem2 {DiaryItem}
* @return {Boolean} True if oItem2 is before oItem1
* @private
*/
_itemSorter: function( oItem1 , oItem2 ){
return DM.before( oItem2.get( "DTSTART" ) , oItem1.get( "DTSTART" ) );
},
/**
* @method _rawItemSorter
* @description Sorting function for arranging items in ascending date/time order, using raw data objects
* @param oItem1 {Date} Property DTSTART used for sorting
* @param oItem2 {Date} Property DTSTART of comparison item for sort
* @return {Boolean} True if oItem2 is before oItem1
* @private
*/
_rawItemSorter: function(oItem1, oItem2) {
if (oItem1 === undefined || oItem1 === null) {
return true;
}
if (oItem2 === undefined || oItem2 === null) {
return false;
}
var fieldMap = this.get("fieldMap");
return DM.before( oItem2[fieldMap.DTSTART] , oItem1[fieldMap.DTSTART] );
},
/**
*
*
*
* render methods
*
*
*
*
*/
/**
* @method render
* @description Renders the Diary. Called after data is parsed - shouldn't
* need to be called otherwise.
*
*/
render: function(){
this._renderCoreDiary();
this.renderItems();
/**
* @event render
* @description When the rendering of the Diary is complete
* @param oArgs.target Diary
*/
this.fireEvent( "render" , { target: this });
},
/**
* @method _renderCoreDiary
* @protected
* @description Renders the Diary except for the items
*
*/
_renderCoreDiary : function () {
if (this.hasClass(CLASS_DIARY_CONTAINER)) {
return;
}
this.addClass(CLASS_DIARY_CONTAINER);
//this._renderDays();
this._renderNav();
this._renderFooter();
this._renderTooltip();
},
/**
* @method _renderNav
* @description Renders the navigation.
* Provides left, today, calendar and right buttons, and adds listeners.
* @protected
*
*/
_renderNav : function() {
if( this.getNavContainer() !== false ){
return;
}
var navContainer = document.createElement("div"),
titleContainer = document.createElement("div"),
buttonContainer = document.createElement("div"),
calContainer, cal, calId, calShowButton,
left = document.createElement("a"),
right = document.createElement("a"),
today = document.createElement("a"),
view = document.createElement("a"),
dayLabels = document.createElement("div");
// Dom.insertBefore( labelEl, parent.firstChild );
Dom.addClass(navContainer, CLASS_DIARY_NAV );
Dom.addClass(titleContainer, CLASS_DIARY_TITLE);
Dom.addClass(buttonContainer, CLASS_DIARY_NAV_BUTTONS);
Dom.addClass(left, CLASS_DIARY_NAV_LEFT);
Dom.addClass(right, CLASS_DIARY_NAV_RIGHT);
Dom.addClass(today, CLASS_DIARY_NAV_TODAY);
Dom.addClass(view, CLASS_DIARY_NAV_VIEW);
left.innerHTML = "previous";
left.title = "Go to previous week";
right.innerHTML = "next";
right.title = "Go to next week";
today.innerHTML = "today";
today.title = "Go to today";
view.innerHTML = "change view";
view.title = "Switch between day, week and month view";
navContainer.appendChild( titleContainer );
buttonContainer.appendChild( left );
if( YAHOO.widget.Calendar !== undefined && this.get("calenderNav") ){
calContainer = document.createElement("div");
calShowButton = document.createElement("div");
calId = Dom.generateId();
Dom.addClass( calContainer, CLASS_DIARY_NAV_CAL );
Dom.addClass( calShowButton, CLASS_DIARY_NAV_CALBUTTON);
calShowButton.appendChild( document.createTextNode( "show calendar" ) );
calShowButton.title = "Show calendar navigation";
Ev.on( calShowButton , "click" , this.showNavCalendar, this, true );
calContainer.id = calId;
buttonContainer.appendChild(calShowButton);
document.body.appendChild(calContainer);
}
buttonContainer.appendChild(today);
buttonContainer.appendChild(view);
buttonContainer.appendChild(right);
navContainer.appendChild(buttonContainer);
this.get("element").insertBefore(navContainer, this.get("element").firstChild);
if (calId !== null) {
cal = new YAHOO.widget.Calendar("navcal", calId, {close: true, navigator: true});
cal.selectEvent.subscribe(this._doCalNav, this, true);
cal.hide();
cal.render();
this._navCalendar = cal;
}
// label for the date
Dom.addClass(dayLabels, CLASS_DIARY_COLLABEL_CONTAINER);
navContainer.appendChild(dayLabels);
this._renderColumnLabels();
Ev.on( left, "click" , this._doPrevious , this , true );
Ev.on( right , "click" , this._doNext , this , true );
Ev.on( today, "click" , this._doFirstDayOfTodaysWeek, this, true );
Ev.on( view, "click" , this._changeView, this, true );
this._renderTitle();
},
/**
* @method _renderColumnLabels
* @protected
* @description Adds column headers and date labels, and click listeners
*/
_renderColumnLabels : function() {
var startDate = this.get("startDate"),
labelEl = document.createElement("span"),
thisLabel,
paddEl,
dayCounter = 0,
dayLabels = Dom.getElementsByClassName(
CLASS_DIARY_COLLABEL_CONTAINER,
"div",
this.getNavContainer()
)[0];
// remove whatever was there
Ev.purgeElement(dayLabels);
dayLabels.innerHTML = '';
Dom.addClass( labelEl, CLASS_DIARY_COLLABEL);
Dom.setStyle( labelEl, "width" , (this._colWidth - 4) + "px");
if (this.get("display").format == "year") {
paddEl = document.createElement("span");
paddEl.innerHTML = " ";
Dom.setStyle(paddEl, "width", "50px");
dayLabels.appendChild(paddEl);
}
// go through the days adding labels:
for (dayCounter = 0; dayCounter < this.get("display").getDaysAcross; dayCounter += 1 ) {
thisLabel = labelEl.cloneNode(false);
thisLabel.innerHTML = this.renderDateLabel( startDate );
dayLabels.appendChild(thisLabel);
startDate = DM.add(startDate, DM.DAY, 1);
}
},
/**
* @method getNavContainer
* @description Returns the container for the navigation els
* @return {HTMLElement}
*/
getNavContainer : function() {
var con = Dom.getElementsByClassName( CLASS_DIARY_NAV, "div", this.get("element" ) );
if( con === null || con === undefined || con.length === 0 ){
return false;
}
return con[0];
},
/**
* @method showNavCalendar
* @description Shows the calendar navigator
* @param ev {Event} Event object; used to position. Pass an object
* with ev.clientX and ev.clientY to position the Calendar manually.
*/
showNavCalendar : function(ev){
var cal = this._navCalendar;
cal.show();
if (ev !== null && Lang.isNumber(ev.clientX) && Lang.isNumber(ev.clientY)) {
Dom.setXY( cal.oDomContainer, [ ev.clientX - 200, ev.clientY ] );
}
},
/**
* @method _setDiaryPosition
* @description Sets the height of the visible pane and scrollTop to
* show the correct segment of the Diary (e.g. 8am to 7pm)
* @private
*/
_setDiaryPosition: function() {
var dayHeight, scrollTop, display = this.get("display");
// set the style of the containers to get the height:
dayHeight = display.getDiaryHeight + "px";
Dom.getElementsByClassName( CLASS_DIARYDAY_CONTAINER, "div", this._calHolder,
function(n){Dom.setStyle( n, "height" , dayHeight); } );
Dom.setStyle(this._calHolder, "height",(display.getDiaryHeight * Math.floor(display.getDaysInView/display.getDaysAcross)) + "px");
scrollTop = display.getDiaryScrollTop;
this._calHolder.scrollTop = scrollTop;
},
/**
* @method renderDateLabel
* @description Overwritable to render date labels at the top of each column.
* Default is oDate.toString().substring(0, 10);
* @param oDate {Date} Date object
* @return {String}
*/
renderDateLabel: function( oDate ) {
var display = this.get("display");
if ( Lang.isFunction(display.renderDateLabel)) {
return display.renderDateLabel(oDate);
}
return oDate.toString().substring( 0, 10 );
},
/**
* @method _renderTitle
* @description Puts the title text in the title container box
* Doesn't actually produce the title string though.
* @protected
*/
_renderTitle: function(){
var titleBox = Dom.getElementsByClassName(CLASS_DIARY_TITLE, "div", this.getNavContainer() );
titleBox[0].innerHTML = this.renderTitle();
},
/**
* @method renderTitle
* @description Overwriteable to render title string
* Can use strftime identifiers in the format string
* @param titleString {String} Title string with strftime placeholders.
* @return {String}
*/
renderTitle: function( titleString ){
if( titleString === undefined ) {
titleString = this.get( "titleString" );
}
return YAHOO.util.Date.format(this.get("startDate"), { format: titleString }, this.get("locale") );
},
/**
* @method renderItems
* @description Renders the diary Items onto the Diary
* @public
*/
renderItems : function() {
var displayFormat = this.get("display"),
zeroTime = parseInt(this.get("startDate").getTime(), 10),
limitTime = zeroTime + displayFormat.getSeconds,
i;
for(i = zeroTime; i < limitTime ; i += 86400000 ) {
if( this._diaryData[ i ] !== undefined ) {
this._diaryData[ i ]._rebuildBlocks();
this._diaryData[ i ].render();
}
}
},
/**
* @method rebuildColumns
* @description Goes through existing data and updates columns and blocks
* - useful for visibility changes of items
* @public
*/
rebuildColumns : function () {
var i = 0,
dData = this._diaryData;
for (i in dData) {
if (dData[i]._rebuildBlocks !== undefined){
dData[i]._rebuildBlocks();
dData[i]._renderBlocks()
}
}
},
/**
* @method _renderTooltip
* @description Renders tooltip for showing full info
* @protected
*/
_renderTooltip: function(){
if( this.get("tooltip" ) && YAHOO.widget.Tooltip !== undefined ){
this._tooltip = new YAHOO.widget.Tooltip( this.get("element").appendChild( document.createElement("div") ), {
showDelay: 300,
hidedelay : 500,
disabled: true
});
}
},
/**
* @method _renderFooter
* @description Adds a footer element for status messages
* @protected
*/
_renderFooter : function() {
var ftEl = document.createElement("div");
Dom.addClass(ftEl, CLASS_DIARY_FOOTER);
Dom.generateId(ftEl);
this.appendChild(ftEl);
this._footerEl = ftEl;
},
/**
* @method _setFooter
* @description Adds text to the footer element, and optionally hides
* it after hideDelay. Called onFooterStringChange.
* Animated opacity change if Anim is available.
* @protected
*/
_setFooter : (function() {
// Anon function keeps single anim object and work private, re-uses
// anim
var ftEl = this._footerEl,
doTextChange,
setText = function (text) {
ftEl.innerHTML = text;
},
anim = (Lang.isObject(YAHOO.util.Anim) ? new YAHOO.util.Anim(ftEl,{opacity:{to:1}}, 0.5) : false),
fadeTimer;
// Animated version
if (anim) {
doTextChange = function (text, fadeOut) {
var animStartOpacity = ( fadeOut ? 1 : 0);
Dom.setStyle(ftEl,"opacity",animStartOpacity);
anim.attributes.opacity.to = (1 - animStartOpacity);
anim.onComplete.unsubscribe(setText);
if (animStartOpacity === 0){
setText(text)
} else {
anim.onComplete.subscribe(setText, text);
}
anim.animate();
}
} else {
// Plain version
doTextChange =setText;
}
// The actual method that's set as _setFooter
return function() {
var ftOb = this.get("footerString");
ftEl = this._footerEl;
// Stop previously set changes:
if (fadeTimer) {
fadeTimer.cancel();
setText("");
}
if (anim) {
anim.setEl(ftEl);
anim.stop(true);
}
// chacks
if (ftOb.text === undefined || !Lang.isString(ftOb.text)) {
return;
}
// write the text (or fade it in)
doTextChange(ftOb.text, ftOb.fadeOut);
if (ftOb.hideDelay !== undefined && ftOb.hideDelay > 0) {
fadeTimer = Lang.later(ftOb.hideDelay, this, function () {
this.set("footerString", {text: "", fadeOut: true});
}, null, false);
}
};
}()),
/**
* @method _renderLoading
* @description Adds a div with a 'loading' class to indicate data's on
* it's way.
* @protected
* @TODO Sort out - messes with navigation currently...
*/
_renderLoading : function() {
return;
/* var elId;
if(this._loadingElId === '') {
elId = Dom.generateId();
this.getNavContainer().innerHTML += "<div class='" + CLASS_DIARY_LOADING + ' ' + CLASS_DIARY_LOADING_HIDDEN + "' id='" + elId + "'>loading data...</div>";
this._loadingElId = elId;
} else {
elId = this._loadingElId;
}
if (Dom.hasClass( elId, CLASS_DIARY_LOADING_HIDDEN )) {
Dom.removeClass(elId, CLASS_DIARY_LOADING_HIDDEN);
} else {
Dom.addClass(elId, CLASS_DIARY_LOADING_HIDDEN);
}
*/
},
/**
* @method _reFormat
* @protected
* @description Called on formatChange to alter start/end time window
* or day/week/month to view.
*/
_reFormat : function (ev) {
// change view
if (ev.prevValue.format !== ev.newValue.format) {
// remove the previous css class added to the container
this.removeClass( CLASS_DIARY_DISPLAY[ ev.prevValue.format.toUpperCase() ] );
// setup the new one:
this._setViewFormat(ev.newValue.format);
}
if (ev.prevValue.startTime !== ev.newValue.startTime ||
ev.prevValue.endTime !== ev.newValue.endTime) {
this._setDiaryPosition();
}
},
/**
* @method _setViewFormat
* @protected
* @description Sets week/day/month view
* @param format {String} type of view
*/
_setViewFormat : function (format) {
var newDataNeeded = (format != "day");
// set the new col width
this.set("scaleColumns", this.get("scaleColumns"), true);
if (format !== "month" && format !== "year") {
this.unlock(true, false);
Dom.getElementsByClassName(CLASS_DIARY_ITEM, "div", this.get("element"),
function (n) {Dom.removeClass(n, CLASS_DIARY_ITEM_MONTHVIEW);});
}
if (format == "year") {
this.setStyle("line-height", "20px");
} else {
this.setStyle("line-height", null);
}
this._reDo(newDataNeeded);
},
/**
* @method lock
* @description Locks resize and/or drag-drop for all currently existing
* DiaryItems.
* @param lockResize {Boolean}
* @param lockDragDrop {Boolean}
*
*/
lock: function (lockResize, lockDragDrop) {
var i,
items = this._itemHash;
if (!lockResize && !lockDragDrop) {
return;
}
if (lockResize) {
this._lockResize = true;
}
if (lockDragDrop) {
this._lockDragDrop = true;
}
for (i in items) {
if (Lang.isFunction(items[i].lock)) {
if (lockResize) {
items[i].lock();
}
if (lockDragDrop && items[i].dragdrop !== null && Lang.isFunction(items[i].dragdrop.lock)) {
items[i].dragdrop.lock();
}
}
}
},
/**
* @method unlock
* @description Unlocks resize and/or drag-drop for all currently existing
* DiaryItems.
* @param unlockResize {Boolean}
* @param unlockDragDrop {Boolean}
*
*/
unlock: function (unlockResize, unlockDragDrop) {
var i,
items = this._itemHash;
if (!unlockResize && !unlockDragDrop) {
return;
}
if (unlockResize) {
this._lockResize = false;
}
if (unlockDragDrop) {
this._lockDragDrop = false;
}
for (i in items) {
if (Lang.isFunction(items[i].unlock)) {
if (unlockResize) {
items[i].unlock();
}
if (unlockDragDrop && items[i].dragdrop !== null && Lang.isFunction(items[i].dragdrop.unlock)) {
items[i].dragdrop.unlock();
}
}
}
},
/**
* @method addItemFilter
* @description Filters DiaryItems based on css selector.
* selector passed will be appended directly to ".lplt-diary-item"
* If nothing is passed, it will filter for all (ie hide everything)
* @param selector {String}
* @return {Int} Number of items hidden
*/
addItemFilter : function (selector) {
YAHOO.log( "Diary.addItemFilter" , "info" );
var i,
items = Selector.query("." + CLASS_DIARY_ITEM + selector, this.get("element"));
for( i = 0; i < items.length; i++ ){
Dom.addClass( items[i], CLASS_DIARY_ITEM_HIDDEN);
this._itemHash[items[i].id].set("visible", false);
}
this._filters[ selector ] = true;
// make remaining ones wider:
this.rebuildColumns();
return i;
},
/**
* @method removeItemFilter
* @description Removes filter on DiaryItems based on css selector.
* selector passed will be appended directly to ".lplt-diary-item"
* If nothing is passed, it will filter for all (ie show everything)
* @param selector {String}
* @return {Int} Number of items shown
*/
removeItemFilter : function (selector) {
var i,
f,
items = Selector.query("." + CLASS_DIARY_ITEM + selector, this.get("element") ),
itemsToRemoveFilter = false,
remainingFilters = [];
// remove this filter from memory.
this._filters[ selector ] = undefined;
// Now build a test selectors based on remaining filters applied:
for (f in this._filters) {
if( Lang.isString(f) && this._filters[f] === true ){
remainingFilters.push("." + CLASS_DIARY_ITEM + f);
}
}
if (remainingFilters.length > 0 ) {
itemsToRemoveFilter = remainingFilters.join(", ");
}
// loop through items that matched the given selector,
// but check that they shouldn't still be hidden because of other
// filters applied
for( i = 0; i < items.length; i++ ){
if (itemsToRemoveFilter === false) {
Dom.removeClass(items[i], CLASS_DIARY_ITEM_HIDDEN);
this._itemHash[items[i].id].set("visible", true);
} else if ( !Selector.test(items[i], itemsToRemoveFilter)) {
Dom.removeClass(items[i], CLASS_DIARY_ITEM_HIDDEN);
this._itemHash[items[i].id].set("visible", true);
}
}
// make remaining ones smaller:
this.rebuildColumns();
return i;
},
/**
* @method toggleItemFilter
* @description Toggles an item filter
* @public
* @param selector {String}
* @return {Int} Number of items shown/hidden: positive => shown; negative
* value => hidden
*/
toggleItemFilter : function(selector) {
if (this._filters[ selector ] !== undefined) {
return this.removeItemFilter(selector);
} else {
return -1 * this.addItemFilter(selector);
}
},
/**
* @method _applyFilters
* @description Re-applies all filters currently in place.
* Called internally when navigating to keep existing filters in place
* @protected
*/
_applyFilters: function(){
var i,filters = this._filters;
for( i in filters ){
if( Lang.isString( i ) && filters[ i ] === true ){
this.addItemFilter( i );
}
}
},
/**
* @method _applyFiltersToElement
* @description Re-applies all filters to the element passed.
* Called internally when navigating to keep existing filters in place.
* @param HTMLElement
* @protected
*/
_applyFiltersToElement : function(el) {
var i,
filters = this._filters;
for (i in filters) {
if(Lang.isString(i) && filters[i] === true && Selector.test(el, i)) {
Dom.addClass(el, CLASS_DIARY_ITEM_HIDDEN);
}
}
},
/**
* @method destroy
* @description destroys the Diary
*/
destroy : function() {
this._destroyData();
this._destroyDays();
this._removeListeners();
// other widgets:
if (this._tooltip) {
this._tooltip.destroy();
this._tooltip = null;
}
if (this._navCalendar) {
this._navCalendar.destroy();
this._navCalendar = null;
}
this.removeClass( CLASS_DIARY_CONTAINER );
this.get("element").innerHTML = "";
this._ds = null;
delete this._ds;
/**
* @event destroy
* @description When the Diary has finished destorying
*/
this.fireEvent("destroy");
},
/**
* @method _destroyData
* @description Clears out existing data
* @protected
*/
_destroyData: function(){
var i;
for( i = 0; i < this._diaryData.length; i++ ){
this._diaryData[i].destroy();
}
for (i in this._itemHash) {
if (Lang.isFunction(this._itemHash[i].destroy)) {
this._itemHash[ i ].destroy();
delete this._itemHash[i];
}
}
this._itemHash = [];
this._diaryData = [];
this._colToDayMap = {};
/**
* @event destroyData
* @description After all the data has been destroyed.
*/
this.fireEvent( "destroyData" );
},
/**
* @method _destroyDays
* @description Destroys the days rendered in the diary
* @protected
*/
_destroyDays: function(){
//Ev.purgeListeners( this._calHolder );
this.get("element").removeChild( this._calHolder );
},
/**
* @method _removeListeners
* @description Removes event listeners
* @protected
*/
_removeListeners : function() {
// listeners on the main element (delegated)
Ev.purgeElement(this.get("element"), false);
// navigation event listeners (recurse: not too deep and need to pick up
// all the nav buttons
Ev.purgeElement(this.getNavContainer(), true);
},
/**
* Returns a string representation of the object.
* @method toString
* @return {String} The string representation of the Diary
* @private
*/
toString : function() {
return "Diary " + this.get("element").id;
}
/**
* @event itemBeforeStartMove
* @description Fired before everything starts moving. Return false to cancel move.
* @param oArgs.item DiaryItem that's about to be moved.
*/
/**
* @event itemBeforeEndMove
* @description fired when an item is moved or resized (ie the times change).
* Return false to cancel the resize/move
* @param oArgs.from Object literal containing original DTSTART and DTEND
* @param oArgs.to Object literal containing final DTSTART and DTEND
* @param oArgs.item DiaryItem that's being moved
* @param oArgs.originEvent Original event from resize/dragdrop passed through.
*/
/**
* @event itemEndMove
* @description fired when an item is moved or resized (ie the times change)
* @param oArgs.from Object literal containing original DTSTART and DTEND
* @param oArgs.to Object literal containing final DTSTART and DTEND
* @param oArgs.item DiaryItem that's being moved
* @param oArgs.originEvent Original event from resize/dragdrop passed through.
*/
});
YAHOO.widget.Diary = Diary;
// Bug in Selector: breaks filtering in IE7
if(YAHOO.env.ua.ie && ((!document.documentMode && YAHOO.env.ua.ie<8) || document.documentMode < 8)){// rewrite class for IE < 8
YAHOO.util.Selector.attrAliases['class'] = 'className';
YAHOO.util.Selector.attrAliases['for'] = 'htmlFor';
}
})();
YAHOO.namespace( "widget" );
YAHOO.register("diary", YAHOO.widget.Diary, {version: "1.4", build: "016"});