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,713 lines (1,390 loc) 66.7 kB
define([ "./ViewBase", "dijit/_TemplatedMixin", "./_ScrollBarBase", "dojo/text!./templates/ColumnView.html", "dojo/_base/declare", "dojo/_base/event", "dojo/_base/lang", "dojo/_base/array", "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/mouse", "dojo/query", "dojox/html/metrics"], function( ViewBase, _TemplatedMixin, _ScrollBarBase, template, declare, event, lang, arr, has, fx, html, on, dom, domClass, domStyle, domGeometry, domConstruct, mouse, query, metrics){ /*===== var __ColumnClickEventArgs = { // summary: // A column click event. // index: Integer // The column index. // date: Date // The date displayed by the column. // triggerEvent: Event // The origin event. }; =====*/ return declare("dojox.calendar.SimpleColumnView", [ViewBase, _TemplatedMixin], { // summary: // The simple column view is displaying a day per column. Each cell of a column is a time slot. baseClass: "dojoxCalendarColumnView", templateString: template, // viewKind: String // Type of the view. Used by the calendar widget to determine how to configure the view. // This view kind is "columns". viewKind: "columns", // scroll container is the focusable item to enable scrolling using up and down arrows _setTabIndexAttr: "domNode", // renderData: Object // The render data is the object that contains all the properties needed to render the component. 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, // columnCount: Integer // The number of column to display (from the startDate). columnCount: 7, // subcolumns: String[] // Array of sub columns values. subColumns: null, // minHours: Integer // The minimum hour to be displayed. It must be in the [0,23] interval and must be lower than the maxHours. minHours: 8, // maxHours: Integer // The maximum hour to be displayed. It must be in the [1,36] interval and must be greater than the minHours. maxHours: 18, // hourSize: Integer // The desired size in pixels of an hour on the screen. // Note that the effective size may be different as the time slot size must be an integer. hourSize: 100, // timeSlotDuration: Integer // Duration of the time slot in minutes. Must be a divisor of 60. timeSlotDuration: 15, // rowHeaderGridSlotDuration: Integer // Duration of the time slot in minutes in the row header. Must be a divisor of 60 and a multiple/divisor of timeSlotDuration. rowHeaderGridSlotDuration: 60, // rowHeaderLabelSlotDuration: Integer // Duration of the time slot in minutes in the row header labels. Must be a divisor of 60 and a multiple/divisor of timeSlotDuration. rowHeaderLabelSlotDuration: 60, // rowHeaderLabelOffset: Integer // Offset of the row label from the top of the row header cell in pixels. rowHeaderLabelOffset: 2, // rowHeaderFirstLabelOffset: Integer // Offset of the first row label from the top of the first row header cell in pixels. rowHeaderFirstLabelOffset: 2, // verticalRenderer: Class // The class use to create vertical renderers. verticalRenderer: null, // verticalDecorationRenderer: Class // The class use to create decoration renderers. verticalDecorationRenderer: null, // minColumnWidth: Integer // The minimum column width. If the number of columns and sub columns displayed makes the // width of a column greater than this property, a horizontal scroll bar is displayed. // If value <= 0, this constraint is ignored and the columns are using the available space. minColumnWidth: -1, // percentOverlap: Integer // The percentage of the renderer width used to superimpose one item renderer on another // when two events are overlapping. percentOverlap: 70, // horizontalGap: Integer // The number of pixels between two item renderers that are overlapping each other if the percentOverlap property is 0. horizontalGap: 4, _showSecondarySheet: false, _columnHeaderHandlers: null, constructor: function(){ this.invalidatingProperties = ["columnCount", "startDate", "minHours", "maxHours", "hourSize", "verticalRenderer", "verticalDecorationRenderer", "rowHeaderTimePattern", "columnHeaderDatePattern", "timeSlotDuration", "rowHeaderGridSlotDuration", "rowHeaderLabelSlotDuration", "rowHeaderLabelOffset", "rowHeaderFirstLabelOffset","percentOverlap", "horizontalGap", "scrollBarRTLPosition","itemToRendererKindFunc", "layoutPriorityFunction", "formatItemTimeFunc", "textDir", "items", "subColumns", "minColumnWidth"]; this._columnHeaderHandlers = []; }, destroy: function(preserveDom){ this._cleanupColumnHeader(); if(this.scrollBar){ this.scrollBar.destroy(preserveDom); } this.inherited(arguments); }, _scrollBar_onScroll: function(value){ this._setScrollPosition(value); }, _hscrollBar_onScroll: function(value){ this._setHScrollPosition(value); }, buildRendering: function(){ this.inherited(arguments); if(this.vScrollBar){ this.scrollBar = new _ScrollBarBase( {content: this.vScrollBarContent}, this.vScrollBar); this.scrollBar.on("scroll", lang.hitch(this, this._scrollBar_onScroll)); } if(this.hScrollBar){ this.hScrollBarW = new _ScrollBarBase( {content: this.hScrollBarContent, direction: "horizontal", value: 0}, this.hScrollBar); this.hScrollBarW.on("scroll", lang.hitch(this, this._hscrollBar_onScroll)); this._hScrollNodes = [this.columnHeaderTable, this.subColumnHeaderTable, this.gridTable, this.itemContainerTable]; } this._viewHandles.push( on(this.scrollContainer, mouse.wheel, dojo.hitch(this, this._mouseWheelScrollHander))); }, postscript: function(){ this.inherited(arguments); this._initialized = true; if(!this.invalidRendering){ this.refreshRendering(); } }, _setVerticalRendererAttr: function(value){ this._destroyRenderersByKind("vertical"); this._set("verticalRenderer", value); }, _createRenderData: function(){ var rd = {}; rd.minHours = this.get("minHours"); rd.maxHours = this.get("maxHours"); rd.hourSize = this.get("hourSize"); rd.hourCount = rd.maxHours - rd.minHours; rd.slotDuration = this.get("timeSlotDuration"); // must be consistent with previous statement rd.rowHeaderGridSlotDuration = this.get("rowHeaderGridSlotDuration"); rd.slotSize = Math.ceil(rd.hourSize / (60 / rd.slotDuration)); rd.hourSize = rd.slotSize * (60 / rd.slotDuration); rd.sheetHeight = rd.hourSize * rd.hourCount; if(!this._rowHeaderWidth){ this._rowHeaderWidth = domGeometry.getMarginBox(this.rowHeader).w; } rd.rowHeaderWidth = this._rowHeaderWidth; var sbMetrics = metrics.getScrollbar(); rd.scrollbarWidth = sbMetrics.w + 1; rd.scrollbarHeight = sbMetrics.h + 1; rd.dateLocaleModule = this.dateLocaleModule; rd.dateClassObj = this.dateClassObj; rd.dateModule = this.dateModule; // arithmetics on Dates rd.dates = []; rd.columnCount = this.get("columnCount"); rd.subColumns = this.get("subColumns"); rd.subColumnCount = rd.subColumns ? rd.subColumns.length : 1; rd.hScrollPaneWidth = domGeometry.getMarginBox(this.grid).w; rd.minSheetWidth = this.minColumnWidth < 0 ? -1 : this.minColumnWidth * rd.subColumnCount * rd.columnCount; rd.hScrollBarEnabled = this.minColumnWidth > 0 && rd.hScrollPaneWidth < rd.minSheetWidth; var d = this.get("startDate"); if (d == null){ d = new rd.dateClassObj(); } d = this.floorToDay(d, false, rd); this.startDate = d; for(var col = 0; col < rd.columnCount ; col++){ rd.dates.push(d); d = this.addAndFloor(d, "day", 1); } rd.startTime = new rd.dateClassObj(rd.dates[0]); rd.startTime.setHours(rd.minHours); rd.endTime = new rd.dateClassObj(rd.dates[rd.columnCount-1]); rd.endTime.setHours(rd.maxHours); if(this.displayedItemsInvalidated && !this._isEditing){ // while editing in no live layout we must not to recompute items (duplicate renderers) rd.items = this.storeManager._computeVisibleItems(rd); }else if (this.renderData){ rd.items = this.renderData.items; } if(this.displayedDecorationItemsInvalidated){ // while editing in no live layout we must not to recompute items (duplicate renderers) rd.decorationItems = this.decorationStoreManager._computeVisibleItems(rd); }else if (this.renderData){ rd.decorationItems = this.renderData.decorationItems; } return rd; }, _validateProperties: function() { this.inherited(arguments); var v = this.minHours; if(v < 0 || v>23 || isNaN(v)){ this.minHours = 0; } v = this.maxHours; if (v < 1 || v>36 || isNaN(v)){ this.minHours = 36; } if(this.minHours > this.maxHours){ var t = this.maxHours; this.maxHours = this.minHours; this.minHours = t; } if (this.maxHours - this.minHours < 1){ this.minHours = 0; this.maxHours = 24; } if (this.columnCount<1 || isNaN(this.columnCount)){ this.columnCount = 1; } v = this.percentOverlap; if(v < 0 ||v > 100 || isNaN(v)){ this.percentOverlap = 70; } if(this.hourSize<5 || isNaN(this.hourSize)){ this.hourSize = 10; } v = this.timeSlotDuration; if (v < 1 || v > 60 || isNaN(v)) { this.timeSlotDuration = 15; } }, _setStartDateAttr: function(value){ this.displayedItemsInvalidated = true; this._set("startDate", value); }, _setColumnCountAttr: function(value){ this.displayedItemsInvalidated = true; this._set("columnCount", value); }, __fixEvt:function(e){ // tags: // private e.sheet = "primary"; e.source = this; return e; }, ////////////////////////////////////////// // // Formatting functions // ////////////////////////////////////////// // rowHeaderTimePattern: String // Custom date/time pattern for the row header labels to override default one coming from the CLDR. // See dojo/date/locale documentation for format string. rowHeaderTimePattern: null, _formatRowHeaderLabel: function(/*Date*/d){ // summary: // Computes the row header label for the specified time of day. // By default a formatter is used, optionally the <code>rowHeaderTimePattern</code> property can be used to set a custom time pattern to the formatter. // d: Date // The date to format // tags: // protected return this.renderData.dateLocaleModule.format(d, { selector: "time", timePattern: this.rowHeaderTimePattern }); }, // columnHeaderDatePattern: String // Custom date/time pattern for column header labels to override default one coming from the CLDR. // See dojo/date/locale documentation for format string. columnHeaderDatePattern: null, _formatColumnHeaderLabel: function(/*Date*/d){ // summary: // Computes the column header label for the specified date. // By default a formatter is used, optionally the <code>columnHeaderDatePattern</code> property can be used to set a custom date pattern to the formatter. // d: Date // The date to format // tags: // protected return this.renderData.dateLocaleModule.format(d, { selector: "date", datePattern: this.columnHeaderDatePattern, formatLength: "medium" }); }, ////////////////////////////////////////// // // Time of day management // ////////////////////////////////////////// // scrollBarRTLPosition: String // Position of the scroll bar in right-to-left display. // Valid values are "left" and "right", default value is "left". scrollBarRTLPosition: "left", _getStartTimeOfDay: function(){ // summary: // Returns the visible first time of day. // tags: // protected // returns: Object var v = (this.get("maxHours") - this.get("minHours")) * this._getScrollPosition() / this.renderData.sheetHeight; return { hours: this.renderData.minHours + Math.floor(v), minutes: (v - Math.floor(v)) * 60 }; }, _getEndTimeOfDay: function(){ // summary: // Returns the visible last time of day. // tags: // protected // returns: Integer[] var v = (this.get("maxHours") - this.get("minHours")) * (this._getScrollPosition() + this.scrollContainer.offsetHeight) / this.renderData.sheetHeight; return { hours: this.renderData.minHours + Math.floor(v), minutes: (v - Math.floor(v)) * 60 }; }, // startTimeOfDay: Object // First time (hour/minute) of day displayed, if reachable. // An object containing "hours" and "minutes" properties. startTimeOfDay: 0, _setStartTimeOfDayAttr: function(value){ if(this.renderData){ this._setStartTimeOfDay(value.hours, value.minutes, value.duration, value.easing); }else{ this._startTimeOfDayInvalidated = true; } this._set("startTimeOfDay", value); }, _getStartTimeOfDayAttr: function(){ if(this.renderData){ return this._getStartTimeOfDay(); }else{ return this._get("startTimeOfDay"); } }, _setStartTimeOfDay: function(hour, minutes, maxDuration, easing){ // summary: // Scrolls the view to show the specified first time of day. // hour: Integer // The hour of the start time of day. // minutes: Integer // The minutes part of the start time of day. // maxDuration: Integer // The max duration of the scroll animation. // tags: // protected var rd = this.renderData; hour = hour || rd.minHours; minutes = minutes || 0; maxDuration = maxDuration || 0; if (minutes < 0){ minutes = 0; }else if (minutes > 59){ minutes = 59; } if (hour < 0){ hour = 0; }else if (hour > rd.maxHours){ hour = rd.maxHours; } var timeInMinutes = hour * 60 + minutes; var minH = rd.minHours*60; var maxH = rd.maxHours*60; if (timeInMinutes < minH){ timeInMinutes = minH; }else if(timeInMinutes > maxH){ timeInMinutes = maxH; } var pos = (timeInMinutes - minH) * rd.sheetHeight / (maxH - minH); pos = Math.min(rd.sheetHeight - this.scrollContainer.offsetHeight, pos); this._scrollToPosition(pos, maxDuration, easing); }, _scrollToPosition: function(position, maxDuration, easing){ // summary: // Scrolls the view to show the specified first time of day. // position: Integer // The position in pixels. // maxDuration: Integer // The max duration of the scroll animation. // tags: // protected if (maxDuration) { if(this._scrollAnimation){ this._scrollAnimation.stop(); } var scrollPos = this._getScrollPosition(); var duration = Math.abs(((position - scrollPos) * maxDuration) / this.renderData.sheetHeight); this._scrollAnimation = new fx.Animation({ curve: [scrollPos, position], duration: duration, easing: easing, onAnimate: lang.hitch(this, function(position) { this._setScrollImpl(position); }) }); this._scrollAnimation.play(); }else{ this._setScrollImpl(position); } }, _setScrollImpl: function(v){ this._setScrollPosition(v); if(this.scrollBar){ this.scrollBar.set("value", v); } }, ensureVisibility: function(start, end, visibilityTarget, margin, 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: Integer // 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. margin = margin == undefined ? this.renderData.slotDuration : margin; if(this.scrollable && this.autoScroll){ var s = start.getHours() * 60 + start.getMinutes() - margin; var e = end.getHours() * 60 + end.getMinutes() + margin; var vs = this._getStartTimeOfDay(); var ve = this._getEndTimeOfDay(); var viewStart = vs.hours * 60 + vs.minutes; var viewEnd = ve.hours * 60 + ve.minutes; var visible = false; var target = null; switch(visibilityTarget){ case "start": visible = s >= viewStart && s <= viewEnd; target = s ; break; case "end": visible = e >= viewStart && e <= viewEnd; target = e - (viewEnd - viewStart); break; case "both": visible = s >= viewStart && e <= viewEnd; target = s; break; } if(!visible){ this._setStartTimeOfDay(Math.floor(target/60), target%60, duration); } } }, scrollView: function(dir){ // summary: // Scrolls the view to the specified direction of one time slot duration. // dir: Integer // Direction of the scroll. Valid values are -1 and 1. // var t = this._getStartTimeOfDay(); t = t.hours*60 + t.minutes + (dir * this.timeSlotDuration); this._setStartTimeOfDay(Math.floor(t/60), t%60); }, scrollViewHorizontal: function(dir){ // summary: // Scrolls the view horizontally to the specified direction of one column or sub column (if set). // dir: Integer // Direction of the scroll. Valid values are -1 and 1. // this._setHScrollPosition(this._getHScrollPosition() + (dir * this.minColumnWidth)); if(this.hScrollBarW){ this.hScrollBarW.set("value", this._getHScrollPosition()); } }, _hScrollNodes: null, _setHScrollPositionImpl: function(pos, useDom, cssProp){ var elts = [this.columnHeaderTable, this.subColumnHeaderTable, this.gridTable, this.itemContainerTable]; var css = useDom ? null : "translateX(-"+pos+"px)"; arr.forEach(elts, function(elt){ if(useDom){ elt.scrollLeft = pos; domStyle.set(elt, "left", (-pos) + "px"); }else{ domStyle.set(elt, cssProp, css); } }, this); }, _mouseWheelScrollHander: function(e){ // summary: // Mouse wheel handler. // tags: // protected if(this.renderData.hScrollBarEnabled && e.altKey){ this.scrollViewHorizontal(e.wheelDelta > 0 ? -1 : 1); }else{ this.scrollView(e.wheelDelta > 0 ? -1 : 1); } event.stop(e); }, ////////////////////////////////////////// // // HTML structure management // ////////////////////////////////////////// refreshRendering: function(){ if(!this._initialized){ return; } this._validateProperties(); var oldRd = this.renderData; var rd = this._createRenderData(); this.renderData = rd; this._createRendering(rd, oldRd); this._layoutDecorationRenderers(rd); this._layoutRenderers(rd); }, _createRendering: function(/*Object*/renderData, /*Object*/oldRenderData){ // tags: // private domStyle.set(this.sheetContainer, "height", renderData.sheetHeight + "px"); // padding for the scroll bar. this._configureVisibleParts(renderData); this._configureScrollBar(renderData); this._buildColumnHeader(renderData, oldRenderData); this._buildSubColumnHeader(renderData, oldRenderData); this._buildRowHeader(renderData, oldRenderData); this._buildGrid(renderData, oldRenderData); this._buildItemContainer(renderData, oldRenderData); this._layoutTimeIndicator(renderData); this._commitProperties(renderData); }, _configureVisibleParts: function(renderData){ if(this.secondarySheetNode){ domStyle.set(this.secondarySheetNode, "display", this._showSecondarySheet ? "block" : "none"); } domClass[this.subColumns == null?"remove":"add"](this.domNode, "subColumns"); domClass[this._showSecondarySheet?"add":"remove"](this.domNode, "secondarySheet"); }, _commitProperties: function(renderData){ if(this._startTimeOfDayInvalidated){ this._startTimeOfDayInvalidated = false; var v = this.startTimeOfDay; if(v != null){ this._setStartTimeOfDay(v.hours, v.minutes == undefined ? 0 : v.minutes); // initial position, no animation } } }, _configureScrollBar: function(renderData){ // summary: // Sets the scroll bar size and position. // renderData: Object // The render data. // tags: // protected if(has("ie") && this.scrollBar){ domStyle.set(this.vScrollBar, "width", (renderData.scrollbarWidth + 1) + "px"); } var atRight = this.isLeftToRight() ? true : this.scrollBarRTLPosition == "right"; var rPos = atRight ? "right" : "left"; var lPos = atRight ? "left" : "right"; if(this.scrollBar){ this.scrollBar.set("maximum", renderData.sheetHeight); domStyle.set(this.vScrollBar, rPos, 0); domStyle.set(this.vScrollBar, atRight? "left" : "right", "auto"); domStyle.set(this.vScrollBar, "bottom", renderData.hScrollBarEnabled? renderData.scrollbarHeight + "px" : "0"); } domStyle.set(this.scrollContainer, rPos, renderData.scrollbarWidth + "px"); domStyle.set(this.scrollContainer, lPos, "0"); domStyle.set(this.header, rPos, renderData.scrollbarWidth + "px"); domStyle.set(this.header, lPos, "0"); domStyle.set(this.subHeader, rPos, renderData.scrollbarWidth + "px"); domStyle.set(this.subHeader, lPos, "0"); if(this.buttonContainer && this.owner != null && this.owner.currentView == this){ domStyle.set(this.buttonContainer, rPos, renderData.scrollbarWidth + "px"); domStyle.set(this.buttonContainer, lPos, "0"); } if(this.hScrollBar){ arr.forEach(this._hScrollNodes, function(elt){ domClass[renderData.hScrollBarEnabled ? "add" : "remove"](elt.parentNode, "dojoxCalendarHorizontalScroll"); }, this); if(!renderData.hScrollBarEnabled){ this._setHScrollPosition(0); this.hScrollBarW.set("value", 0); } domStyle.set(this.hScrollBar, { "display": renderData.hScrollBarEnabled ? "block" : "none", "height": renderData.scrollbarHeight + "px", "left": (atRight ? renderData.rowHeaderWidth : renderData.scrollbarWidth) + "px", "right": (atRight ? renderData.scrollbarWidth : renderData.rowHeaderWidth) + "px" }); domStyle.set(this.scrollContainer, "bottom", renderData.hScrollBarEnabled ? (renderData.scrollbarHeight + 1) + "px" : "0"); this._configureHScrollDomNodes(renderData.hScrollBarEnabled ? renderData.minSheetWidth + "px" : "100%"); this.hScrollBarW.set("maximum", renderData.minSheetWidth); this.hScrollBarW.set("containerSize", renderData.hScrollPaneWidth); } }, _configureHScrollDomNodes: function(styleWidth){ arr.forEach(this._hScrollNodes, function(elt){ domStyle.set(elt, "width", styleWidth); }, this); }, resize: function(e){ this._resizeHandler(e); }, _resizeHandler: function(e, apply){ // summary: // Refreshes the scroll bars after a resize of the widget. // e: Event // The resize event (optional) // apply: Boolean // Whether apply the changes or wait for 100 ms // tags: // private var rd = this.renderData; if(rd == null){ return; } if(apply){ var hScrollPaneWidth = domGeometry.getMarginBox(this.grid).w; if(rd.hScrollPaneWidth != hScrollPaneWidth){ // refresh values rd.hScrollPaneWidth = hScrollPaneWidth; rd.minSheetWidth = this.minColumnWidth < 0 ? -1 : this.minColumnWidth * rd.subColumnCount * rd.columnCount; rd.hScrollBarEnabled = this.minColumnWidth > 0 && domGeometry.getMarginBox(this.grid).w < rd.minSheetWidth; } this._configureScrollBar(rd); }else{ if(this._resizeTimer != undefined){ clearTimeout(this._resizeTimer); } this._resizeTimer = setTimeout(lang.hitch(this, function(){ this._resizeHandler(e, true); }), 100); } }, _columnHeaderClick: function(e){ // tags: // private event.stop(e); var index = query("td", this.columnHeaderTable).indexOf(e.currentTarget); this._onColumnHeaderClick({ index: index, date: this.renderData.dates[index], triggerEvent: e }); }, _buildColumnHeader: function(renderData, 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._cleanupColumnHeader(); 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); var h = []; h.push(on(td, "click", lang.hitch(this, this._columnHeaderClick))); if(has("touch-events")){ h.push(on(td, "touchstart", function(e){ event.stop(e); domClass.add(e.currentTarget, "Active"); })); h.push(on(td, "touchend", function(e){ event.stop(e); domClass.remove(e.currentTarget, "Active"); })); }else{ h.push(on(td, "mousedown", function(e){ event.stop(e); domClass.add(e.currentTarget, "Active"); })); h.push(on(td, "mouseup", function(e){ event.stop(e); domClass.remove(e.currentTarget, "Active"); })); h.push(on(td, "mouseover", function(e){ event.stop(e); domClass.add(e.currentTarget, "Hover"); })); h.push(on(td, "mouseout", function(e){ event.stop(e); domClass.remove(e.currentTarget, "Hover"); })); } this._columnHeaderHandlers.push(h); } }else{ // deletion count = -count; for(var i=0; i < count; i++){ td = tr.lastChild; tr.removeChild(td); domConstruct.destroy(td); var list = this._columnHeaderHandlers.pop(); while(list.length>0){ list.pop().remove(); } } } // fill & configure query("td", table).forEach(function(td, i){ td.className = ""; if(i == 0){ domClass.add(td, "first-child"); }else if(i == this.renderData.columnCount-1){ domClass.add(td, "last-child"); } var d = renderData.dates[i]; this._setText(td, this._formatColumnHeaderLabel(d)); this.styleColumnHeaderCell(td, d, renderData); }, this); if(this.yearColumnHeaderContent){ var d = renderData.dates[0]; this._setText(this.yearColumnHeaderContent, renderData.dateLocaleModule.format(d, {selector: "date", datePattern:"yyyy"})); } }, _cleanupColumnHeader: function(){ while(this._columnHeaderHandlers.length > 0){ var list = this._columnHeaderHandlers.pop(); while(list.length > 0){ list.pop().remove(); } } }, 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: // - "dojoxCalendarToday" class name if the date displayed is the current date, // - "dojoxCalendarWeekend" if the date represents a weekend, // - the CSS class corresponding of the displayed day of week ("Sun", "Mon" and so on). // 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.isToday(date)){ domClass.add(node, "dojoxCalendarToday"); } else if(this.isWeekEnd(date)){ domClass.add(node, "dojoxCalendarWeekend"); } }, _buildSubColumnHeader: function(renderData, 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.subColumnHeaderTable; if (!table || this.subColumns == null){ return; } var count = renderData.columnCount - query("td", table).length; 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._colSubTableSave == null){ this._colSubTableSave = lang.clone(table); }else if(count < 0){ this.subColumnHeader.removeChild(table); domConstruct.destroy(table); table = lang.clone(this._colSubTableSave); this.subColumnHeaderTable = table; this.subColumnHeader.appendChild(table); count = renderData.columnCount; } } // else incremental dom add/remove for real browsers. var tbodies = query(">tbody", table); var tbody, tr, td; if (tbodies.length == 1){ tbody = tbodies[0]; }else{ tbody = html.create("tbody", null, table); } var trs = query(">tr", tbody); if (trs.length == 1){ tr = trs[0]; }else{ tr = domConstruct.create("tr", null, tbody); } var subCount = renderData.subColumnCount; // Build HTML structure (incremental) if(count > 0){ // creation for(var i=0; i < count; i++){ td = domConstruct.create("td", null, tr); domConstruct.create("div", {"className": "dojoxCalendarSubHeaderContainer"}, td); } }else{ // deletion count = -count; for(var i=0; i < count; i++){ td = tr.lastChild; tr.removeChild(td); domConstruct.destroy(td); } } // fill & configure query("td", table).forEach(function(td, i){ td.className = ""; if(i == 0){ domClass.add(td, "first-child"); }else if(i == this.renderData.columnCount-1){ domClass.add(td, "last-child"); } query(".dojoxCalendarSubHeaderContainer", td).forEach(function(div, i){ var count = query(".dojoxCalendarSubHeaderContainer", div).length - subCount; if(count != 0){ var len = div.childNodes.length; for(var i=0; i<len; i++){ div.removeChild(div.lastChild); } for(var j=0; j<subCount; j++){ domConstruct.create("div", {"className": "dojoxCalendarSubHeaderCell dojoxCalendarSubHeaderLabel"}, div); } } var colW = (100/subCount) + "%"; query(".dojoxCalendarSubHeaderCell", div).forEach(function(div, i){ div.className = "dojoxCalendarSubHeaderCell dojoxCalendarSubHeaderLabel"; var col = subCount == 1 ? i : Math.floor(i / subCount); subColIdx = subCount == 1 ? 0 : i - col * subCount; domStyle.set(div, {width: colW, left: ((subColIdx * 100)/subCount)+"%"}); domClass[subColIdx<subCount-1 && subCount !== 1?"add":"remove"](div, "subColumn"); domClass.add(div, this.subColumns[subColIdx]); this._setText(div, this.subColumnLabelFunc(this.subColumns[subColIdx])); }, this); }, this); var d = renderData.dates[i]; this.styleSubColumnHeaderCell(td, d, renderData); }, this); }, subColumnLabelFunc: function(value){ // summary: // Computes the label for a sub column from the subColumns property. // By default, return the value. return value; }, styleSubColumnHeaderCell: function(node, date, renderData){ // summary: // Styles the CSS classes to the node that displays a sub column header cell. // By default this method is not setting anythin: // node: Node // The DOM node that displays the column in the grid. // subColumnIndex: Integer // The cub column index. // renderData: Object // The render data. // tags: // protected domClass.add(node, this._cssDays[date.getDay()]); if(this.isToday(date)){ domClass.add(node, "dojoxCalendarToday"); } else if(this.isWeekEnd(date)){ domClass.add(node, "dojoxCalendarWeekend"); } }, _addMinutesClasses: function(node, minutes){ switch(minutes){ case 0: domClass.add(node, "hour"); break; case 30: domClass.add(node, "halfhour"); break; case 15: case 45: domClass.add(node, "quarterhour"); break; } }, _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; } if(this._rowHeaderLabelContainer == null){ this._rowHeaderLabelContainer = domConstruct.create("div", {"class": "dojoxCalendarRowHeaderLabelContainer"}, this.rowHeader); } domStyle.set(rowHeaderTable, "height", renderData.sheetHeight + "px"); var tbodies = query("tbody", rowHeaderTable); var tbody, tr, td; if (tbodies.length == 1){ tbody = tbodies[0]; }else{ tbody = domConstruct.create("tbody", null, rowHeaderTable); } var nbRows = Math.floor(60 / renderData.rowHeaderGridSlotDuration) * renderData.hourCount; var count = nbRows - (oldRenderData ? Math.floor(60 / oldRenderData.rowHeaderGridSlotDuration) * oldRenderData.hourCount : 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); } }else{ count = -count; // deletion of existing nodes for(var i=0; i < count; i++){ tbody.removeChild(tbody.lastChild); } } // fill labels var rd = this.renderData; var size = Math.ceil(renderData.hourSize / (60 / renderData.rowHeaderGridSlotDuration)); var d = new Date(2000, 0, 1, 0, 0, 0); query("tr", rowHeaderTable).forEach(function(tr, i){ var td = query("td", tr)[0]; td.className = ""; domStyle.set(tr, "height", (has("ie") == 7)?size-2*(60 / renderData.rowHeaderGridSlotDuration):size + "px"); var h = renderData.minHours + (i * this.renderData.rowHeaderGridSlotDuration) / 60; var m = (i * this.renderData.rowHeaderGridSlotDuration) % 60; this.styleRowHeaderCell(td, h, m, rd); this._addMinutesClasses(td, m); }, this); var lc = this._rowHeaderLabelContainer; count = (Math.floor(60 / this.rowHeaderLabelSlotDuration) * renderData.hourCount) - lc.childNodes.length; var span; if(count>0){ // creation for(var i=0; i < count; i++){ span = domConstruct.create("span", null, lc); domClass.add(span, "dojoxCalendarRowHeaderLabel"); } }else{ count = -count; // deletion of existing nodes for(var i=0; i < count; i++){ lc.removeChild(lc.lastChild); } } size = Math.ceil(renderData.hourSize / (60 / this.rowHeaderLabelSlotDuration)); query(">span", lc).forEach(function(span, i){ d.setHours(0); d.setMinutes(renderData.minHours * 60 + (i*this.rowHeaderLabelSlotDuration)); this._configureRowHeaderLabel(span, d, i, size*i, rd); }, this); }, _configureRowHeaderLabel: function(node, d, index, pos, renderData){ // summary: // Configures the label of a row header cell. // node: DOMNode // The DOM node that is the parent of the label. // d:Date // A date object that contains the hours and minutes displayed by this row header cell. // index: Integer // The index of this row header cell // pos: Integer // The computed position of the row header cell // renderData: Object // The render data. this._setText(node, this._formatRowHeaderLabel(d)); domStyle.set(node, "top", (pos + (index==0?this.rowHeaderFirstLabelOffset:this.rowHeaderLabelOffset))+"px"); var h = renderData.minHours + (index * this.rowHeaderLabelSlotDuration) / 60; var m = (index * this.rowHeaderLabelSlotDuration) % 60; domClass.remove(node, ["hour", "halfhour", "quarterhour"]); this._addMinutesClasses(node, m); this.styleRowHeaderCell(node, h, m, renderData); }, styleRowHeaderCell: function(node, h, m, 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. // h: Integer // The time of day displayed by this row header cell. // 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; } domStyle.set(table, "height", renderData.sheetHeight + "px"); var nbRows = Math.floor(60 / renderData.slotDuration) * renderData.hourCount; var rowDiff = nbRows - (oldRenderData ? Math.floor(60 / oldRenderData.slotDuration) * oldRenderData.hourCount : 0); 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 = nbRows; addRows = true; } } var tbodies = query("tbody", table); var tbody; if (tbodies.length == 1){ tbody = tbodies[0]; }else{ tbody = domConstruct.create("tbody", null, table); } // Build time slots (lines) 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 = Math.floor(60 / renderData.slotDuration) * renderData.hourCount - 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++){ domConstruct.create("td", null, tr); } }else{ // deletion for(var i=0; i<colDiff; i++){ tr.removeChild(tr.lastChild); } } }); // Set the CSS classes query("tr", table).forEach(function (tr, i){ domStyle.set(tr, "height", renderData.slotSize + "px"); if(i == 0){ domClass.add(tr, "first-child"); }else if(i == nbRows-1){ domClass.add(tr, "last-child"); } // the minutes part of the time of day displayed by the current tr var m = (i * this.renderData.slotDuration) % 60; var h = this.minHours + Math.floor((i * this.renderData.slotDuration) / 60); query("td", tr).forEach(function (td, col){ td.className = ""; if(col == 0){ domClass.add(td, "first-child"); }else if(col == this.renderData.columnCount-1){ domClass.add(td, "last-child"); } var d = renderData.dates[col]; this.styleGridCell(td, d, h, m, renderData); this._addMinutesClasses(td, m); }, 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, hours, minutes, renderData){ // summary: // Styles the CSS classes to the node that displays a cell. // By default this method is setting: // - "dojoxCalendarToday" class name if the date displayed is the current date, // - "dojoxCalendarWeekend" if the date represents a weekend, // - the CSS class corresponding of the displayed day of week ("Sun", "Mon" and so on), // - the CSS classes corresponfing to the time of day (e.g. "H14" and "M30" for for 2:30pm). // node: Node // The DOM node that displays the cell in the grid. // date: Date // The date displayed by this cell. // hours: Integer // The hours part of time of day displayed by the start of this cell. // minutes: Integer // The minutes part of time of day displayed by the start of this cell. // renderData: Object // The render data object. // tags: // protected domClass.add(node, [this._cssDays[date.getDay()], "H"+hours, "M"+minutes]); if(this.isToday(date)){ return domClass.add(node, "dojoxCalendarToday"); } else if(this.isWeekEnd(date)){ return domClass.add(node, "dojoxCalendarWeekend"); } }, styleGridCell: function(node, date, hours, minutes, 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 column // renderData: Object // The render data object. // tags: // protected if(this.styleGridCellFunc){ this.styleGridCellFunc(node, date, hours, minutes, renderData); }else{ this.defaultStyleGridCell(node, date, hours, minutes, 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 bgCols = [], decoCols = []; domStyle.set(table, "height", renderData.sheetHeight + "px"); var oldCount = oldRenderData ? oldRenderData.columnCount : 0; var count = renderData.columnCount - oldCount; 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); 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 trs = query("tr", table); var tbody, tr, td; if (tbodies.length == 1){ tbody = tbodies[0]; }else{ tbody = domConstruct.create("tbody", null, table); } if (trs.length == 1){ tr = trs[0]; }else{ tr = domConstruct.create("tr", null, tbody); } var subCount = renderData.subColumnCount; // Build HTML structure (incremental) if(count>0){ // creation for(var i=0; i < count; i++){ td = domConstruct.create("td", null, tr); domConstruct.create("div", {"className": "dojoxCalendarContainerColumn"}, td); } }else{ // deletion count = -count; for(var i=0; i < count; i++){ tr.removeChild(tr.lastChild); } } query("td", table).forEach(function(td, i){ query(".dojoxCalendarContainerColumn", td).forEach(function(div, i){ domStyle.set(div, "height", renderData.sheetHeight + "px"); var count = query(".dojoxCalendarSubContainerColumn", td).length - subCount; if(count != 0){ var len = div.childNodes.length; for(var i=0; i<len; i++){ div.removeChild(div.lastChild); } for(var j=0; j<subCount; j++){ var subdiv = domConstruct.create("div", {"className": "dojoxCalendarSubContainerColumn"}, div); domConstruct.create("div", {"className": "dojoxCalendarDecorationContainerColumn"}, subdiv); domConstruct.create("div", {"className": "dojoxCalendarEventContainerColumn"}, subdiv); } } }, this); var colW = (100/subCount) + "%"; query(".dojoxCalendarSubContainerColumn", td).forEach(function(div, i){ var col = subCount == 1 ? i : Math.floor(i / subCount); subColIdx = subCount == 1 ? 0 : i - col * subCount; domStyle.set(div, {width: colW, left: ((subColIdx * 100)/subCount)+"%"}); domClass[subColIdx<subCount-1 && subCount !== 1?"add":"remove"](div, "subColumn"); query(".dojoxCalendarEventContainerColumn", div).forEach(function(eventContainer, i){ bgCols.push(eventContainer); }, this); query(".dojoxCalendarDecorationContainerColumn", div).forEach(function(decoContainer, i){ decoCols.push(decoContainer); }, this); }, this); }, this); renderData.cells = bgCols; renderData.decorationCells = decoCols; }, // showTimeIndicator: Boolean // Whether show or not an indicator (default a red line) at the current time. showTimeIndicator: true, // timeIndicatorRefreshInterval: Integer // Maximal interval between two refreshes of time indicator, in milliseconds. timeIndicatorRefreshInterval: 60000, _setShowTimeIndicatorAttr: function(value){ this._set("showTimeIndicator", value); this._layoutTimeIndicator(this.renderData); }, _layoutTimeIndicator: function(renderData){ if(!renderData){ return; } if(this.showTimeIndicator){ var now = new renderData.dateClassObj(); var visible = this.isOverlapping(renderData, renderData.startTime, renderData.endTime, now, now) && now.getHours() >= this.get("minHours") && (now.getHours()*60+now.getMinutes() < this.get("maxHours")*60); if(visible){ if(!this._timeIndicator){ this._timeIndicator = domConstruct.create("div", {"className": "dojoxCalendarTimeIndicator"}); } var node = this._timeIndicator; for(var column=0; column<this.renderData.columnCount; column++){ if(this.isSameDay(now, this.renderData.dates[column])){ break; } } var top = this.computeProjectionOnDate(renderData, this.floorToDay(now), now, renderData.sheetHeight); if(top != renderData.sheetHeight){ domStyle.set(node, {top: top+"px", display: "block"}); var parentNode = renderData.cells[column*renderData.subColumnCount].parentNode.parentNode; if(parentNode != node.parentNode){ if(node.parentNode != null){ node.parentNode.removeChild(node); } parentNode.appendChild(node); } if(this._timeIndicatorTimer == null){ this._timeIndicatorTimer = setInterval(lang.hitch(this, function(){ this._layoutTimeIndicator(this.renderData); }), this.timeIndicatorRefreshInterval); } return; } } } // not visible or specifically not shown fallback if(this._timeIndicatorTimer){ clearInterval(this._timeIndicatorTimer); this._timeIndicatorTimer = null; } if(this._timeIndicator){ domStyle.set(this._timeIndicator, "display", "none"); } }, beforeDeactivate: function(){ if(this._timeIndicatorTimer){ clearInterval(this._timeIndicatorTimer); this._timeIndicatorTimer = null; } }, /////////////////////////////////////////////////////////////// // // Layout // /////////////////////////////////////////////////////////////// _overlapLayoutPass2: function(lanes){ // summary: // Second pass of the overlap layout (optional). Compute the extent of each layout item. // lanes: // The array of lanes. // tags: // private var i,j,lane, layoutItem; // last lane, no extent possible lane = lanes[lanes.length-1]; for(j = 0; j < lane.length; j++){ lane[j].extent = 1; } for(i=0; i<lanes.length-1; i++){ lane = lanes[i]; for(var j=0; j<lane.length; j++){ layoutItem = lane[j]; // if item was already overlapping another one there is no extent possible.