UNPKG

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,840 lines (1,510 loc) 64.7 kB
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", "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, 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, // expandRenderer: Class // The class use to create horizontal decoration renderers. horizontalDecorationRenderer: 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", "horizontalDecaorationRenderer", "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 = this.addAndFloor(d, "day", 1); } } 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){ // while editing in no live layout we must not to recompute items (duplicate renderers) this.displayedItemsInvalidated = false; this._computeVisibleItems(rd); }else if(this.renderData){ rd.items = this.renderData.items; } if(this.displayedDecorationItemsInvalidated){ rd.decorationItems = this.decorationStoreManager._computeVisibleItems(rd); }else if (this.renderData){ rd.decorationItems = this.renderData.decorationItems; } 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 <code>columnHeaderLabelLength</code> // 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()]; }, // cellHeaderShortPattern: String // Custom date/time pattern for grid cell label to override default one coming from the CLDR. // See dojo/date/locale documentation for format string. cellHeaderShortPattern: null, // cellHeaderLongPattern: String // Custom date/time pattern for grid cell label to override default one coming from the CLDR. // The long pattern is used for the first day of month or the first displayed day of a month. // See dojo/date/locale documentation for format string. cellHeaderLongPattern: null, _formatGridCellLabel: function(d, row, col){ // summary: // Computes the column header label for the specified date. // By default a formatter is used, optionally the <code>cellHeaderLongPattern</code> and <code>cellHeaderShortPattern</code> // 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; var rd = this.renderData = this._createRenderData(); this._createRendering(rd, oldRd); this._layoutDecorationRenderers(rd); this._layoutRenderers(rd); }, _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 - (currentTR ? query("td", currentTR[0]).length : 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; i<rowDiff; i++){ domConstruct.create("tr", null, tbody); } }else{ // deletion rowDiff = -rowDiff; for(var i=0; i<rowDiff; i++){ tbody.removeChild(tbody.lastChild); } } var rowIndex = renderData.rowCount - rowDiff; var addCols = addRows || colDiff >0; 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<len; i++){ var td = domConstruct.create("td", null, tr); domConstruct.create("span", null, td); } }else{ // deletion for(var i=0; i<colDiff; i++){ tr.removeChild(tr.lastChild); } } }); // Set the CSS classes query("tr", table).forEach(function (tr, row){ domStyle.set(tr, "height", this._getRowHeight(row) + "px"); tr.className = ""; // compatibility layer for IE7 & 8 that does not support :first-child and :last-child pseudo selectors if(row == 0){ domClass.add(tr, "first-child"); } if(row == renderData.rowCount-1){ domClass.add(tr, "last-child"); } query("td", tr).forEach(function (td, col){ td.className = ""; if(col == 0){ domClass.add(td, "first-child"); } if(col == renderData.columnCount-1){ domClass.add(td, "last-child"); } var d = renderData.dates[row][col]; var span = query("span", td)[0]; this._setText(span, this.showCellLabel ? this._formatGridCellLabel(d, row, col): null); this.styleGridCell(td, d, renderData); }, this); }, this); }, // styleGridCellFunc: Function // Custom function to customize the appearance of a grid cell by installing custom CSS class on the node. // The signature of the function must be the same then the styleGridCell one. // By default the defaultStyleGridCell function is used. styleGridCellFunc: null, defaultStyleGridCell: function(node, date, renderData){ // summary: // Styles the CSS classes to the node that displays a cell. // By default this method is setting the following CSS classes: // - "dojoxCalendarToday" class name if the date displayed is the current date, // - "dojoxCalendarWeekend" if the date represents a weekend or // - "dojoxCalendarDayDisabled" if the date is out of the [refStartTime, refEndTime] interval. // - the CSS class corresponding of the displayed day of week ("Sun", "Mon" and so on). // 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 domClass.add(node, this._cssDays[date.getDay()]); var cal = this.dateModule; if(this.isToday(date)){ domClass.add(node, "dojoxCalendarToday"); }else if(this.refStartTime != null && this.refEndTime != null && (cal.compare(date, this.refEndTime) >= 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); this._layoutDecorationRenderers(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); this._layoutDecorationRenderers(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<this._ddRendererList.length; i++){ var ir = this._ddRendererList[i]; ir.set("Up", false); ir.set("Down", false); if(remove){ ir.domNode.parentNode.removeChild(ir.domNode); } domStyle.set(ir.domNode, "display", "none"); } this._ddRendererPool = this._ddRendererPool.concat(this._ddRendererList); this._ddRendererList = []; }, _defaultItemToRendererKindFunc:function(item){ // tags: // private var dur = Math.abs(this.renderData.dateModule.difference(item.startTime, item.endTime, "minute")); return dur >= 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<this.renderData.columnCount; col++){ var stop = false; var colPos = domGeometry.position(this._getCellAt(0, col)); pos = colPos.x - refPos; posEnd = pos + colPos.w; for(var lane=lanes.length-1; lane>=0 && !stop; lane--){ for (var i=0; i<lanes[lane].length; i++){ var item = lanes[lane][i]; stop = item.start < posEnd && pos < item.end; if(stop){ res[col] = lane + 1; break; } } } if(!stop){ res[col] = 0; } } return res; }, 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: // private domStyle.set(renderer.container, {"zIndex": edited || selected ? renderer.renderer.mobile ? 100 : 0: item.lane == undefined ? 1 : item.lane+1}); }, _layoutDecorationRenderers: function(renderData){ // tags: // private if(renderData == null || renderData.decorationItems == null || renderData.rowHeight <= 0){ return; } if(!this.gridTable || this._expandAnimation != null || this.horizontalDecorationRenderer == null){ this.decorationRendererManager.recycleItemRenderers(); return; } this._layoutStep = renderData.columnCount; this.renderData.gridTablePosX = domGeometry.position(this.gridTable).x; this.inherited(arguments); }, _layoutRenderers: function(renderData){ // tags: // private if(renderData == null || renderData.items == null || renderData.rowHeight <= 0){ return; } if(!this.gridTable || this._expandAnimation != null || (this.horizontalRenderer == null && this.labelRenderer == null)){ this._recycleItemRenderers(); return; } this.renderData.gridTablePosX = domGeometry.position(this.gridTable).x; this._layoutStep = renderData.columnCount; this._recycleExpandRenderers(); this._hiddenItems = []; this._offsets = []; this.naturalRowsHeight = []; this.inherited(arguments); }, _offsets: null, _layoutInterval: function(/*Object*/renderData, /*Integer*/index, /*Date*/start, /*Date*/end, /*Object[]*/items, /*String*/itemsType){ // tags: // private if(this.renderData.cells == null){ return; } if(itemsType === "dataItems"){ var horizontalItems = []; var labelItems = []; for(var i=0; i<items.length; i++){ var item = items[i]; var kind = this._itemToRendererKind(item); if(kind == "horizontal"){ horizontalItems.push(item); }else if(kind == "label"){ labelItems.push(item); } } var expIndex = this.getExpandedRowIndex(); if(expIndex != -1 && expIndex != index){ return; // when row is expanded, layout only expanded row } var offsets; var hiddenItems = []; var hItems = null; var hOffsets = []; if(horizontalItems.length > 0 && this.horizontalRenderer){ var hItems = this._createHorizontalLayoutItems(index, start, end, horizontalItems, itemsType); 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, itemsType); } if(lItems != null){ this._layoutLabelItemsImpl(index, lItems, hasHiddenItems, hiddenItems, hOffsets, itemsType); } this._layoutExpandRenderers(index, hasHiddenItems, hiddenItems); this._hiddenItems[index] = hiddenItems; }else{ // itemsType === "decorationItems" if(this.horizontalDecorationRenderer){ var hItems = this._createHorizontalLayoutItems(index, start, end, items, itemsType); if(hItems != null){ this._layoutHorizontalItemsImpl(index, hItems, null, false, null, itemsType); } } } }, _createHorizontalLayoutItems: function(/*Integer*/index, /*Date*/startTime, /*Date*/endTime, /*Object[]*/items, /*String*/itemsType){ // tags: // private var rd = this.renderData; var cal = rd.dateModule; var sign = rd.rtl ? -1 : 1; var layoutItems = []; var isDecoration = itemsType === "decorationItems"; // 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(isDecoration && !item.isAllDay || !isDecoration && !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(!isDecoration && 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<rd.columnCount; i++){ var numLanes = overlapLayoutRes.addedPassRes[i]; var index = rd.rtl ? rd.columnCount - i - 1 : i; if(vOverlap == 0){ offsets[index] = numLanes == 0 ? 0 : numLanes == 1 ? irHeight : irHeight + (numLanes-1) * (irHeight + this.verticalGap); }else{ offsets[index] = numLanes == 0 ? 0 : numLanes * irHeight - (numLanes-1) * (vOverlap * irHeight) + this.verticalGap; } offsets[index] += this.cellPaddingTop; } return overlapLayoutRes; }, _createLabelLayoutItems: function(/*Integer*/index, /*Date*/startTime, /*Date*/endTime, /*Object[]*/items){ // tags: // private if(this.labelRenderer == null){ return; } var d; var rd = this.renderData; var cal = rd.dateModule; var layoutItems = []; for(var i = 0; i < items.length; i++){ var item = items[i]; d = this.floorToDay(item.startTime, false, rd); var comp = this.dateModule.compare; // iterate on columns overlapped by this item to create one item per column //while(d < item.endTime && d < rd.endTime){ while(comp(d, item.endTime) == -1 && comp(d, endTime) == -1){ var dayEnd = cal.add(d, "day", 1); dayEnd = this.floorToDay(dayEnd, true); var overlap = this.computeRangeOverlap(rd, item.startTime, item.endTime, d, dayEnd); var startOffset = cal.difference(startTime, this.floorToDay(overlap[0], false, rd), "day"); if(startOffset >= 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<this.renderData.columnCount; i++){ offsets[i] = layoutItems[i] == null ? 0 : layoutItems[i].length * (this.labelRendererHeight + this.verticalGap); } }, _computeColHasHiddenItems: function(index, hOffsets, lOffsets){ // tags: // private var res = []; var cellH = this._getRowHeight(index); var h; var maxH = 0; for(var i=0; i<this.renderData.columnCount; i++){ h = hOffsets == null || hOffsets[i] == null ? this.cellPaddingTop : hOffsets[i]; h += lOffsets == null || lOffsets[i] == null ? 0 : lOffsets[i]; if(h > maxH){ maxH = h; } res[i] = h > cellH; } this.naturalRowsHeight[index] = maxH; return res; }, _layoutHorizontalItemsImpl: function(index, layoutItems, hOverlapLayout, hasHiddenItems, hiddenItems, itemsType){ // 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<layoutItems.length; i++){ var item = layoutItems[i]; var lane = item.lane; if(itemsType === "dataItems"){ var posY = this.cellPaddingTop; if(vOverl