dojox
Version:
Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.
1,862 lines (1,568 loc) • 81.3 kB
JavaScript
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/_base/array",
"dojo/_base/window",
"dojo/_base/event",
"dojo/_base/html",
"dojo/sniff",
"dojo/query",
"dojo/dom",
"dojo/dom-style",
"dojo/dom-class",
"dojo/dom-construct",
"dojo/dom-geometry",
"dojo/on",
"dojo/date",
"dojo/date/locale",
"dojo/when",
"dijit/_WidgetBase",
"dojox/widget/_Invalidating",
"dojox/widget/Selection",
"./time",
"./StoreMixin",
"./StoreManager",
"./RendererManager"],
function(
declare,
lang,
arr,
win,
event,
html,
has,
query,
dom,
domStyle,
domClass,
domConstruct,
domGeometry,
on,
date,
locale,
when,
_WidgetBase,
_Invalidating,
Selection,
timeUtil,
StoreMixin,
StoreManager,
RendererManager){
/*=====
var __GridClickEventArgs = {
// summary:
// The event dispatched when the grid is clicked or double-clicked.
// date: Date
// The start of the previously displayed time interval, if any.
// triggerEvent: Event
// The event at the origin of this event.
};
=====*/
/*=====
var __ItemMouseEventArgs = {
// summary:
// The event dispatched when an item is clicked, double-clicked or context-clicked.
// item: Object
// The item clicked.
// renderer: dojox/calendar/_RendererMixin
// The item renderer clicked.
// triggerEvent: Event
// The event at the origin of this event.
};
=====*/
/*=====
var __itemEditingEventArgs = {
// summary:
// An item editing event.
// item: Object
// The render item that is being edited. Set/get the startTime and/or endTime properties to customize editing behavior.
// storeItem: Object
// The real data from the store. DO NOT change properties, but you may use properties of this item in the editing behavior logic.
// editKind: String
// Kind of edit: "resizeBoth", "resizeStart", "resizeEnd" or "move".
// dates: Date[]
// The computed date/time of the during the event editing. One entry per edited date (touch use case).
// startTime: Date?
// The start time of data item.
// endTime: Date?
// The end time of data item.
// sheet: String
// For views with several sheets (columns view for example), the sheet when the event occurred.
// source: dojox/calendar/ViewBase
// The view where the event occurred.
// eventSource: String
// The device that triggered the event. This property can take the following values:
//
// - "mouse",
// - "keyboard",
// - "touch"
// triggerEvent: Event
// The event at the origin of this event.
};
=====*/
/*=====
var __rendererLifecycleEventArgs = {
// summary:
// An renderer lifecycle event.
// renderer: Object
// The renderer.
// source: dojox/calendar/ViewBase
// The view where the event occurred.
// item:Object?
// The item that will be displayed by the renderer for the "rendererCreated" and "rendererReused" events.
};
=====*/
return declare("dojox.calendar.ViewBase", [_WidgetBase, StoreMixin, _Invalidating, Selection], {
// summary:
// The dojox.calendar.ViewBase widget is the base of calendar view widgets
// datePackage: Object
// JavaScript namespace to find Calendar routines. Uses Gregorian Calendar routines at dojo.date by default.
datePackage: date,
_calendar: "gregorian",
// viewKind: String
// Kind of the view. Used by the calendar widget to determine how to configure the view.
viewKind: null,
// _layoutStep: [protected] Integer
// The number of units displayed by a visual layout unit (i.e. a column or a row)
_layoutStep: 1,
// _layoutStep: [protected] Integer
// The unit displayed by a visual layout unit (i.e. a column or a row)
_layoutUnit: "day",
// resizeCursor: String
// CSS value to apply to the cursor while resizing an item renderer.
resizeCursor: "n-resize",
// formatItemTimeFunc: Function
// Optional function to format the time of day of the item renderers.
// The function takes the date, the render data object, the view and the data item as arguments and returns a String.
formatItemTimeFunc: null,
_cssDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
_getFormatItemTimeFuncAttr: function(){
if(this.formatItemTimeFunc){
return this.formatItemTimeFunc;
}
if(this.owner != null){
return this.owner.get("formatItemTimeFunc");
}
},
// The listeners added by the view itself.
_viewHandles: null,
// doubleTapDelay: Integer
// The maximum time amount in milliseconds between to touchstart events that trigger a double-tap event.
doubleTapDelay: 300,
constructor: function(/*Object*/ args){
args = args || {};
this._calendar = args.datePackage ? args.datePackage.substr(args.datePackage.lastIndexOf(".")+1) : this._calendar;
this.dateModule = args.datePackage ? lang.getObject(args.datePackage, false) : date;
this.dateClassObj = this.dateModule.Date || Date;
this.dateLocaleModule = args.datePackage ? lang.getObject(args.datePackage+".locale", false) : locale;
this._viewHandles = [];
this.storeManager = new StoreManager({owner: this, _ownerItemsProperty: "items"});
this.storeManager.on("layoutInvalidated", lang.hitch(this, this._refreshItemsRendering));
this.storeManager.on("dataLoaded", lang.hitch(this, function(items){
this.set("items", items);
}));
this.storeManager.on("renderersInvalidated", lang.hitch(this, function(item){
this.updateRenderers(item);
}));
this.rendererManager = new RendererManager({owner: this});
this.rendererManager.on("rendererCreated", lang.hitch(this, this._onRendererCreated));
this.rendererManager.on("rendererReused", lang.hitch(this, this._onRendererReused));
this.rendererManager.on("rendererRecycled", lang.hitch(this, this._onRendererRecycled));
this.rendererManager.on("rendererDestroyed", lang.hitch(this, this._onRendererDestroyed));
this.decorationStoreManager = new StoreManager({owner: this, _ownerItemsProperty: "decorationItems"});
this.decorationStoreManager.on("layoutInvalidated", lang.hitch(this, this._refreshDecorationItemsRendering));
this.decorationStoreManager.on("dataLoaded", lang.hitch(this, function(items){
this.set("decorationItems", items);
}));
this.decorationRendererManager = new RendererManager({owner: this});
this._setupDayRefresh();
},
destroy: function(preserveDom){
this.rendererManager.destroy();
this.decorationRendererManager.destroy();
while(this._viewHandles.length > 0){
this._viewHandles.pop().remove();
}
this.inherited(arguments);
},
_setupDayRefresh: function(){
// Refresh the view when the current day changes.
var now = this.newDate(new Date());
var d = timeUtil.floor(now, "day", 1, this.dateClassObj);
var d = this.dateModule.add(d, "day", 1);
// manages DST at 24h
if(d.getHours() == 23){
d = this.dateModule.add(d, "hour", 2); // go to 1am
}else{
d = timeUtil.floorToDay(d, true, this.dateClassObj);
}
setTimeout(lang.hitch(this, function(){
if(!this._isEditing){
this.refreshRendering(true); // recursive refresh
}
this._setupDayRefresh();
}), d.getTime()-now.getTime() + 5000);
// add 5 seconds to be sure to be tomorrow
},
resize: function(changeSize){
// summary:
// Function to call when the view is resized.
// If the view is in a Dijit container or in a Dojo mobile container, it will be automatically called.
// On other use cases, this method must called when the window is resized and/or when the orientation has changed.
if(changeSize){
domGeometry.setMarginBox(this.domNode, changeSize);
}
},
// view lifecycle methods
beforeActivate: function(){
// summary:
// Function invoked just before the view is displayed by the calendar.
// tags:
// protected
},
afterActivate: function(){
// summary:
// Function invoked just after the view is displayed by the calendar.
// tags:
// protected
},
beforeDeactivate: function(){
// summary:
// Function invoked just before the view is hidden or removed by the calendar.
// tags:
// protected
},
afterDeactivate: function(){
// summary:
// Function invoked just after the view is the view is hidden or removed by the calendar.
// tags:
// protected
},
_getTopOwner: function(){
// summary:
// Returns the top owner: the calendar or the parent view.
var p = this;
while(p.owner != undefined){
p = p.owner;
}
return p;
},
_createRenderData: function(){
// summary:
// Creates the object that contains all the data needed to render this widget.
// tags:
// protected
},
_validateProperties: function(){
// summary:
// Validates the widget properties before the rendering pass.
// tags:
// protected
},
_setText: function(node, text, allowHTML){
// summary:
// Creates a text node under the parent node after having removed children nodes if any.
// node: Node
// The node that will contain the text node.
// text: String
// The text to set to the text node.
if(text != null){
if(!allowHTML && node.hasChildNodes()){
// span > textNode
node.childNodes[0].childNodes[0].nodeValue = text;
}else{
while(node.hasChildNodes()){
node.removeChild(node.lastChild);
}
var tNode = win.doc.createElement("span");
if(has("dojo-bidi")){
this.applyTextDir(tNode, text);
}
if(allowHTML){
tNode.innerHTML = text;
}else{
tNode.appendChild(win.doc.createTextNode(text));
}
node.appendChild(tNode);
}
}
},
isAscendantHasClass: function(node, ancestor, className){
// summary:
// Determines if a node has an ascendant node that has the css class specified.
// node: Node
// The DOM node.
// ancestor: Node
// The ancestor node used to limit the search in hierarchy.
// className: String
// The css class name.
// returns: Boolean
while(node != ancestor && node != document){
if(domClass.contains(node, className)){
return true;
}
node = node.parentNode;
}
return false;
},
isWeekEnd: function(date){
// summary:
// Determines whether the specified date is a week-end.
// This method is using dojo.date.locale.isWeekend() method as
// dojox.date.XXXX calendars are not supporting this method.
// date: Date
// The date to test.
return locale.isWeekend(date);
},
getWeekNumberLabel: function(date){
// summary:
// Returns the week number string from dojo.date.locale.format() method as
// dojox.date.XXXX calendar are not supporting the "w" pattern.
// date: Date
// The date to format.
if(date.toGregorian){
date = date.toGregorian();
}
return locale.format(date, {
selector: "date",
datePattern: "w"});
},
addAndFloor: function(date, unit, steps){
// date must be floored!!
// unit >= day
var d = this.dateModule.add(date, unit, steps);
if(d.getHours() == 23){
d = this.dateModule.add(d, "hour", 2); // go to 1am
}else{
d = timeUtil.floorToDay(d, true, this.dateClassObj);
}
return d;
},
floorToDay: function(date, reuse){
// summary:
// Floors the specified date to the start of day.
// date: Date
// The date to floor.
// reuse: Boolean
// Whether use the specified instance or create a new one. Default is false.
// returns: Date
return timeUtil.floorToDay(date, reuse, this.dateClassObj);
},
floorToMonth: function(date, reuse){
// summary:
// Floors the specified date to the start of the date's month.
// date: Date
// The date to floor.
// reuse: Boolean
// Whether use the specified instance or create a new one. Default is false.
// returns: Date
return timeUtil.floorToMonth(date, reuse, this.dateClassObj);
},
floorDate: function(date, unit, steps, reuse){
// summary:
// floors the date to the unit.
// date: Date
// The date/time to floor.
// unit: String
// The unit. Valid values are "minute", "hour", "day".
// steps: Integer
// For "day" only 1 is valid.
// reuse: Boolean
// Whether use the specified instance or create a new one. Default is false.
// returns: Date
return timeUtil.floor(date, unit, steps, reuse, this.dateClassObj);
},
isToday: function(date){
// summary:
// Returns whether the specified date is in the current day.
// date: Date
// The date to test.
// renderData: Object
// The current renderData
// returns: Boolean
return timeUtil.isToday(date, this.dateClassObj);
},
isStartOfDay: function(d){
// summary:
// Tests if the specified date represents the starts of day.
// d:Date
// The date to test.
// returns: Boolean
return timeUtil.isStartOfDay(d, this.dateClassObj, this.dateModule);
},
isOverlapping: function(renderData, start1, end1, start2, end2, includeLimits){
// summary:
// Computes if the first time range defined by the start1 and end1 parameters
// is overlapping the second time range defined by the start2 and end2 parameters.
// renderData: Object
// The render data.
// start1: Date
// The start time of the first time range.
// end1: Date
// The end time of the first time range.
// start2: Date
// The start time of the second time range.
// end2: Date
// The end time of the second time range.
// includeLimits: Boolean
// Whether include the end time or not.
// returns: Boolean
return timeUtil.isOverlapping(renderData, start1, end1, start2, end2, includeLimits);
},
computeRangeOverlap: function(renderData, start1, end1, start2, end2, includeLimits){
// summary:
// Computes the overlap time range of the time ranges.
// Returns a vector of Date with at index 0 the start time and at index 1 the end time.
// renderData: Object.
// The render data.
// start1: Date
// The start time of the first time range.
// end1: Date
// The end time of the first time range.
// start2: Date
// The start time of the second time range.
// end2: Date
// The end time of the second time range.
// includeLimits: Boolean
// Whether include the end time or not.
// returns: Date[]
var cal = renderData.dateModule;
if(start1 == null || start2 == null || end1 == null || end2 == null){
return null;
}
var comp1 = cal.compare(start1, end2);
var comp2 = cal.compare(start2, end1);
if(includeLimits){
if(comp1 == 0 || comp1 == 1 || comp2 == 0 || comp2 == 1){
return null;
}
} else if(comp1 == 1 || comp2 == 1){
return null;
}
return [
this.newDate(cal.compare(start1, start2)>0 ? start1: start2, renderData),
this.newDate(cal.compare(end1, end2)>0 ? end2: end1, renderData)
];
},
isSameDay : function(date1, date2){
// summary:
// Tests if the specified dates are in the same day.
// date1: Date
// The first date.
// date2: Date
// The second date.
// returns: Boolean
if(date1 == null || date2 == null){
return false;
}
return date1.getFullYear() == date2.getFullYear() &&
date1.getMonth() == date2.getMonth() &&
date1.getDate() == date2.getDate();
},
computeProjectionOnDate: function(renderData, refDate, date, max){
// summary:
// Computes the time to pixel projection in a day.
// renderData: Object
// The render data.
// refDate: Date
// The reference date that defines the destination date.
// date: Date
// The date to project.
// max: Integer
// The size in pixels of the representation of a day.
// tags:
// protected
// returns: Number
var cal = renderData.dateModule;
var minH = renderData.minHours;
var maxH = renderData.maxHours;
if(max <= 0 || cal.compare(date, refDate) == -1){
return 0;
}
var gt = function(d){
return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
};
var referenceDate = this.floorToDay(refDate, false, renderData);
if(date.getDate() != referenceDate.getDate()){
if(date.getMonth() == referenceDate.getMonth()){
if(date.getDate() < referenceDate.getDate()){
return 0;
} else if(date.getDate() > referenceDate.getDate() && maxH < 24){
return max;
}
}else{
if(date.getFullYear() == referenceDate.getFullYear()){
if(date.getMonth() < referenceDate.getMonth()){
return 0;
} else if(date.getMonth() > referenceDate.getMonth()){
return max;
}
}else{
if(date.getFullYear() < referenceDate.getFullYear()){
return 0;
} else if(date.getFullYear() > referenceDate.getFullYear()){
return max;
}
}
}
}
var res;
var ONE_DAY = 86400; // 24h x 60m x 60s
if(this.isSameDay(refDate, date) || maxH > 24){
var d = lang.clone(refDate);
var minTime = 0;
if(minH != null && minH != 0){
d.setHours(minH);
minTime = gt(d);
}
d = lang.clone(refDate);
d.setHours(maxH);
var maxTime;
if(maxH == null || maxH == 24){
maxTime = ONE_DAY;
}else if(maxH > 24){
maxTime = ONE_DAY + gt(d);
}else{
maxTime = gt(d);
}
//precision is the second
//use this API for daylight time issues.
var delta = 0;
if(maxH > 24 && refDate.getDate() != date.getDate()){
delta = ONE_DAY + gt(date);
}else{
delta = gt(date);
}
if(delta < minTime){
return 0;
}
if(delta > maxTime){
return max;
}
delta -= minTime;
res = (max * delta)/(maxTime - minTime);
}else{
if(date.getDate() < refDate.getDate() &&
date.getMonth() == refDate.getMonth()){
return 0;
}
var d2 = this.floorToDay(date);
var dp1 = renderData.dateModule.add(refDate, "day", 1);
dp1 = this.floorToDay(dp1, false, renderData);
if(cal.compare(d2, refDate) == 1 && cal.compare(d2, dp1) == 0 || cal.compare(d2, dp1) == 1){
res = max;
}else{
res = 0;
}
}
return res;
},
getTime: function(e, x, y, touchIndex){
// summary:
// Returns the time displayed at the specified point by this component.
// e: Event
// Optional mouse event.
// x: Number
// Position along the x-axis with respect to the sheet container used if event is not defined.
// y: Number
// Position along the y-axis with respect to the sheet container (scroll included) used if event is not defined.
// touchIndex: Integer
// If parameter 'e' is not null and a touch event, the index of the touch to use.
// returns: Date
return null;
},
getSubColumn: function(e, x, y, touchIndex){
// summary:
// Returns the sub column at the specified point by this component.
// e: Event
// Optional mouse event.
// x: Number
// Position along the x-axis with respect to the sheet container used if event is not defined.
// y: Number
// Position along the y-axis with respect to the sheet container (scroll included) used if event is not defined.
// touchIndex: Integer
// If parameter 'e' is not null and a touch event, the index of the touch to use.
// returns: Object
return null;
},
getSubColumnIndex: function(value){
// summary:
// Returns the sub column index that has the specified value, if any. -1 otherwise.
// value: String
// The sub column index.
if(this.subColumns){
for(var i=0; i<this.subColumns.length; i++){
if(this.subColumns[i] == value){
return i;
}
}
}
return -1;
},
newDate: function(obj){
// summary:
// Creates a new Date object.
// obj: Object
// This object can have several values:
//
// - the time in milliseconds since gregorian epoch.
// - a Date instance
// returns: Date
return timeUtil.newDate(obj, this.dateClassObj);
},
_isItemInView: function(item){
// summary:
// Computes whether the specified item is entirely in the view or not.
// item: Object
// The item to test
// returns: Boolean
var rd = this.renderData;
var cal = rd.dateModule;
if(cal.compare(item.startTime, rd.startTime) == -1){
return false;
}
return cal.compare(item.endTime, rd.endTime) != 1;
},
_ensureItemInView: function(item){
// summary:
// If needed, moves the item to be entirely in view.
// item: Object
// The item to test
// returns: Boolean
// Whether the item has been moved to be in view or not.
// tags:
// protected
var rd = this.renderData;
var cal = rd.dateModule;
var duration = Math.abs(cal.difference(item.startTime, item.endTime, "millisecond"));
var fixed = false;
if(cal.compare(item.startTime, rd.startTime) == -1){
item.startTime = rd.startTime;
item.endTime = cal.add(item.startTime, "millisecond", duration);
fixed = true;
}else if(cal.compare(item.endTime, rd.endTime) == 1){
item.endTime = rd.endTime;
item.startTime = cal.add(item.endTime, "millisecond", -duration);
fixed = true;
}
return fixed;
},
/////////////////////////////////////////////////////////
//
// Scrollable
//
/////////////////////////////////////////////////////////
// scrollable: Boolean
// Indicates whether the view can be scrolled or not.
scrollable: true,
// autoScroll: Boolean
// Indicates whether the view can be scrolled automatically.
// Auto scrolling is used when moving focus to a non visible renderer using keyboard
// and while editing an item.
autoScroll: true,
_autoScroll: function(gx, gy, orientation){
// summary:
// Starts or stops the auto scroll according to the mouse cursor position during an item editing.
// gx: Integer
// The position of the mouse cursor along the x-axis.
// gy: Integer
// The position of the mouse cursor along the y-axis.
// tags:
// extension
return false;
},
// scrollMethod: String
// Method used to scroll the view, for example the scroll of column view.
// Valid value are:
//
// - "auto": let the view decide (default),
// - "css": use css 3d transform,
// - "dom": use the scrollTop property.
scrollMethod: "auto",
_setScrollMethodAttr: function(value){
if(this.scrollMethod != value){
this.scrollMethod = value;
// reset
if(this._domScroll !== undefined){
if(this._domScroll){
domStyle.set(this.sheetContainer, this._cssPrefix+"transform", "translateY(0px)");
}else{
this.scrollContainer.scrollTop = 0;
}
}
delete this._domScroll;
var pos = this._getScrollPosition();
delete this._scrollPos;
this._setScrollPosition(pos);
}
},
_startAutoScroll: function(step){
// summary:
// Starts the auto scroll of the view (if it's scrollable). Used only during editing.
// tags:
// protected
var sp = this._scrollProps;
if(!sp){
sp = this._scrollProps = {};
}
sp.scrollStep = step;
if (!sp.isScrolling){
sp.isScrolling = true;
sp.scrollTimer = setInterval(lang.hitch(this, this._onScrollTimer_tick), 10);
}
},
_stopAutoScroll: function(){
// summary:
// Stops the auto scroll of the view (if it's scrollable). Used only during editing.
// tags:
// protected
var sp = this._scrollProps;
if (sp && sp.isScrolling) {
clearInterval(sp.scrollTimer);
sp.scrollTimer = null;
}
this._scrollProps = null;
},
_onScrollTimer_tick: function(pos){
},
_scrollPos: 0,
_hscrollPos: 0,
getCSSPrefix: function(){
// summary:
// Utility method that return the specific CSS prefix
// for non standard CSS properties. Ex: -moz-border-radius.
if(has("ie")){
return "-ms-";
}
if(has("webkit")){
return "-webkit-";
}
if(has("mozilla")){
return "-moz-";
}
if(has("opera")){
return "-o-";
}
return "";
},
// _hScrollNodes: DOMNodes[]
// Array of nodes that will be scrolled horizontally.
// Must be set by sub class on buildRendering.
_hScrollNodes: null,
_setScrollPositionBase: function(pos, vertical){
// summary:
// Sets the scroll position (if the view is scrollable), using the scroll method defined.
// tags:
// protected
if(vertical && this._scrollPos == pos ||
!vertical && this._hScrollPos == pos){
return;
}
// determine scroll method once.
if(this._domScroll === undefined){
var sm = this.get("scrollMethod");
if(sm === "auto"){
this._domScroll = !has("ios") && !has("android") && !has("webkit");
}else{
this._domScroll = sm === "dom";
}
}
var max = 0;
if(vertical){
var containerSize = domGeometry.getMarginBox(this.scrollContainer);
var sheetSize = domGeometry.getMarginBox(this.sheetContainer);
max = sheetSize.h - containerSize.h;
}else{
var gridSize = domGeometry.getMarginBox(this.grid);
var gridTableSize = domGeometry.getMarginBox(this.gridTable);
max = gridTableSize.w - gridSize.w;
}
if(pos < 0){
pos = 0;
}else if(pos > max){
pos = max;
}
if(vertical){
this._scrollPos = pos;
}else{
this._hScrollPos = pos;
}
var rtl = !this.isLeftToRight();
if(this._domScroll){
if(vertical){
this.scrollContainer.scrollTop = pos;
}else{
arr.forEach(this._hScrollNodes, function(elt){
domStyle.set(elt, "left", ((rtl?1:-1) * pos) + "px");
}, this);
}
}else{
if(!this._cssPrefix){
this._cssPrefix = this.getCSSPrefix();
}
var cssProp = this._cssPrefix+"transform";
if(vertical){
domStyle.set(this.sheetContainer, cssProp, "translateY(-"+pos+"px)");
}else{
var css = "translateX("+(rtl?"":"-")+pos+"px)";
arr.forEach(this._hScrollNodes, function(elt){
domStyle.set(elt, cssProp, css);
}, this);
}
}
},
_setScrollPosition: function(pos){
// summary:
// Sets the verical scroll position (if the view is scrollable), using the scroll method defined.
// tags:
// protected
this._setScrollPositionBase(pos, true);
},
_getScrollPosition: function(){
// summary:
// Returns the vertical scroll position (if the view is scrollable), using the scroll method defined.
// tags:
// protected
return this._scrollPos;
},
_setHScrollPosition: function(pos){
// summary:
// Sets the horizontal scroll position (if the view is scrollable), using the scroll method defined.
// tags:
// protected
this._setScrollPositionBase(pos, false);
},
_setHScrollPositionImpl: function(pos, useDom, cssProperty){
// summary:
// Sets the horizontal scroll position on sub elements (if the view is scrollable), using the scroll method defined.
// Important: must be implemented by sub classes and not called directly. Use _setHScrollPosition() method instead.
// tags:
// private
var css = useDom ? null : "translateX(-"+pos+"px)";
arr.forEach(this._hScrollNodes, function(elt){
if(useDom){
elt.scrollLeft = pos;
domStyle.set(elt, "left", (-pos) + "px");
}else{
domStyle.set(elt, cssProp, css);
}
}, this);
},
_hScrollPos: 0,
_getHScrollPosition: function(){
// summary:
// Returns the horizontal scroll position (if the view is scrollable), using the scroll method defined.
// tags:
// protected
return this._hScrollPos;
},
scrollView: function(dir){
// summary:
// If the view is scrollable, scrolls it vertically to the specified direction.
// dir: Integer
// Direction of the scroll. Valid values are -1 and 1.
// tags:
// extension
},
ensureVisibility: function(start, end, margin, visibilityTarget, duration){
// summary:
// Scrolls the view if the [start, end] time range is not visible or only partially visible.
// start: Date
// Start time of the range of interest.
// end: Date
// End time of the range of interest.
// margin: int
// Margin in minutes around the time range.
// visibilityTarget: String
// The end(s) of the time range to make visible.
// Valid values are: "start", "end", "both".
// duration: Number
// Optional, the maximum duration of the scroll animation.
// tags:
// extension
},
////////////////////////////////////////////////////////
//
// Store & Items
//
////////////////////////////////////////////////////////
_getStoreAttr: function(){
if(this.owner){
return this.owner.get("store");
}
return this.store;
},
_setItemsAttr: function(value){
this._set("items", value);
this.displayedItemsInvalidated = true;
},
_refreshItemsRendering: function(){
var rd = this.renderData;
this._computeVisibleItems(rd);
this._layoutRenderers(rd);
},
_refreshDecorationItemsRendering: function(){
var rd = this.renderData;
this._computeVisibleItems(rd);
this._layoutDecorationRenderers(rd);
},
invalidateLayout: function(){
// summary:
// Triggers a re-layout of the renderers.
this._layoutRenderers(this.renderData);
this._layoutDecorationRenderers(this.renderData);
},
_setDecorationItemsAttr: function(value){
this._set("decorationItems", value);
this.displayedDecorationItemsInvalidated = true;
},
_getDecorationStoreAttr: function(){
if(this.owner){
return this.owner.get("decorationStore");
}
return this.decorationStore;
},
_setDecorationStoreAttr: function(value){
this.decorationStore = value;
this.decorationStoreManager.set("store", value);
},
////////////////////////////////////////////////////////
//
// Layout
//
////////////////////////////////////////////////////////
computeOverlapping: function(layoutItems, func){
// summary:
// Computes the overlap layout of a list of items. A lane and extent properties are added to each layout item.
// layoutItems: Object[]
// List of layout items, each item must have a start and end properties.
// addedPass: Function
// Whether computes the extent of each item renderer on free sibling lanes.
// returns: Object
// tags:
// protected
if(layoutItems.length == 0){
return {
numLanes: 0,
addedPassRes: [1]
};
}
var lanes = [];
for(var i=0; i<layoutItems.length; i++){
var layoutItem = layoutItems[i];
this._layoutPass1(layoutItem, lanes);
}
var addedPassRes = null;
if(func){
addedPassRes = lang.hitch(this, func)(lanes);
}
return {
numLanes: lanes.length,
addedPassRes: addedPassRes
};
},
_layoutPass1: function (layoutItem, lanes){
// summary:
// First pass of the overlap layout. Find a lane where the item can be placed or create a new one.
// layoutItem: Object
// An object that contains a start and end properties at least.
// lanes:
// The array of lanes.
// tags:
// protected
var stop = true;
for(var i=0; i<lanes.length; i++){
var lane = lanes[i];
stop = false;
for(var j=0; j<lane.length && !stop; j++){
if(lane[j].start < layoutItem.end && layoutItem.start < lane[j].end){
// one already placed item is overlapping
stop = true;
lane[j].extent = 1;
}
}
if(!stop){
//we have found a place
layoutItem.lane = i;
layoutItem.extent = -1;
lane.push(layoutItem);
return;
}
}
//no place found -> add a lane
lanes.push([layoutItem]);
layoutItem.lane = lanes.length-1;
layoutItem.extent = -1;
},
_layoutInterval: function(renderData, index, start, end, items){
// summary:
// For each item in the items list: retrieve a renderer, compute its location and size and add it to the DOM.
// renderData: Object
// The render data.
// index: Integer
// The index of the interval.
// start: Date
// The start time of the displayed date interval.
// end: Date
// The end time of the displayed date interval.
// items: Object[]
// The list of the items to represent.
// tags:
// extension
},
// layoutPriorityFunction: Function
// An optional comparison function use to determine the order the item will be laid out
// The function is used to sort an array and must, as any sorting function, take two items
// as argument and must return an integer whose sign define order between arguments.
// By default, a comparison by start time then end time is used.
layoutPriorityFunction: null,
_sortItemsFunction: function(a, b){
var res = this.dateModule.compare(a.startTime, b.startTime);
if(res == 0){
res = -1 * this.dateModule.compare(a.endTime, b.endTime);
}
return res;
},
_layoutRenderers: function(renderData){
this._layoutRenderersImpl(renderData, this.rendererManager, renderData.items, "dataItems");
},
_layoutDecorationRenderers: function(renderData){
this._layoutRenderersImpl(renderData, this.decorationRendererManager, renderData.decorationItems, "decorationItems");
},
_layoutRenderersImpl: function(renderData, rendererManager, items, itemType){
// summary:
// Renders the data items. This method will call the _layoutInterval() method.
// renderData: Object
// The render data.
// tags:
// protected
if(!items){
return;
}
// recycle renderers first
rendererManager.recycleItemRenderers();
var cal = renderData.dateModule;
// Date
var startDate = this.newDate(renderData.startTime);
// Date and time
var startTime = lang.clone(startDate);
var endDate;
var items = items.concat();
var itemsTemp = [], events;
var processing = {};
var index = 0;
while(cal.compare(startDate, renderData.endTime) == -1 && items.length > 0){
endDate = this.addAndFloor(startDate, this._layoutUnit, this._layoutStep);
var endTime = lang.clone(endDate);
if(renderData.minHours){
startTime.setHours(renderData.minHours);
}
if(renderData.maxHours != undefined && renderData.maxHours != 24){
if(renderData.maxHours < 24){
endTime = cal.add(endDate, "day", -1);
} // else > 24
endTime = this.floorToDay(endTime, true, renderData);
endTime.setHours(renderData.maxHours - (renderData.maxHours < 24 ? 0 : 24));
}
// look for events that overlap the current sub interval
events = arr.filter(items, function(item){
var r = this.isOverlapping(renderData, item.startTime, item.endTime, startTime, endTime);
if(r){
processing[item.id] = true;
itemsTemp.push(item);
}else{
if(processing[item.id]){
delete processing[item.id];
}else{
itemsTemp.push(item);
}
}
return r;
}, this);
items = itemsTemp;
itemsTemp = [];
// if event are in the current sub interval, layout them
if(events.length > 0){
// Sort the item according a sorting function, by default start time then end time comparison are used.
events.sort(lang.hitch(this, this.layoutPriorityFunction ? this.layoutPriorityFunction : this._sortItemsFunction));
this._layoutInterval(renderData, index, startTime, endTime, events, itemType);
}
startDate = endDate;
startTime = lang.clone(startDate);
index++;
}
this._onRenderersLayoutDone(this);
},
/////////////////////////////////////////////////////////////////
//
// Renderers management
//
////////////////////////////////////////////////////////////////
_recycleItemRenderers: function(remove){
this.rendererManager.recycleItemRenderers(remove);
},
getRenderers: function(item){
// summary:
// Returns the renderers that are currently used to displayed the speficied item.
// Returns an array of objects that contains two properties:
// - container: The DOM node that contains the renderer.
// - renderer: The dojox.calendar._RendererMixin instance.
// Do not keep references on the renderers are they are recycled and reused for other items.
// item: Object
// The data or render item.
// returns: Object[]
return this.rendererManager.getRenderers(item);
},
// itemToRendererKindFunc: Function
// An optional function to associate a kind of renderer ("horizontal", "label" or null) with the specified item.
// By default, if an item is lasting more that 24 hours an horizontal item is used, otherwise a label is used.
itemToRendererKindFunc: null,
_itemToRendererKind: function(item){
// summary:
// Associates a kind of renderer with a data item.
// item: Object
// The data item.
// returns: String
// tags:
// protected
if(this.itemToRendererKindFunc){
return this.itemToRendererKindFunc(item);
}
return this._defaultItemToRendererKindFunc(item); // String
},
_defaultItemToRendererKindFunc:function(item){
// tags:
// private
return null;
},
_createRenderer: function(item, kind, rendererClass, cssClass){
// summary:
// Creates an item renderer of the specified kind. A renderer is an object with the "container" and "instance" properties.
// item: Object
// The data item.
// kind: String
// The kind of renderer.
// rendererClass: Object
// The class to instantiate to create the renderer.
// returns: Object
// tags:
// protected
return this.rendererManager.createRenderer(item, kind, rendererClass, cssClass);
},
_onRendererCreated: function(e){
if(e.source == this){
this.onRendererCreated(e);
}
if(this.owner != null){
this.owner._onRendererCreated(e);
}
},
onRendererCreated: function(e){
// summary:
// Event dispatched when an item renderer has been created.
// e: __rendererLifecycleEventArgs
// The renderer lifecycle event.
// tags:
// callback
},
_onRendererRecycled: function(e){
if(e.source == this){
this.onRendererRecycled(e);
}
if(this.owner != null){
this.owner._onRendererRecycled(e);
}
},
onRendererRecycled: function(e){
// summary:
// Event dispatched when an item renderer has been recycled.
// e: __rendererLifecycleEventArgs
// The renderer lifecycle event.
// tags:
// callback
},
_onRendererReused: function(e){
if(e.source == this){
this.onRendererReused(e);
}
if(this.owner != null){
this.owner._onRendererReused(e);
}
},
onRendererReused: function(e){
// summary:
// Event dispatched when an item renderer that was recycled is reused.
// e: __rendererLifecycleEventArgs
// The renderer lifecycle event.
// tags:
// callback
},
_onRendererDestroyed: function(e){
if(e.source == this){
this.onRendererDestroyed(e);
}
if(this.owner != null){
this.owner._onRendererDestroyed(e);
}
},
onRendererDestroyed: function(e){
// summary:
// Event dispatched when an item renderer is destroyed.
// e: __rendererLifecycleEventArgs
// The renderer lifecycle event.
// tags:
// callback
},
_onRenderersLayoutDone: function(view){
// tags:
// private
this.onRenderersLayoutDone(view);
if(this.owner != null){
this.owner._onRenderersLayoutDone(view);
}
},
onRenderersLayoutDone: function(view){
// summary:
// Event triggered when item renderers layout has been done.
// tags:
// callback
},
_recycleRenderer: function(renderer, remove){
// summary:
// Recycles the item renderer to be reused in the future.
// renderer: dojox/calendar/_RendererMixin
// The item renderer to recycle.
// tags:
// protected
this.rendererManager.recycleRenderer(renderer, remove);
},
_destroyRenderer: function(renderer){
// summary:
// Destroys the item renderer.
// renderer: dojox/calendar/_RendererMixin
// The item renderer to destroy.
// tags:
// protected
this.rendererManager.destroyRenderer(renderer);
},
_destroyRenderersByKind: function(kind){
// tags:
// private
this.rendererManager.destroyRenderersByKind(kind);
},
_updateEditingCapabilities: function(item, renderer){
// summary:
// Update the moveEnabled and resizeEnabled properties of a renderer according to its event current editing state.
// item: Object
// The store data item.
// renderer: dojox/calendar/_RendererMixin
// The item renderer.
// tags:
// protected
var moveEnabled = this.isItemMoveEnabled(item, renderer.rendererKind);
var resizeEnabled = this.isItemResizeEnabled(item, renderer.rendererKind);
var changed = false;
if(moveEnabled != renderer.get("moveEnabled")){
renderer.set("moveEnabled", moveEnabled);
changed = true;
}
if(resizeEnabled != renderer.get("resizeEnabled")){
renderer.set("resizeEnabled", resizeEnabled);
changed = true;
}
if(changed){
renderer.updateRendering();
}
},
updateRenderers: function(obj, stateOnly){
// summary:
// Updates all the renderers that represents the specified item(s).
// obj: Object
// A render item or an array of render items.
// stateOnly: Boolean
// Whether only the state of the item has changed (selected, edited, edited, focused) or a more global change has occured.
// tags:
// protected
if(obj == null){
return;
}
var items = lang.isArray(obj) ? obj : [obj];
for(var i=0; i<items.length; i++){
var item = items[i];
if(item == null || item.id == null){
continue;
}
var list = this.rendererManager.itemToRenderer[item.id];
if(list == null){
continue;
}
var selected = this.isItemSelected(item);
var hovered = this.isItemHovered(item);
var edited = this.isItemBeingEdited(item);
var focused = this.showFocus ? this.isItemFocused(item) : false;
for(var j = 0; j < list.length; j++){
var renderer = list[j].renderer;
renderer.set("hovered", hovered);
renderer.set("selected", selected);
renderer.set("edited", edited);
renderer.set("focused", focused);
renderer.set("storeState", this.getItemStoreState(item));
this.applyRendererZIndex(item, list[j], hovered, selected, edited, focused);
if(!stateOnly){
renderer.set("item", item); // force content refresh
if(renderer.updateRendering){
renderer.updateRendering(); // reuse previously set dimensions
}
}
}
}
},
applyRendererZIndex: function(item, renderer, hovered, selected, edited, focused){
// summary:
// Applies the z-index to the renderer based on the state of the item.
// This methods is setting a z-index of 20 is the item is selected or edited
// and the current lane value computed by the overlap layout (i.e. the renderers
// are stacked according to their lane).
// item: Object
// The render item.
// renderer: Object
// A renderer associated with the render item.
// hovered: Boolean
// Whether the item is hovered or not.
// selected: Boolean
// Whether the item is selected or not.
// edited: Boolean
// Whether the item is being edited not not.
// focused: Boolean
// Whether the item is focused not not.
// tags:
// protected
domStyle.set(renderer.container, {"zIndex": edited || selected ? 20: item.lane == undefined ? 0 : item.lane});
},
getIdentity: function(item){
return this.owner ? this.owner.getIdentity(item) : item.id;
},
/////////////////////////////////////////////////////
//
// Hovered item
//
////////////////////////////////////////////////////
_setHoveredItem: function(item, renderer){
// summary:
// Sets the current hovered item.
// item: Object
// The data item.
// renderer: dojox/calendar/_RendererMixin
// The item renderer.
// tags:
// protected
if(this.owner){
this.owner._setHoveredItem(item, renderer);
return;
}
if(this.hoveredItem && item && this.hoveredItem.id != item.id ||
item == null || this.hoveredItem == null){
var old = this.hoveredItem;
this.hoveredItem = item;
this.updateRenderers([old, this.hoveredItem], true);
if(item && renderer){
this._updateEditingCapabilities(item._item ? item._item : item, renderer);
}
}
},
// hoveredItem: Object
// The currently hovered data item.
hoveredItem: null,
isItemHovered: function(item){
// summary:
// Returns whether the specified item is hovered or not.
// item: Object
// The item.
// returns: Boolean
if (this._isEditing && this._edProps){
return item.id == this._edProps.editedItem.id;
}
return this.owner ?
this.owner.isItemHovered(item) :
this.hoveredItem != null && this.hoveredItem.id == item.id;
},
isItemFocused: function(item){
// summary:
// Returns whether the specified item is focused or not.
// item: Object
// The item.
// returns: Boolean
return this._isItemFocused ? this._isItemFocused(item) : false;
},
////////////////////////////////////////////////////////////////////
//
// Selection delegation
//
///////////////////////////////////////////////////////////////////
_setSelectionModeAttr: function(value){
if(this.owner){
this.owner.set("selectionMode", value);
}else{
this.inherited(arguments);
}
},
_getSelectionModeAttr: function(value){
if(this.owner){
return this.owner.get("selectionMode");
}
return this.inherited(arguments);
},
_setSelectedItemAttr: function(value){
if(this.owner){
this.owner.set("selectedItem", value);
}else{
this.inherited(arguments);
}
},
_getSelectedItemAttr: function(value){
if(this.owner){
return this.owner.get("selectedItem");
}
return this.selectedItem; // no getter on super class (dojox.widget.Selection)
},
_setSelectedItemsAttr: function(value){
if(this.owner){
this.owner.set("selectedItems", value);
}else{
this.inherited(arguments);
}
},
_getSelectedItemsAttr: function(){
if(this.owner){
return this.owner.get("selectedItems");
}
return this.inherited(arguments);
},
isItemSelected: function(item){
if(this.owner){
return this.owner.isItemSelected(item);
}
return this.inherited(arguments);
},
selectFromEvent: function(e, item, renderer, dispatch){
if(this.owner){
this.owner.selectFromEvent(e, item, renderer, dispatch);
}else{
this.inherited(arguments);
}
},
setItemSelected: function(item, value){
if(this.owner){
this.owner.setItemSelected(item, value);
}else{
this.inherited(arguments);
}
},
////////////////////////////////////////////////////////////////////
//
// Event creation
//
///////////////////////////////////////////////////////////////////
createItemFunc: null,
/*=====
createItemFunc: function(view, d, e){
// summary:
// A user supplied function that creates a new event.
// view: ViewBase
// the current view,
// d: Date
// the date at the clicked location.
// e: MouseEvemt
// the mouse event (can be used to return null for example)
// subColumn: Object
// the subcolumn at clicked location (can return null)
},
=====*/
_getCreateItemFuncAttr: function(){
if(this.owner){
return this.owner.get("createItemFunc");
}
return this.createItemFunc;
},
// createOnGridClick: Boolean
// Indicates whether the user can create new event by clicking and dragging the grid.
// A createItem function must be defined on the view or the calendar object.
createOnGridClick: false,
_getCreateOnGridClickAttr: function(){
if(this.owner){
return this.owner.get("createOnGridClick");
}
return this.createOnGridClick;
},
////////////////////////////////////////////////////////////////////
//
// Event creation
//
///////////////////////////////////////////////////////////////////
_gridMouseDown: false,
_tempIdCount: 0,
_tempItemsMap: null,
_onGridMouseDown: function(e){
// tags:
// private
this._gridMouseDown = true;
this.showFocus = false;
if(this._isEditing){
this._endItemEditing("mouse", false);
}
this._doEndItemEditing(this.owner, "mouse");
this.set("focusedItem", null);
this.selectFromEvent(e, null, null, true);
if(this._setTabIndexAttr){
this[this._setTabIndexAttr].focus();
}
if(this._onRendererHandleMouseDown){
var f = this.get("createItemFunc");
if(!f){
return;
}
var newItem = this._createdEvent = f(this, this.getTime(e), e, this.getSubColumn(e));
var store = this.get("store");
if(!newItem || store == null){
return;
}
// calendar needs an ID to work with
if(store.getIdentity(newItem) == undefined){
var id = "_tempId_" + (this._tempIdCount++);
newItem[store.idProperty] = id;
if(this._tempItemsMap == null){
this._tempItemsMap = {};
}
this._tempItemsMap[id] = true;
}
var newRenderItem = this.itemToRenderItem(newItem, store);
newRenderItem._item = newItem;
this._setItemStoreState(newItem, "unstored");
// add the new temporary item to the displayed list and force view refresh
var owner = this._getTopOwner();
var items = owner.get("items");
owner.set("items", items ? items.concat([newRenderItem]) : [newRenderItem]);
this._refreshItemsRendering();
// renderer created in _refreshItemsRenderering()
var renderers = this.getRenderers(newItem);
if(renderers && renderers.length>0){
var renderer = renderers[0];
if(renderer){
// trigger editing
this._onRendererHandleMouseDown(e, renderer.renderer, "resizeEnd");
this._startItemEditing(newRenderItem, "mouse");
}
}
}
},
_onGridMouseMove: function(e){
// tags:
// private
},
_onGridMouseUp: function(e){
// tags:
// private
},
_onGridTouchStart: function(e){
// tags:
// privat