define([
"dojo/_base/declare",
"dojo/_base/array",
"dojo/_base/event",
"dojo/_base/lang",
"dojo/_base/sniff",
"dojo/_base/fx",
"dojo/_base/html",
"dojo/on",
"dojo/dom",
"dojo/dom-class",
"dojo/dom-style",
"dojo/dom-geometry",
"dojo/dom-construct",
"dojo/query",
"dojox/html/metrics",
"dojo/i18n",
"./ViewBase",
"dojo/text!./templates/MatrixView.html",
"dijit/_TemplatedMixin"],
function(
declare,
arr,
event,
lang,
has,
fx,
html,
on,
dom,
domClass,
domStyle,
domGeometry,
domConstruct,
query,
metrics,
i18n,
ViewBase,
template,
_TemplatedMixin){
/*=====
var __HeaderClickEventArgs = {
// summary:
// A column click event.
// index: Integer
// The column index.
// date: Date
// The date displayed by the column.
// triggerEvent: Event
// The origin event.
};
=====*/
/*=====
var __ExpandRendererClickEventArgs = {
// summary:
// A expand renderer click event.
// columnIndex: Integer
// The column index of the cell.
// rowIndex: Integer
// The row index of the cell.
// date: Date
// The date displayed by the cell.
// triggerEvent: Event
// The origin event.
};
=====*/
return declare("dojox.calendar.MatrixView", [ViewBase, _TemplatedMixin], {
// summary:
// The matrix view is a calendar view that displaying a matrix where each cell is a day.
templateString: template,
baseClass: "dojoxCalendarMatrixView",
_setTabIndexAttr: "domNode",
// viewKind: String
// Type of the view. Used by the calendar widget to determine how to configure the view.
// This view kind is "matrix".
viewKind: "matrix",
// renderData: Object
// The render data object contains all the data needed to render the widget.
renderData: null,
// startDate: Date
// The start date of the time interval displayed.
// If not set at initialization time, will be set to current day.
startDate: null,
// refStartTime: Date?
// (Optional) Start of the time interval of interest.
// It is used to style differently the displayed rows out of the
// time interval of interest.
refStartTime: null,
// refStartTime: Date?
// (Optional) End of the time interval of interest.
// It is used to style differently the displayed rows out of the
// time interval of interest.
refEndTime: null,
// columnCount: Integer
// The number of column to display (from the startDate).
columnCount: 7,
// rowCount: Integer
// The number of rows to display (from the startDate).
rowCount: 5,
// horizontalRenderer: Class
// The class use to create horizontal renderers.
horizontalRenderer: null,
// labelRenderer: Class
// The class use to create label renderers.
labelRenderer: null,
// expandRenderer: Class
// The class use to create drill down renderers.
expandRenderer: null,
// percentOverlap: Integer
// The percentage of the renderer width used to superimpose one item renderers on another
// when two events are overlapping. By default 0.
percentOverlap: 0,
// verticalGap: Integer
// The number of pixels between two item renderers that are overlapping each other if the percentOverlap property is 0.
verticalGap: 2,
// horizontalRendererHeight: Integer
// The height in pixels of the horizontal and label renderers that is applied by the layout.
horizontalRendererHeight: 17,
// horizontalRendererHeight: Integer
// The height in pixels of the horizontal and label renderers that is applied by the layout.
labelRendererHeight: 14,
// expandRendererHeight: Integer
// The height in pixels of the expand/collapse renderers that is applied by the layout.
expandRendererHeight: 15,
// cellPaddingTop: Integer
// The top offset in pixels of each cell applied by the layout.
cellPaddingTop: 16,
// expandDuration: Integer
// Duration of the animation when expanding or collapsing a row.
expandDuration: 300,
// expandEasing: Function
// Easing function of the animation when expanding or collapsing a row (null by default).
expandEasing: null,
// layoutDuringResize: Boolean
// Indicates if the item renderers' position and size is updated or if they are hidden during a resize of the widget.
layoutDuringResize: false,
// roundToDay: Boolean
// For horizontal renderers that are not filling entire days, whether fill the day or not.
roundToDay: true,
// showCellLabel: Boolean
// Whether display or not the grid cells label (usually the day of month).
showCellLabel: true,
// scrollable: [private] Boolean
scrollable: false,
// resizeCursor: [private] Boolean
resizeCursor: "e-resize",
constructor: function(){
this.invalidatingProperties = ["columnCount", "rowCount", "startDate", "horizontalRenderer", "labelRenderer", "expandRenderer",
"rowHeaderDatePattern", "columnHeaderLabelLength", "cellHeaderShortPattern", "cellHeaderLongPattern", "percentOverlap",
"verticalGap", "horizontalRendererHeight", "labelRendererHeight", "expandRendererHeight", "cellPaddingTop",
"roundToDay", "itemToRendererKindFunc", "layoutPriorityFunction", "formatItemTimeFunc", "textDir", "items"];
this._ddRendererList = [];
this._ddRendererPool = [];
this._rowHeaderHandles = [];
},
destroy: function(preserveDom){
this._cleanupRowHeader();
this.inherited(arguments);
},
postCreate: function(){
this.inherited(arguments);
this._initialized = true;
if(!this.invalidRendering){
this.refreshRendering();
}
},
_createRenderData: function(){
var rd = {};
rd.dateLocaleModule = this.dateLocaleModule;
rd.dateClassObj = this.dateClassObj;
rd.dateModule = this.dateModule; // arithmetics on Dates
rd.dates = [];
rd.columnCount = this.get("columnCount");
rd.rowCount = this.get("rowCount");
rd.sheetHeight = this.itemContainer.offsetHeight;
this._computeRowsHeight(rd);
var d = this.get("startDate");
if(d == null){
d = new rd.dateClassObj();
}
d = this.floorToDay(d, false, rd);
this.startDate = d;
for(var row = 0; row < rd.rowCount ; row++){
rd.dates.push([]);
for(var col = 0; col < rd.columnCount ; col++){
rd.dates[row].push(d);
d = rd.dateModule.add(d, "day", 1);
d = this.floorToDay(d, false, rd);
}
}
rd.startTime = this.newDate(rd.dates[0][0], rd);
rd.endTime = this.newDate(rd.dates[rd.rowCount-1][rd.columnCount-1], rd);
rd.endTime = rd.dateModule.add(rd.endTime, "day", 1);
rd.endTime = this.floorToDay(rd.endTime, true);
if(this.displayedItemsInvalidated && !this._isEditing){
this.displayedItemsInvalidated = false;
this._computeVisibleItems(rd);
}else if(this.renderData){
rd.items = this.renderData.items;
}
rd.rtl = !this.isLeftToRight();
return rd;
},
_validateProperties: function(){
this.inherited(arguments);
if(this.columnCount<1 || isNaN(this.columnCount)){
this.columnCount = 1;
}
if(this.rowCount<1 || isNaN(this.rowCount)){
this.rowCount = 1;
}
if(isNaN(this.percentOverlap) || this.percentOverlap < 0 || this.percentOverlap > 100){
this.percentOverlap = 0;
}
if(isNaN(this.verticalGap) || this.verticalGap < 0){
this.verticalGap = 2;
}
if(isNaN(this.horizontalRendererHeight) || this.horizontalRendererHeight < 1){
this.horizontalRendererHeight = 17;
}
if(isNaN(this.labelRendererHeight) || this.labelRendererHeight < 1){
this.labelRendererHeight = 14;
}
if(isNaN(this.expandRendererHeight) || this.expandRendererHeight < 1){
this.expandRendererHeight = 15;
}
},
_setStartDateAttr: function(value){
this.displayedItemsInvalidated = true;
this._set("startDate", value);
},
_setColumnCountAttr: function(value){
this.displayedItemsInvalidated = true;
this._set("columnCount", value);
},
_setRowCountAttr: function(value){
this.displayedItemsInvalidated = true;
this._set("rowCount", value);
},
__fixEvt:function(e){
e.sheet = "primary";
e.source = this;
return e;
},
//////////////////////////////////////////
//
// Formatting functions
//
//////////////////////////////////////////
_formatRowHeaderLabel: function(/*Date*/d){
// summary:
// Computes the row header label for the specified time of day.
// By default the getWeekNumberLabel() function is called.
// The rowHeaderDatePattern property can be used to set a
// custom date pattern to the formatter.
// d: Date
// The date to format
// tags:
// protected
if(this.rowHeaderDatePattern){
return this.renderData.dateLocaleModule.format(d, {
selector: 'date',
datePattern: this.rowHeaderDatePattern
});
}else{
return this.getWeekNumberLabel(d);
}
},
_formatColumnHeaderLabel: function(d){
// summary:
// Computes the column header label for the specified date.
// By default a formatter is used, optionally the columnHeaderLabelLength
// property can be used to specify the length of the string.
// d: Date
// The date to format
// tags:
// protected
return this.renderData.dateLocaleModule.getNames('days', this.columnHeaderLabelLength ? this.columnHeaderLabelLength : 'wide', 'standAlone')[d.getDay()];
},
_formatGridCellLabel: function(d, row, col){
// summary:
// Computes the column header label for the specified date.
// By default a formatter is used, optionally the cellHeaderLongPattern
and cellHeaderShortPattern
// properties can be used to set a custom date pattern to the formatter.
// d: Date
// The date to format.
// row: Integer
// The row that displays the current date.
// col: Integer
// The column that displays the current date.
// tags:
// protected
var isFirstDayOfMonth = row == 0 && col == 0 || d.getDate() == 1;
var format, rb;
if(isFirstDayOfMonth){
if(this.cellHeaderLongPattern){
format = this.cellHeaderLongPattern;
}else{
rb = i18n.getLocalization("dojo.cldr", this._calendar);
format = rb["dateFormatItem-MMMd"];
}
}else{
if(this.cellHeaderShortPattern){
format = this.cellHeaderShortPattern;
}else{
rb = i18n.getLocalization("dojo.cldr", this._calendar);
format = rb["dateFormatItem-d"];
}
}
return this.renderData.dateLocaleModule.format(d, {
selector: 'date',
datePattern: format
});
},
////////////////////////////////////////////
//
// HTML structure management
//
///////////////////////////////////////////
refreshRendering: function(){
this.inherited(arguments);
if(!this.domNode){
return;
}
this._validateProperties();
var oldRd = this.renderData;
this.renderData = this._createRenderData();
this._createRendering(this.renderData, oldRd);
this._layoutRenderers(this.renderData);
},
_createRendering: function(renderData, oldRenderData){
// summary:
// Creates the HTML structure (grid, place holders, headers, etc)
// renderData: Object
// The new render data
// oldRenderData: Object
// The previous render data
// tags:
// private
if(renderData.rowHeight <= 0){
renderData.columnCount = 1;
renderData.rowCount = 1;
renderData.invalidRowHeight = true;
return;
}
if(oldRenderData){
// make sure to have correct rowCount
if(this.itemContainerTable){
var rows = query(".dojoxCalendarItemContainerRow", this.itemContainerTable);
oldRenderData.rowCount = rows.length;
}
}
this._buildColumnHeader(renderData, oldRenderData);
this._buildRowHeader(renderData, oldRenderData);
this._buildGrid(renderData, oldRenderData);
this._buildItemContainer(renderData, oldRenderData);
if(this.buttonContainer && this.owner != null && this.owner.currentView == this){
domStyle.set(this.buttonContainer, {"right":0, "left":0});
}
},
_buildColumnHeader: function(/*Object*/ renderData, /*Object*/oldRenderData){
// summary:
// Creates incrementally the HTML structure of the column header and configures its content.
//
// renderData:
// The render data to display.
//
// oldRenderData:
// The previously render data displayed, if any.
// tags:
// private
var table = this.columnHeaderTable;
if(!table){
return;
}
var count = renderData.columnCount - (oldRenderData ? oldRenderData.columnCount : 0);
if(has("ie") == 8){
// workaround Internet Explorer 8 bug.
// if on the table, width: 100% and table-layout: fixed are set
// and columns are removed, width of remaining columns is not
// recomputed: must rebuild all.
if(this._colTableSave == null){
this._colTableSave = lang.clone(table);
}else if(count < 0){
this.columnHeader.removeChild(table);
domConstruct.destroy(table);
table = lang.clone(this._colTableSave);
this.columnHeaderTable = table;
this.columnHeader.appendChild(table);
count = renderData.columnCount;
}
} // else incremental dom add/remove for real browsers.
var tbodies = query("tbody", table);
var trs = query("tr", table);
var tbody, tr, td;
if(tbodies.length == 1){
tbody = tbodies[0];
}else{
tbody = html.create("tbody", null, table);
}
if(trs.length == 1){
tr = trs[0];
}else{
tr = domConstruct.create("tr", null, tbody);
}
// Build HTML structure (incremental)
if(count > 0){ // creation
for(var i=0; i < count; i++){
td = domConstruct.create("td", null, tr);
}
}else{ // deletion
count = -count;
for(var i=0; i < count; i++){
tr.removeChild(tr.lastChild);
}
}
// fill & configure
query("td", table).forEach(function(td, i){
td.className = "";
var d = renderData.dates[0][i];
this._setText(td, this._formatColumnHeaderLabel(d));
if(i == 0){
domClass.add(td, "first-child");
}else if(i == this.renderData.columnCount-1){
domClass.add(td, "last-child");
}
this.styleColumnHeaderCell(td, d, renderData);
}, this);
if(this.yearColumnHeaderContent){
var d = renderData.dates[0][0];
this._setText(this.yearColumnHeaderContent, renderData.dateLocaleModule.format(d,
{selector: "date", datePattern:"yyyy"}));
}
},
styleColumnHeaderCell: function(node, date, renderData){
// summary:
// Styles the CSS classes to the node that displays a column header cell.
// By default this method is setting the "dojoxCalendarWeekend" if the day of week represents a weekend.
// node: Node
// The DOM node that displays the column in the grid.
// date: Date
// The date displayed by this column
// renderData: Object
// The render data.
// tags:
// protected
domClass.add(node, this._cssDays[date.getDay()]);
if(this.isWeekEnd(date)){
domClass.add(node, "dojoxCalendarWeekend");
}
},
_rowHeaderHandles: null,
_cleanupRowHeader: function(){
// tags:
// private
while(this._rowHeaderHandles.length > 0){
var list = this._rowHeaderHandles.pop();
while(list.length>0){
list.pop().remove();
}
}
},
_rowHeaderClick: function(e){
// tags:
// private
var index = query("td", this.rowHeaderTable).indexOf(e.currentTarget);
this._onRowHeaderClick({
index: index,
date: this.renderData.dates[index][0],
triggerEvent: e
});
},
_buildRowHeader: function(renderData, oldRenderData){
// summary:
// Creates incrementally the HTML structure of the row header and configures its content.
//
// renderData:
// The render data to display.
//
// oldRenderData:
// The previously render data displayed, if any.
// tags:
// private
var rowHeaderTable = this.rowHeaderTable;
if(!rowHeaderTable){
return;
}
var tbodies = query("tbody", rowHeaderTable);
var tbody, tr, td;
if(tbodies.length == 1){
tbody = tbodies[0];
}else{
tbody = domConstruct.create("tbody", null, rowHeaderTable);
}
var count = renderData.rowCount - (oldRenderData ? oldRenderData.rowCount : 0);
// Build HTML structure
if(count>0){ // creation
for(var i=0; i < count; i++){
tr = domConstruct.create("tr", null, tbody);
td = domConstruct.create("td", null, tr);
var h = [];
h.push(on(td, "click", lang.hitch(this, this._rowHeaderClick)));
if(!has("touch")){
h.push(on(td, "mousedown", function(e){
domClass.add(e.currentTarget, "Active");
}));
h.push(on(td, "mouseup", function(e){
domClass.remove(e.currentTarget, "Active");
}));
h.push(on(td, "mouseover", function(e){
domClass.add(e.currentTarget, "Hover");
}));
h.push(on(td, "mouseout", function(e){
domClass.remove(e.currentTarget, "Hover");
}));
}
this._rowHeaderHandles.push(h);
}
}else{
count = -count;
// deletion of existing nodes
for(var i=0; i < count; i++){
tbody.removeChild(tbody.lastChild);
var list = this._rowHeaderHandles.pop();
while(list.length>0){
list.pop().remove();
}
}
}
// fill labels
query("tr", rowHeaderTable).forEach(function(tr, i){
domStyle.set(tr, "height", this._getRowHeight(i) + "px");
var d = renderData.dates[i][0];
var td = query("td", tr)[0];
td.className = "";
if(i == 0){
domClass.add(td, "first-child");
}
if(i == this.renderData.rowCount-1){
domClass.add(td, "last-child");
}
this.styleRowHeaderCell(td, d, renderData);
this._setText(td, this._formatRowHeaderLabel(d));
}, this);
},
styleRowHeaderCell: function(node, date, renderData){
// summary:
// Styles the CSS classes to the node that displays a row header cell.
// By default this method is doing nothing.
// node: Node
// The DOM node that displays the column in the grid.
// date: Date
// The date in the week.
// renderData: Object
// The render data.
// tags:
// protected
},
_buildGrid: function (renderData, oldRenderData){
// summary:
// Creates incrementally the HTML structure of the grid and configures its content.
//
// renderData:
// The render data to display.
//
// oldRenderData:
// The previously render data displayed, if any.
// tags:
// private
var table = this.gridTable;
if(!table){
return;
}
var currentTR = query("tr", table);
var rowDiff = renderData.rowCount - currentTR.length;
var addRows = rowDiff > 0;
var colDiff = renderData.columnCount - (oldRenderData ? oldRenderData.columnCount : 0);
if(has("ie") == 8){
// workaround Internet Explorer 8 bug.
// if on the table, width: 100% and table-layout: fixed are set
// and columns are removed, width of remaining columns is not
// recomputed: must rebuild all.
if(this._gridTableSave == null){
this._gridTableSave = lang.clone(table);
}else if(colDiff < 0){
this.grid.removeChild(table);
domConstruct.destroy(table);
table = lang.clone(this._gridTableSave);
this.gridTable = table;
this.grid.appendChild(table);
colDiff = renderData.columnCount;
rowDiff = renderData.rowCount;
addRows = true;
}
}
var tbodies = query("tbody", table);
var tbody;
if(tbodies.length == 1){
tbody = tbodies[0];
}else{
tbody = domConstruct.create("tbody", null, table);
}
// Build rows HTML structure (incremental)
if(addRows){ // creation
for(var i=0; i0;
colDiff = addCols ? colDiff : -colDiff;
query("tr", table).forEach(function(tr, i){
if(addCols){ // creation
var len = i >= rowIndex ? renderData.columnCount : colDiff;
for(var i=0; i= 0 ||
cal.compare(cal.add(date, "day", 1), this.refStartTime) <= 0)){
domClass.add(node, "dojoxCalendarDayDisabled");
}else if(this.isWeekEnd(date)){
domClass.add(node, "dojoxCalendarWeekend");
}
},
styleGridCell: function(node, date, renderData){
// summary:
// Styles the CSS classes to the node that displays a cell.
// Delegates to styleGridCellFunc if defined or defaultStyleGridCell otherwise.
// node: Node
// The DOM node that displays the cell in the grid.
// date: Date
// The date displayed by this cell.
// renderData: Object
// The render data.
// tags:
// protected
if(this.styleGridCellFunc){
this.styleGridCellFunc(node, date, renderData);
}else{
this.defaultStyleGridCell(node, date, renderData);
}
},
_buildItemContainer: function(renderData, oldRenderData){
// summary:
// Creates the HTML structure of the item container and configures its content.
//
// renderData:
// The render data to display.
//
// oldRenderData:
// The previously render data displayed, if any.
// tags:
// private
var table = this.itemContainerTable;
if(!table){
return;
}
var rows = [];
var count = renderData.rowCount - (oldRenderData ? oldRenderData.rowCount : 0)
if(has("ie") == 8){
// workaround Internet Explorer 8 bug.
// if on the table, width: 100% and table-layout: fixed are set
// and columns are removed, width of remaining columns is not
// recomputed: must rebuild all.
if(this._itemTableSave == null){
this._itemTableSave = lang.clone(table);
}else if(count < 0){
this.itemContainer.removeChild(table);
this._recycleItemRenderers(true);
this._recycleExpandRenderers(true);
domConstruct.destroy(table);
table = lang.clone(this._itemTableSave);
this.itemContainerTable = table;
this.itemContainer.appendChild(table);
count = renderData.columnCount;
}
} // else incremental dom add/remove for real browsers.
var tbodies = query("tbody", table);
var tbody, tr, td, div;
if(tbodies.length == 1){
tbody = tbodies[0];
}else{
tbody = domConstruct.create("tbody", null, table);
}
// Build HTML structure (incremental)
if(count>0){ // creation
for(var i=0; i < count; i++){
tr = domConstruct.create("tr", null, tbody);
domClass.add(tr, "dojoxCalendarItemContainerRow");
td = domConstruct.create("td", null, tr);
div = domConstruct.create("div", null, td);
domClass.add(div, "dojoxCalendarContainerRow");
}
}else{ // deletion
count = -count;
for(var i=0; i < count; i++){
tbody.removeChild(tbody.lastChild);
}
}
query(".dojoxCalendarItemContainerRow", table).forEach(function(tr, i){
domStyle.set(tr, "height", this._getRowHeight(i) + "px");
rows.push(tr.childNodes[0].childNodes[0]);
}, this);
renderData.cells = rows;
},
resize: function(changeSize){
this.inherited(arguments);
this._resizeHandler(null, false);
},
_resizeHandler: function(e, apply){
// summary:
// Refreshes and apply the row height according to the widget height.
// e: Event
// The resize event (optional)
// apply: Boolean
// Whether take into account the layoutDuringResize flag to relayout item while resizing or not.
// tags:
// private
var rd = this.renderData;
if(rd == null){
this.refreshRendering();
return;
}
if(rd.sheetHeight != this.itemContainer.offsetHeight){
// refresh values
rd.sheetHeight = this.itemContainer.offsetHeight;
var expRow = this.getExpandedRowIndex();
if(expRow == -1){
this._computeRowsHeight();
this._resizeRows();
}else{
this.expandRow(rd.expandedRow, rd.expandedRowCol, 0, null, true);
}
if(rd.invalidRowHeight){
// complete recompute
delete rd.invalidRowHeight;
this.renderData = null;
this.displayedItemsInvalidated = true;
this.refreshRendering();
return;
}
}
if(this.layoutDuringResize || apply){
// Use a time for FF (at least). In FF the cell size and position info are not ready yet.
setTimeout(lang.hitch(this, function(){
this._layoutRenderers(this.renderData);
}), 20);
}else{
domStyle.set(this.itemContainer, "opacity", 0);
this._recycleItemRenderers();
this._recycleExpandRenderers();
if(this._resizeTimer != undefined){
clearTimeout(this._resizeTimer);
}
this._resizeTimer = setTimeout(lang.hitch(this, function(){
delete this._resizeTimer;
this._resizeRowsImpl(this.itemContainer, "tr");
this._layoutRenderers(this.renderData);
if(this.resizeAnimationDuration == 0){
domStyle.set(this.itemContainer, "opacity", 1);
}else{
fx.fadeIn({node:this.itemContainer, curve:[0, 1]}).play(this.resizeAnimationDuration);
}
}), 200);
}
},
// resizeAnimationDuration: Integer
// Duration, in milliseconds, of the fade animation showing the item renderers after a widget resize.
resizeAnimationDuration: 0,
/////////////////////////////////////////////
//
// Row height management
//
//////////////////////////////////////////////
getExpandedRowIndex: function(){
// summary:
// Returns the index of the expanded row or -1 if there's no row expanded.
return this.renderData.expandedRow == null ? -1 : this.renderData.expandedRow;
},
collapseRow: function(duration, easing, apply){
// summary:
// Collapses the expanded row, if any.
// duration: Integer
// Duration in milliseconds of the optional animation.
// easing: Function
// Easing function of the optional animation.
var rd = this.renderData;
if(apply == undefined){
apply = true;
}
if(duration == undefined){
duration = this.expandDuration;
}
if(rd && rd.expandedRow != null && rd.expandedRow != -1){
if(apply && duration){
var index = rd.expandedRow;
var oldSize = rd.expandedRowHeight;
delete rd.expandedRow;
this._computeRowsHeight(rd);
var size = this._getRowHeight(index);
rd.expandedRow = index;
this._recycleExpandRenderers();
this._recycleItemRenderers();
domStyle.set(this.itemContainer, "display", "none");
this._expandAnimation = new fx.Animation({
curve: [oldSize, size],
duration: duration,
easing: easing,
onAnimate: lang.hitch(this, function(size) {
this._expandRowImpl(Math.floor(size));
}),
onEnd: lang.hitch(this, function(size) {
this._expandAnimation = null;
this._collapseRowImpl(false);
this._resizeRows();
domStyle.set(this.itemContainer, "display", "block");
setTimeout(lang.hitch(this, function(){
this._layoutRenderers(rd);
}), 100);
this.onExpandAnimationEnd(false);
})
});
this._expandAnimation.play();
}else{
this._collapseRowImpl(apply);
}
}
},
_collapseRowImpl: function(apply){
// tags:
// private
var rd = this.renderData;
delete rd.expandedRow;
delete rd.expandedRowHeight;
this._computeRowsHeight(rd);
if(apply == undefined || apply){
this._resizeRows();
this._layoutRenderers(rd);
}
},
expandRow: function(rowIndex, colIndex, duration, easing, apply){
// summary:
// Expands the specified row.
// rowIndex: Integer
// The index of the row to expand.
// colIndex: Integer?
// The column index of the expand renderer that triggers the action, optional.
// duration: Integer?
// Duration in milliseconds of the optional animation.
// easing: Function?
// Easing function of the optional animation.
var rd = this.renderData;
if(!rd || rowIndex < 0 || rowIndex >= rd.rowCount){
return -1;
}
if(colIndex == undefined || colIndex < 0 || colIndex >= rd.columnCount){
colIndex = -1; // ignore invalid values
}
if(apply == undefined){
apply = true;
}
if(duration == undefined){
duration = this.expandDuration;
}
if(easing == undefined){
easing = this.expandEasing;
}
var oldSize = this._getRowHeight(rowIndex);
var size = rd.sheetHeight - Math.ceil(this.cellPaddingTop * (rd.rowCount-1));
rd.expandedRow = rowIndex;
rd.expandedRowCol = colIndex;
rd.expandedRowHeight = size;
if(apply){
if(duration){
//debugger;
this._recycleExpandRenderers();
this._recycleItemRenderers();
domStyle.set(this.itemContainer, "display", "none");
this._expandAnimation = new fx.Animation({
curve: [oldSize, size],
duration: duration,
delay:50,
easing: easing,
onAnimate: lang.hitch(this, function(size) {
this._expandRowImpl(Math.floor(size));
}),
onEnd: lang.hitch(this, function(){
this._expandAnimation = null;
domStyle.set(this.itemContainer, "display", "block");
setTimeout(lang.hitch(this, function(){
this._expandRowImpl(size, true);
}), 100);
this.onExpandAnimationEnd(true);
})
});
this._expandAnimation.play();
}else{
this._expandRowImpl(size, true);
}
}
},
_expandRowImpl: function(size, layout){
// tags:
// private
var rd = this.renderData;
rd.expandedRowHeight = size;
this._computeRowsHeight(rd, rd.sheetHeight-size);
this._resizeRows();
if(layout){
this._layoutRenderers(rd);
}
},
onExpandAnimationEnd: function(expand){
// summary:
// Event dispatched at the end of an expand or collapse animation.
// expand: Boolean
// Whether the finished animation was an expand or a collapse animation.
// tags:
// callback
},
_resizeRows: function(){
// summary:
// Refreshes the height of the underlying HTML objects.
// tags:
// private
if(this._getRowHeight(0) <= 0){
return;
}
if(this.rowHeaderTable){
this._resizeRowsImpl(this.rowHeaderTable, "tr");
}
if(this.gridTable){
this._resizeRowsImpl(this.gridTable, "tr");
}
if(this.itemContainerTable){
this._resizeRowsImpl(this.itemContainerTable, "tr");
}
},
_computeRowsHeight:function(renderData, max){
// summary:
// 1. Determine if it's better to add or remove pixels
// 2. distribute added/removed pixels on first and last rows.
// if rows are not too small, it is not noticeable.
// tags:
// private
var rd = renderData == null ? this.renderData : renderData;
max = max || rd.sheetHeight;
max--;
if(has("ie") == 7){
max -= rd.rowCount;
}
if(rd.rowCount == 1){
rd.rowHeight = max;
rd.rowHeightFirst = max;
rd.rowHeightLast = max;
return;
}
var count = rd.expandedRow == null ? rd.rowCount : rd.rowCount-1;
var rhx = max / count;
var rhf, rhl, rh;
var diffMin = max - (Math.floor(rhx) * count);
var diffMax = Math.abs(max - (Math.ceil(rhx) * count));
var diff;
var sign = 1;
if(diffMin < diffMax){
rh = Math.floor(rhx);
diff = diffMin;
}else{
sign = -1;
rh = Math.ceil(rhx);
diff = diffMax;
}
rhf = rh + sign * Math.floor(diff/2);
rhl = rhf + sign * (diff%2);
rd.rowHeight = rh;
rd.rowHeightFirst = rhf;
rd.rowHeightLast = rhl;
},
_getRowHeight: function(index){
// tags:
// private
var rd = this.renderData;
if(index == rd.expandedRow){
return rd.expandedRowHeight;
} else if(rd.expandedRow == 0 && index == 1 || index == 0){
return rd.rowHeightFirst;
} else if(rd.expandedRow == this.renderData.rowCount-1 &&
index == this.renderData.rowCount-2 ||
index == this.renderData.rowCount-1){
return rd.rowHeightLast;
}else{
return rd.rowHeight;
}
},
_resizeRowsImpl: function(tableNode, query){
// tags:
// private
dojo.query(query, tableNode).forEach(function(tr, i){
domStyle.set(tr, "height", this._getRowHeight(i)+"px");
}, this);
},
////////////////////////////////////////////
//
// Item renderers
//
///////////////////////////////////////////
_setHorizontalRendererAttr: function(value){
this._destroyRenderersByKind("horizontal");
this._set("horizontalRenderer", value);
},
_setLabelRendererAttr: function(value){
this._destroyRenderersByKind("label");
this._set("labelRenderer", value);
},
_destroyExpandRenderer: function(renderer){
// summary:
// Destroys the expand renderer.
// renderer: dojox/calendar/_RendererMixin
// The item renderer to destroy.
// tags:
// protected
if(renderer["destroyRecursive"]){
renderer.destroyRecursive();
}
html.destroy(renderer.domNode);
},
_setExpandRendererAttr: function(value){
while(this._ddRendererList.length>0){
this._destroyExpandRenderer(this._ddRendererList.pop());
}
var pool = this._ddRendererPool;
if(pool){
while(pool.length > 0){
this._destroyExpandRenderer(pool.pop());
}
}
this._set("expandRenderer", value);
},
_ddRendererList: null,
_ddRendererPool: null,
_getExpandRenderer: function(date, items, rowIndex, colIndex, expanded){
// tags:
// private
if(this.expandRenderer == null){
return null;
}
var ir = this._ddRendererPool.pop();
if(ir == null){
ir = new this.expandRenderer();
}
this._ddRendererList.push(ir);
ir.set("owner", this);
ir.set("date", date);
ir.set("items", items);
ir.set("rowIndex", rowIndex);
ir.set("columnIndex", colIndex);
ir.set("expanded", expanded);
return ir;
},
_recycleExpandRenderers: function(remove){
// tags:
// private
for(var i=0; i= 1440 ? "horizontal" : "label";
},
////////////////////////////////////////////
//
// Layout
//
///////////////////////////////////////////
// naturalRowHeight: Integer[]
// After an item layout has been done, contains for each row the natural height of the row.
// Ie. the height, in pixels, needed to display all the item renderers.
naturalRowsHeight: null,
_roundItemToDay: function(item){
// tags:
// private
var s = item.startTime, e = item.endTime;
if(!this.isStartOfDay(s)){
s = this.floorToDay(s, false, this.renderData);
}
if(!this.isStartOfDay(e)){
e = this.renderData.dateModule.add(e, "day", 1);
e = this.floorToDay(e, true);
}
return {startTime:s, endTime:e};
},
_sortItemsFunction: function(a, b){
// tags:
// private
if(this.roundToDay){
a = this._roundItemToDay(a);
b = this._roundItemToDay(b);
}
var res = this.dateModule.compare(a.startTime, b.startTime);
if(res == 0){
res = -1 * this.dateModule.compare(a.endTime, b.endTime);
}
return res;
},
_overlapLayoutPass3: function(lanes){
// summary:
// Third pass of the overlap layout (optional). Compute the number of lanes used by sub interval.
// lanes: Object[]
// The array of lanes.
// tags:
// private
var pos=0, posEnd=0;
var res = [];
var refPos = domGeometry.position(this.gridTable).x;
for(var col=0; col=0 && !stop; lane--){
for (var i=0; i 0 && this.horizontalRenderer){
var hItems = this._createHorizontalLayoutItems(index, start, end, horizontalItems);
var hOverlapLayout = this._computeHorizontalOverlapLayout(hItems, hOffsets);
}
var lItems;
var lOffsets = [];
if(labelItems.length > 0 && this.labelRenderer){
lItems = this._createLabelLayoutItems(index, start, end, labelItems);
this._computeLabelOffsets(lItems, lOffsets);
}
var hasHiddenItems = this._computeColHasHiddenItems(index, hOffsets, lOffsets);
if(hItems != null){
this._layoutHorizontalItemsImpl(index, hItems, hOverlapLayout, hasHiddenItems, hiddenItems);
}
if(lItems != null){
this._layoutLabelItemsImpl(index, lItems, hasHiddenItems, hiddenItems, hOffsets);
}
this._layoutExpandRenderers(index, hasHiddenItems, hiddenItems);
this._hiddenItems[index] = hiddenItems;
},
_createHorizontalLayoutItems: function(/*Integer*/index, /*Date*/startTime, /*Date*/endTime, /*Object[]*/items){
// tags:
// private
if(this.horizontalRenderer == null){
return;
}
var rd = this.renderData;
var cal = rd.dateModule;
var sign = rd.rtl ? -1 : 1;
var layoutItems = [];
// step 1: compute projected position and size
for(var i = 0; i < items.length; i++){
var item = items[i];
var overlap = this.computeRangeOverlap(rd, item.startTime, item.endTime, startTime, endTime);
var startOffset = cal.difference(startTime, this.floorToDay(overlap[0], false, rd), "day");
var dayStart = rd.dates[index][startOffset];
var celPos = domGeometry.position(this._getCellAt(index, startOffset, false));
var start = celPos.x - rd.gridTablePosX;
if(rd.rtl){
start += celPos.w;
}
if(!this.roundToDay && !item.allDay){
start += sign * this.computeProjectionOnDate(rd, dayStart, overlap[0], celPos.w);
}
start = Math.ceil(start);
var endOffset = cal.difference(startTime, this.floorToDay(overlap[1], false, rd), "day");
var end;
if(endOffset > rd.columnCount-1){
celPos = domGeometry.position(this._getCellAt(index, rd.columnCount-1, false));
if(rd.rtl){
end = celPos.x - rd.gridTablePosX;
}else{
end = celPos.x - rd.gridTablePosX + celPos.w;
}
}else{
dayStart = rd.dates[index][endOffset];
celPos = domGeometry.position(this._getCellAt(index, endOffset, false));
end = celPos.x - rd.gridTablePosX;
if(rd.rtl){
end += celPos.w;
}
if(this.roundToDay){
if(!this.isStartOfDay(overlap[1])){
end += sign * celPos.w;
}
}else{
end += sign * this.computeProjectionOnDate(rd, dayStart, overlap[1], celPos.w);
}
}
end = Math.floor(end);
if(rd.rtl){
var t = end;
end = start;
start = t;
}
if(end > start){ // invalid items are not displayed
var litem = lang.mixin({
start: start,
end: end,
range: overlap,
item: item,
startOffset: startOffset,
endOffset: endOffset
}, item);
layoutItems.push(litem);
}
}
return layoutItems;
},
_computeHorizontalOverlapLayout: function(layoutItems, offsets){
// tags:
// private
var rd = this.renderData;
var irHeight = this.horizontalRendererHeight;
var overlapLayoutRes = this.computeOverlapping(layoutItems, this._overlapLayoutPass3);
var vOverlap = this.percentOverlap / 100;
for(var i=0; i= this.columnCount){
// If the offset is greater than the column count
// the item will be processed in another row.
break;
}
if(startOffset >= 0){
var list = layoutItems[startOffset];
if(list == null){
list = [];
layoutItems[startOffset] = list;
}
list.push(lang.mixin(
{ startOffset: startOffset,
range: overlap,
item: item
}, item));
}
d = cal.add(d, "day", 1);
this.floorToDay(d, true);
}
}
return layoutItems;
},
_computeLabelOffsets: function(layoutItems, offsets){
// tags:
// private
for(var i=0; i maxH){
maxH = h;
}
res[i] = h > cellH;
}
this.naturalRowsHeight[index] = maxH;
return res;
},
_layoutHorizontalItemsImpl: function(index, layoutItems, hOverlapLayout, hasHiddenItems, hiddenItems){
// tags:
// private
var rd = this.renderData;
var cell = rd.cells[index];
var cellH = this._getRowHeight(index);
var irHeight = this.horizontalRendererHeight;
var vOverlap = this.percentOverlap / 100;
for(var i=0; i= 9 && item.start + w < this.itemContainer.offsetWidth) {
w++;
}
domStyle.set(ir.container, {
"top": (fullHeight ? this.cellPaddingTop : posY) + "px",
"left": item.start + "px",
"width": w + "px",
"height": h + "px"
});
this._applyRendererLayout(item, ir, cell, w, h, "horizontal");
}else{
// The items does not fit in view, fill hidden items per column
for(var d=item.startOffset;d r.w){
x = r.w-1;
}
if(y < 0){
y = 0;
}else if(y > r.h){
y = r.h-1;
}
// compute the date from column the time in day instead of time from start date of row to prevent DST hour offset.
var w = domGeometry.getMarginBox(this.itemContainer).w;
var colW = w / rd.columnCount;
var row;
if(rd.expandedRow == null){
row = Math.floor(y / (domGeometry.getMarginBox(this.itemContainer).h / rd.rowCount));
}else{
row = rd.expandedRow; //other rows are not usable
}
var r = domGeometry.getContentBox(this.itemContainer);
if(rd.rtl){
x = r.w - x;
}
var col = Math.floor(x / colW);
var tm = Math.floor((x-(col*colW)) * 1440 / colW);
var date = null;
if(row < rd.dates.length && col < this.renderData.dates[row].length){
date = this.newDate(this.renderData.dates[row][col]);
date = this.renderData.dateModule.add(date, "minute", tm);
}
return date;
},
/////////////////////////////////////////////
//
// Event management
//
//////////////////////////////////////////////
_onGridMouseUp: function(e){
// tags:
// private
this.inherited(arguments);
if(this._gridMouseDown){
this._gridMouseDown = false;
this._onGridClick({
date: this.getTime(e),
triggerEvent: e
});
}
},
_onGridTouchEnd: function(e){
// tags:
// private
this.inherited(arguments);
var g = this._gridProps;
if(g){
if(!this._isEditing){
// touched on grid and on touch start editing was ongoing.
if(!g.fromItem && !g.editingOnStart){
this.selectFromEvent(e, null, null, true);
}
if(!g.fromItem){
if(this._pendingDoubleTap && this._pendingDoubleTap.grid){
this._onGridDoubleClick({
date: this.getTime(this._gridProps.event),
triggerEvent: this._gridProps.event
});
clearTimeout(this._pendingDoubleTap.timer);
delete this._pendingDoubleTap;
}else{
this._onGridClick({
date: this.getTime(this._gridProps.event),
triggerEvent: this._gridProps.event
});
this._pendingDoubleTap = {
grid: true,
timer: setTimeout(lang.hitch(this, function(){
delete this._pendingDoubleTap;
}), this.doubleTapDelay)
};
}
}
}
this._gridProps = null;
}
},
/////////////////////////////////////////////
//
// Events
//
//////////////////////////////////////////////
_onRowHeaderClick: function(e){
this._dispatchCalendarEvt(e, "onRowHeaderClick");
// tags:
// private
},
onRowHeaderClick: function(e){
// summary:
// Event dispatched when a row header cell is clicked.
// e: __HeaderClickEventArgs
// Header click event.
// tags:
// callback
},
expandRendererClickHandler: function(e, renderer){
// summary:
// Default action when an expand renderer is clicked.
// e: Event
// The mouse event.
// renderer: Object
// The expand renderer.
// tags:
// protected
event.stop(e);
var ri = renderer.get("rowIndex");
var ci = renderer.get("columnIndex");
this._onExpandRendererClick(lang.mixin(this._createItemEditEvent(), {
rowIndex: ri,
columnIndex: ci,
renderer: renderer,
triggerEvent: e,
date: this.renderData.dates[ri][ci]
}));
},
onExpandRendererClick: function(e){
// summary:
// Event dispatched when an expand renderer is clicked.
// e: __ExpandRendererClickEventArgs
// Expand renderer click event.
// tags:
// callback
},
_onExpandRendererClick: function(e){
this._dispatchCalendarEvt(e, "onExpandRendererClick");
if(!e.isDefaultPrevented()){
if(this.getExpandedRowIndex() != -1){
this.collapseRow();
}else{
this.expandRow(e.rowIndex, e.columnIndex);
}
}
},
////////////////////////////////////////////
//
// Editing
//
///////////////////////////////////////////
snapUnit: "minute",
snapSteps: 15,
minDurationUnit: "minute",
minDurationSteps: 15,
triggerExtent: 3,
liveLayout: false,
stayInView: true,
allowStartEndSwap: true,
allowResizeLessThan24H: false
});
});