diff options
Diffstat (limited to 'js/dojo/dojox/grid/enhanced/plugins')
31 files changed, 11395 insertions, 0 deletions
diff --git a/js/dojo/dojox/grid/enhanced/plugins/AutoScroll.js b/js/dojo/dojox/grid/enhanced/plugins/AutoScroll.js new file mode 100644 index 0000000..0dcb466 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/AutoScroll.js @@ -0,0 +1,182 @@ +//>>built +define("dojox/grid/enhanced/plugins/AutoScroll", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/html", + "dojo/_base/window", + "../_Plugin", + "../../_RowSelector", + "../../EnhancedGrid" +], function(declare, array, lang, html, win, _Plugin, _RowSelector, EnhancedGrid){ + +var AutoScroll = declare("dojox.grid.enhanced.plugins.AutoScroll", _Plugin, { + // summary: + // Provides horizontal and vertical auto-scroll for grid. + + // name: String + // Plugin name + name: "autoScroll", + + // autoScrollInterval: Integer + // The time interval (in miliseconds) between 2 scrolling. + autoScrollInterval: 1000, + + // autoScrollMargin: Integer + // The width (in pixel) of the margin area where autoscroll can be triggered. + autoScrollMargin: 30, + + constructor: function(grid, args){ + this.grid = grid; + this.readyForAutoScroll = false; + this._scrolling = false; + args = lang.isObject(args) ? args : {}; + if("interval" in args){ + this.autoScrollInterval = args.interval; + } + if("margin" in args){ + this.autoScrollMargin = args.margin; + } + this._initEvents(); + this._mixinGrid(); + }, + _initEvents: function(){ + var g = this.grid; + this.connect(g, "onCellMouseDown", function(){ + this.readyForAutoScroll = true; + }); + this.connect(g, "onHeaderCellMouseDown", function(){ + this.readyForAutoScroll = true; + }); + this.connect(g, "onRowSelectorMouseDown", function(){ + this.readyForAutoScroll = true; + }); + this.connect(win.doc, "onmouseup", function(evt){ + this._manageAutoScroll(true); + this.readyForAutoScroll = false; + }); + this.connect(win.doc, "onmousemove", function(evt){ + if(this.readyForAutoScroll){ + this._event = evt; + var gridPos = html.position(g.domNode), + hh = g._getHeaderHeight(), + margin = this.autoScrollMargin, + ey = evt.clientY, ex = evt.clientX, + gy = gridPos.y, gx = gridPos.x, + gh = gridPos.h, gw = gridPos.w; + if(ex >= gx && ex <= gx + gw){ + if(ey >= gy + hh && ey < gy + hh + margin){ + this._manageAutoScroll(false, true, false); + return; + }else if(ey > gy + gh - margin && ey <= gy + gh){ + this._manageAutoScroll(false, true, true); + return; + }else if(ey >= gy && ey <= gy + gh){ + var withinSomeview = array.some(g.views.views, function(view, i){ + if(view instanceof _RowSelector){ + return false; + } + var viewPos = html.position(view.domNode); + if(ex < viewPos.x + margin && ex >= viewPos.x){ + this._manageAutoScroll(false, false, false, view); + return true; + }else if(ex > viewPos.x + viewPos.w - margin && ex < viewPos.x + viewPos.w){ + this._manageAutoScroll(false, false, true, view); + return true; + } + return false; + }, this); + if(withinSomeview){ + return; + } + } + } + //stop autoscroll. + this._manageAutoScroll(true); + } + }); + }, + _mixinGrid: function(){ + var g = this.grid; + g.onStartAutoScroll = function(/*isVertical, isForward*/){}; + g.onEndAutoScroll = function(/*isVertical, isForward, view, scrollToRowIndex, event*/){}; + }, + _fireEvent: function(eventName, args){ + var g = this.grid; + switch(eventName){ + case "start": + g.onStartAutoScroll.apply(g, args); + break; + case "end": + g.onEndAutoScroll.apply(g, args); + break; + } + }, + _manageAutoScroll: function(toStop, isVertical, isForward, view){ + if(toStop){ + this._scrolling = false; + clearInterval(this._handler); + }else if(!this._scrolling){ + this._scrolling = true; + this._fireEvent("start", [isVertical, isForward, view]); + this._autoScroll(isVertical, isForward, view); + this._handler = setInterval(lang.hitch(this, "_autoScroll", isVertical, isForward, view), this.autoScrollInterval); + } + }, + _autoScroll: function(isVertical, isForward, view){ + var g = this.grid, + target = null; + if(isVertical){ + var targetRow = g.scroller.firstVisibleRow + (isForward ? 1 : -1); + if(targetRow >= 0 && targetRow < g.rowCount){ + g.scrollToRow(targetRow); + target = targetRow; + } + }else{ + target = this._scrollColumn(isForward, view); + } + if(target !== null){ + this._fireEvent("end", [isVertical, isForward, view, target, this._event]); + } + }, + _scrollColumn: function(isForward, view){ + var node = view.scrollboxNode, + target = null; + if(node.clientWidth < node.scrollWidth){ + var cells = array.filter(this.grid.layout.cells, function(cell){ + return !cell.hidden; + }); + var viewPos = html.position(view.domNode); + var limit, edge, headerPos, i; + if(isForward){ + limit = node.clientWidth; + for(i = 0; i < cells.length; ++i){ + headerPos = html.position(cells[i].getHeaderNode()); + edge = headerPos.x - viewPos.x + headerPos.w; + if(edge > limit){ + target = cells[i].index; + node.scrollLeft += edge - limit + 10; + break; + } + } + }else{ + limit = 0; + for(i = cells.length - 1; i >= 0; --i){ + headerPos = html.position(cells[i].getHeaderNode()); + edge = headerPos.x - viewPos.x; + if(edge < limit){ + target = cells[i].index; + node.scrollLeft += edge - limit - 10; + break; + } + } + } + } + return target; + } +}); + +EnhancedGrid.registerPlugin(AutoScroll); + +return AutoScroll; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/CellMerge.js b/js/dojo/dojox/grid/enhanced/plugins/CellMerge.js new file mode 100644 index 0000000..7284ace --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/CellMerge.js @@ -0,0 +1,276 @@ +//>>built +define("dojox/grid/enhanced/plugins/CellMerge", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/html", + "../_Plugin", + "../../EnhancedGrid" +], function(declare, array, lang, html, _Plugin, EnhancedGrid){ + +var CellMerge = declare("dojox.grid.enhanced.plugins.CellMerge", _Plugin, { + // summary: + // This plugin provides functions to merge(un-merge) adjacent cells within one row. + // Acceptable plugin paramters: + // 1. mergedCells: Array + // An array of objects with structure: + // { + // row: function(Integer)|Integer + // If it's a function, it's a predicate to decide which rows are to be merged. + // It takes an integer (the row index), and should return true or false; + // start: Integer + // The column index of the left most cell that shall be merged. + // end: Integer + // The column index of the right most cell that shall be merged. + // major: Integer + // The column index of the cell whose content should be used as the content of the merged cell. + // It must be larger than or equal to the startColumnIndex, and less than or equal to the endColumnIndex. + // If it is omitted, the content of the leading edge (left-most for ltr, right most for rtl) cell will be used. + // } + + // name: String + // Plugin name + name: "cellMerge", + + constructor: function(grid, args){ + this.grid = grid; + this._records = []; + this._merged = {}; + if(args && lang.isObject(args)){ + this._setupConfig(args.mergedCells); + } + this._initEvents(); + this._mixinGrid(); + }, + //----------------Public---------------------------- + mergeCells: function(rowTester, startColumnIndex, endColumnIndex, majorColumnIndex){ + // summary: + // Merge cells from *startColumnIndex* to *endColumnIndex* at rows that make *rowTester* return true, + // using the content of the cell at *majorColumnIndex* + // tags: + // public + // rowTester: function(Integer)|Integer + // If it's a function, it's a predicate to decide which rows are to be merged. + // It takes an integer (the row index), and should return true or false; + // startColumnIndex: Integer + // The column index of the left most cell that shall be merged. + // endColumnIndex: Integer + // The column index of the right most cell that shall be merged. + // majorColumnIndex: Integer? + // The column index of the cell whose content should be used as the content of the merged cell. + // It must be larger than or equal to the startColumnIndex, and less than or equal to the endColumnIndex. + // If it is omitted, the content of the leading edge (left-most for ltr, right most for rtl) cell will be used. + // return: Object | null + // A handler for the merged cells created by a call of this function. + // This handler can be used later to unmerge cells using the function unmergeCells + // If the merge is not valid, returns null; + var item = this._createRecord({ + "row": rowTester, + "start": startColumnIndex, + "end": endColumnIndex, + "major": majorColumnIndex + }); + if(item){ + this._updateRows(item); + } + return item; + }, + unmergeCells: function(mergeHandler){ + // summary: + // Unmerge the cells that are merged by the *mergeHandler*, which represents a call to the function mergeCells. + // tags: + // public + // mergeHandler: object + // A handler for the merged cells created by a call of function mergeCells. + var idx; + if(mergeHandler && (idx = array.indexOf(this._records, mergeHandler)) >= 0){ + this._records.splice(idx, 1); + this._updateRows(mergeHandler); + } + }, + getMergedCells: function(){ + // summary: + // Get all records of currently merged cells. + // tags: + // public + // return: Array + // An array of records for merged-cells. + // The record has the following structure: + // { + // "row": 1, //the row index + // "start": 2, //the start column index + // "end": 4, //the end column index + // "major": 3, //the major column index + // "handle": someHandle, //The handler that covers this merge cell record. + // } + var res = []; + for(var i in this._merged){ + res = res.concat(this._merged[i]); + } + return res; + }, + getMergedCellsByRow: function(rowIndex){ + // summary: + // Get the records of currently merged cells at the given row. + // tags: + // public + // return: Array + // An array of records for merged-cells. See docs of getMergedCells. + return this._merged[rowIndex] || []; + }, + + //----------------Private-------------------------- + _setupConfig: function(config){ + array.forEach(config, this._createRecord, this); + }, + _initEvents: function(){ + array.forEach(this.grid.views.views, function(view){ + this.connect(view, "onAfterRow", lang.hitch(this, "_onAfterRow", view.index)); + }, this); + }, + _mixinGrid: function(){ + var g = this.grid; + g.mergeCells = lang.hitch(this, "mergeCells"); + g.unmergeCells = lang.hitch(this, "unmergeCells"); + g.getMergedCells = lang.hitch(this, "getMergedCells"); + g.getMergedCellsByRow = lang.hitch(this, "getMergedCellsByRow"); + }, + _getWidth: function(colIndex){ + var node = this.grid.layout.cells[colIndex].getHeaderNode(); + return html.position(node).w; + }, + _onAfterRow: function(viewIdx, rowIndex, subrows){ + try{ + if(rowIndex < 0){ + return; + } + var result = [], i, j, len = this._records.length, + cells = this.grid.layout.cells; + //Apply merge-cell requests one by one. + for(i = 0; i < len; ++i){ + var item = this._records[i]; + var storeItem = this.grid._by_idx[rowIndex]; + if(item.view == viewIdx && item.row(rowIndex, storeItem && storeItem.item, this.grid.store)){ + var res = { + record: item, + hiddenCells: [], + totalWidth: 0, + majorNode: cells[item.major].getNode(rowIndex), + majorHeaderNode: cells[item.major].getHeaderNode() + }; + //Calculated the width of merged cell. + for(j = item.start; j <= item.end; ++j){ + var w = this._getWidth(j, rowIndex); + res.totalWidth += w; + if(j != item.major){ + res.hiddenCells.push(cells[j].getNode(rowIndex)); + } + } + //If width is valid, remember it. There may be multiple merges within one row. + if(subrows.length != 1 || res.totalWidth > 0){ + //Remove conflicted merges. + for(j = result.length - 1; j >= 0; --j){ + var r = result[j].record; + if((r.start >= item.start && r.start <= item.end) || + (r.end >= item.start && r.end <= item.end)){ + result.splice(j, 1); + } + } + result.push(res); + } + } + } + this._merged[rowIndex] = []; + array.forEach(result, function(res){ + array.forEach(res.hiddenCells, function(node){ + html.style(node, "display", "none"); + }); + var pbm = html.marginBox(res.majorHeaderNode).w - html.contentBox(res.majorHeaderNode).w; + var tw = res.totalWidth; + + //Tricky for WebKit. + if(!html.isWebKit){ + tw -= pbm; + } + + html.style(res.majorNode, "width", tw + "px"); + //In case we're dealing with multiple subrows. + res.majorNode.setAttribute("colspan", res.hiddenCells.length + 1); + + this._merged[rowIndex].push({ + "row": rowIndex, + "start": res.record.start, + "end": res.record.end, + "major": res.record.major, + "handle": res.record + }); + }, this); + }catch(e){ + console.warn("CellMerge._onAfterRow() error: ", rowIndex, e); + } + }, + _createRecord: function(item){ + if(this._isValid(item)){ + item = { + "row": item.row, + "start": item.start, + "end": item.end, + "major": item.major + }; + var cells = this.grid.layout.cells; + item.view = cells[item.start].view.index; + item.major = typeof item.major == "number" && !isNaN(item.major) ? item.major : item.start; + if(typeof item.row == "number"){ + var r = item.row; + item.row = function(rowIndex){ + return rowIndex === r; + }; + }else if(typeof item.row == "string"){ + var id = item.row; + item.row = function(rowIndex, storeItem, store){ + try{ + if(store && storeItem && store.getFeatures()['dojo.data.api.Identity']){ + return store.getIdentity(storeItem) == id; + } + }catch(e){ + console.error(e); + } + return false; + }; + } + if(lang.isFunction(item.row)){ + this._records.push(item); + return item; + } + } + return null; + }, + _isValid: function(item){ + var cells = this.grid.layout.cells, + colCount = cells.length; + return (lang.isObject(item) && ("row" in item) && ("start" in item) && ("end" in item) && + item.start >= 0 && item.start < colCount && + item.end > item.start && item.end < colCount && + cells[item.start].view.index == cells[item.end].view.index && + cells[item.start].subrow == cells[item.end].subrow && + !(typeof item.major == "number" && (item.major < item.start || item.major > item.end))); + }, + _updateRows: function(item){ + var min = null; + for(var i = 0, count = this.grid.rowCount; i < count; ++i){ + var storeItem = this.grid._by_idx[i]; + if(storeItem && item.row(i, storeItem && storeItem.item, this.grid.store)){ + this.grid.views.updateRow(i); + if(min === null){ min = i; } + } + } + if(min >= 0){ + this.grid.scroller.rowHeightChanged(min); + } + } +}); + +EnhancedGrid.registerPlugin(CellMerge); + +return CellMerge; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/Cookie.js b/js/dojo/dojox/grid/enhanced/plugins/Cookie.js new file mode 100644 index 0000000..d0160f7 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Cookie.js @@ -0,0 +1,362 @@ +//>>built +define("dojox/grid/enhanced/plugins/Cookie", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/sniff", + "dojo/_base/html", + "dojo/_base/json", + "dojo/_base/window", + "dojo/_base/unload", + "dojo/cookie", + "../_Plugin", + "../../_RowSelector", + "../../EnhancedGrid", + "../../cells/_base" +], function(declare, array, lang, has, html, json, win, unload, cookie, _Plugin, _RowSelector, EnhancedGrid){ + + var gridCells = lang.getObject("dojox.grid.cells"); + + // Generate a cookie key for the given grid. + var _cookieKeyBuilder = function(grid){ + return window.location + "/" + grid.id; + }; + + //Utilities: + var _getCellsFromStructure = function(structure){ + var cells = []; + if(!lang.isArray(structure)){ + structure = [structure]; + } + array.forEach(structure,function(viewDef){ + if(lang.isArray(viewDef)){ + viewDef = {"cells" : viewDef}; + } + var rows = viewDef.rows || viewDef.cells; + if(lang.isArray(rows)){ + if(!lang.isArray(rows[0])){ + rows = [rows]; + } + array.forEach(rows, function(row){ + if(lang.isArray(row)){ + array.forEach(row, function(cell){ + cells.push(cell); + }); + } + }); + } + }); + return cells; + }; + + // Persist column width + var _loadColWidth = function(colWidths, grid){ + if(lang.isArray(colWidths)){ + var oldFunc = grid._setStructureAttr; + grid._setStructureAttr = function(structure){ + if(!grid._colWidthLoaded){ + grid._colWidthLoaded = true; + var cells = _getCellsFromStructure(structure); + for(var i = cells.length - 1; i >= 0; --i){ + if(typeof colWidths[i] == "number"){ + cells[i].width = colWidths[i] + "px"; + }else if(colWidths[i] == 'hidden'){ + cells[i].hidden = true; + } + } + } + oldFunc.call(grid, structure); + grid._setStructureAttr = oldFunc; + }; + } + }; + + + var _saveColWidth = function(grid){ + return array.map(array.filter(grid.layout.cells, function(cell){ + return !(cell.isRowSelector || cell instanceof gridCells.RowIndex); + }), function(cell){ + return cell.hidden ? 'hidden' : html[has("webkit") ? "marginBox" : "contentBox"](cell.getHeaderNode()).w; + }); + }; + + // Persist column order + var _loadColumnOrder = function(colOrder, grid){ + if(colOrder && array.every(colOrder, function(viewInfo){ + return lang.isArray(viewInfo) && array.every(viewInfo, function(subrowInfo){ + return lang.isArray(subrowInfo) && subrowInfo.length > 0; + }); + })){ + var oldFunc = grid._setStructureAttr; + var isCell = function(def){ + return ("name" in def || "field" in def || "get" in def); + }; + var isView = function(def){ + return (def !== null && lang.isObject(def) && + ("cells" in def || "rows" in def || ("type" in def && !isCell(def)))); + }; + grid._setStructureAttr = function(structure){ + if(!grid._colOrderLoaded){ + grid._colOrderLoaded = true; + grid._setStructureAttr = oldFunc; + structure = lang.clone(structure); + if(lang.isArray(structure) && !array.some(structure, isView)){ + structure = [{ cells: structure }]; + }else if(isView(structure)){ + structure = [structure]; + } + var cells = _getCellsFromStructure(structure); + array.forEach(lang.isArray(structure) ? structure : [structure], function(viewDef, viewIdx){ + var cellArray = viewDef; + if(lang.isArray(viewDef)){ + viewDef.splice(0, viewDef.length); + }else{ + delete viewDef.rows; + cellArray = viewDef.cells = []; + } + array.forEach(colOrder[viewIdx], function(subrow){ + array.forEach(subrow, function(cellInfo){ + var i, cell; + for(i = 0; i < cells.length; ++i){ + cell = cells[i]; + if(json.toJson({'name':cell.name,'field':cell.field}) == json.toJson(cellInfo)){ + break; + } + } + if(i < cells.length){ + cellArray.push(cell); + } + }); + }); + }); + } + oldFunc.call(grid, structure); + }; + } + }; + + var _saveColumnOrder = function(grid){ + var colOrder = array.map(array.filter(grid.views.views, function(view){ + return !(view instanceof _RowSelector); + }), function(view){ + return array.map(view.structure.cells, function(subrow){ + return array.map(array.filter(subrow, function(cell){ + return !(cell.isRowSelector || cell instanceof gridCells.RowIndex); + }), function(cell){ + return { + "name": cell.name, + "field": cell.field + }; + }); + }); + }); + return colOrder; + }; + + // Persist sorting order + var _loadSortOrder = function(sortOrder, grid){ + try{ + if(lang.isObject(sortOrder)){ + grid.setSortIndex(sortOrder.idx, sortOrder.asc); + } + }catch(e){ + //setSortIndex will finally call _fetch, some exceptions will be throw + //'cause the grid hasn't be fully loaded now. Just ignore them. + } + }; + + var _saveSortOrder = function(grid){ + return { + idx: grid.getSortIndex(), + asc: grid.getSortAsc() + }; + }; + + if(!has("ie")){ + // Now in non-IE, widgets are no longer destroyed on page unload, + // so we have to destroy it manually to trigger saving cookie. + unload.addOnWindowUnload(function(){ + array.forEach(dijit.findWidgets(win.body()), function(widget){ + if(widget instanceof EnhancedGrid && !widget._destroyed){ + widget.destroyRecursive(); + } + }); + }); + } + + var Cookie = declare("dojox.grid.enhanced.plugins.Cookie", _Plugin, { + // summary: + // This plugin provides a way to persist some grid features in cookie. + // Default persistable features are: + // column width: "columnWidth" (handler name) + // column order: "columnOrder" + // sorting order: "sortOrder" + // + // Grid users can define new persistable features + // by calling the following before grid is initialized (that is, during "preInit"); + // | grid.addCookieHandler({ + // | name: "a name for the new persistable feature", + // | onLoad: function(savedObject, grid){ + // | //load the cookie. + // | }, + // | onSave: function(grid){ + // | //save the cookie. + // | } + // | }); + + // name: String + // Plugin name + name: "cookie", + + _cookieEnabled: true, + + constructor: function(grid, args){ + this.grid = grid; + args = (args && lang.isObject(args)) ? args : {}; + this.cookieProps = args.cookieProps; + this._cookieHandlers = []; + this._mixinGrid(); + + //Column width & simple sorting & column reorder are base grid features, so they must be supported. + this.addCookieHandler({ + name: "columnWidth", + onLoad: _loadColWidth, + onSave: _saveColWidth + }); + this.addCookieHandler({ + name: "columnOrder", + onLoad: _loadColumnOrder, + onSave: _saveColumnOrder + }); + this.addCookieHandler({ + name: "sortOrder", + onLoad: _loadSortOrder, + onSave: _saveSortOrder + }); + + array.forEach(this._cookieHandlers, function(handler){ + if(args[handler.name] === false){ + handler.enable = false; + } + }, this); + }, + destroy:function(){ + this._saveCookie(); + this._cookieHandlers = null; + this.inherited(arguments); + }, + _mixinGrid: function(){ + var g = this.grid; + g.addCookieHandler = lang.hitch(this, "addCookieHandler"); + g.removeCookie = lang.hitch(this, "removeCookie"); + g.setCookieEnabled = lang.hitch(this, "setCookieEnabled"); + g.getCookieEnabled = lang.hitch(this, "getCookieEnabled"); + }, + _saveCookie: function(){ + if(this.getCookieEnabled()){ + var ck = {}, + chs = this._cookieHandlers, + cookieProps = this.cookieProps, + cookieKey = _cookieKeyBuilder(this.grid); + for(var i = chs.length-1; i >= 0; --i){ + if(chs[i].enabled){ + //Do the real saving work here. + ck[chs[i].name] = chs[i].onSave(this.grid); + } + } + cookieProps = lang.isObject(this.cookieProps) ? this.cookieProps : {}; + cookie(cookieKey, json.toJson(ck), cookieProps); + }else{ + this.removeCookie(); + } + }, + onPreInit: function(){ + var grid = this.grid, + chs = this._cookieHandlers, + cookieKey = _cookieKeyBuilder(grid), + ck = cookie(cookieKey); + if(ck){ + ck = json.fromJson(ck); + for(var i = 0; i < chs.length; ++i){ + if(chs[i].name in ck && chs[i].enabled){ + //Do the real loading work here. + chs[i].onLoad(ck[chs[i].name], grid); + } + } + } + this._cookie = ck || {}; + this._cookieStartedup = true; + }, + addCookieHandler: function(args){ + // summary: + // If a grid plugin wants cookie service, call this. + // This must be called during preInit. + // args: Object + // An object with the following structure: + // | { + // | name: "some-string", + // | onLoad: /* void */ function(/* object */partOfCookie, /* EDG */grid){...}, + // | onSave: /* object */ function(/* EDG */grid){...} + // | } + if(args.name){ + var dummy = function(){}; + args.onLoad = args.onLoad || dummy; + args.onSave = args.onSave || dummy; + if(!("enabled" in args)){ + args.enabled = true; + } + for(var i = this._cookieHandlers.length - 1; i >= 0; --i){ + if(this._cookieHandlers[i].name == args.name){ + this._cookieHandlers.splice(i, 1); + } + } + this._cookieHandlers.push(args); + if(this._cookieStartedup && args.name in this._cookie){ + args.onLoad(this._cookie[args.name], this.grid); + } + } + }, + removeCookie: function(){ + // summary: + // Remove cookie for this grid. + var key = _cookieKeyBuilder(this.grid); + cookie(key, null, {expires: -1}); + }, + setCookieEnabled: function(cookieName, enabled){ + // summary: + // A setter to enable|disable cookie support for a particular Grid feature. + // cookieName: String? + // Name of a cookie handler if provided, otherwise for all cookies. + // enabled: Boolean + if(typeof cookieName == 'string'){ + var chs = this._cookieHandlers; + for(var i = chs.length - 1; i >= 0; --i){ + if(chs[i].name === cookieName){ + chs[i].enabled = !!enabled; + } + } + }else{ + this._cookieEnabled = !!cookieName; + if(!this._cookieEnabled){ this.removeCookie(); } + } + }, + getCookieEnabled: function(cookieName){ + // summary: + // A getter to check cookie support of a particular Grid feature. + // cookieName: String? + // Name of a cookie handler if provided, otherwise for all cookies. + if(lang.isString(cookieName)){ + var chs = this._cookieHandlers; + for(var i = chs.length - 1; i >= 0; --i){ + if(chs[i].name == cookieName){ return chs[i].enabled; } + } + return false; + } + return this._cookieEnabled; + } + }); + + EnhancedGrid.registerPlugin(Cookie, {"preInit": true}); + + return Cookie; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/Dialog.js b/js/dojo/dojox/grid/enhanced/plugins/Dialog.js new file mode 100644 index 0000000..97dbbd4 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Dialog.js @@ -0,0 +1,40 @@ +//>>built +define("dojox/grid/enhanced/plugins/Dialog", [ + "dojo/_base/declare", + "dojo/_base/html", + "dojo/window", + "dijit/Dialog" +], function(declare, html, win, Dialog){ + +return declare("dojox.grid.enhanced.plugins.Dialog", Dialog, { + refNode: null, + _position: function(){ + if(this.refNode && !this._relativePosition){ + var refPos = html.position(html.byId(this.refNode)), + thisPos = html.position(this.domNode), + viewPort = win.getBox(); + if(thisPos.w && thisPos.h){ + if(refPos.x < 0){ + refPos.x = 0; + } + if(refPos.x + refPos.w > viewPort.w){ + refPos.w = viewPort.w - refPos.x; + } + if(refPos.y < 0){ + refPos.y = 0; + } + if(refPos.y + refPos.h > viewPort.h){ + refPos.h = viewPort.h - refPos.y; + } + refPos.x = refPos.x + refPos.w / 2 - thisPos.w / 2; + refPos.y = refPos.y + refPos.h / 2 - thisPos.h / 2; + if(refPos.x >= 0 && refPos.x + thisPos.w <= viewPort.w && + refPos.y >= 0 && refPos.y + thisPos.h <= viewPort.h){ + this._relativePosition = refPos; + } + } + } + this.inherited(arguments); + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/DnD.js b/js/dojo/dojox/grid/enhanced/plugins/DnD.js new file mode 100644 index 0000000..e9306ed --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/DnD.js @@ -0,0 +1,1094 @@ +//>>built +define("dojox/grid/enhanced/plugins/DnD", [ + "dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/connect", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/html", + "dojo/_base/json", + "dojo/_base/window", + "dojo/query", + "dojo/keys", + "dojo/dnd/Source", + "dojo/dnd/Avatar", + "../_Plugin", + "../../EnhancedGrid", + "./Selector", + "./Rearrange", + "dojo/dnd/Manager" +], function(dojo, declare, connect, array, lang, html, json, win, query, keys, Source, Avatar, _Plugin, EnhancedGrid){ + +var _devideToArrays = function(a){ + a.sort(function(v1, v2){ + return v1 - v2; + }); + var arr = [[a[0]]]; + for(var i = 1, j = 0; i < a.length; ++i){ + if(a[i] == a[i-1] + 1){ + arr[j].push(a[i]); + }else{ + arr[++j] = [a[i]]; + } + } + return arr; + }, + _joinToArray = function(arrays){ + var a = arrays[0]; + for(var i = 1; i < arrays.length; ++i){ + a = a.concat(arrays[i]); + } + return a; + }; +var GridDnDElement = declare("dojox.grid.enhanced.plugins.GridDnDElement", null, { + constructor: function(dndPlugin){ + this.plugin = dndPlugin; + this.node = html.create("div"); + this._items = {}; + }, + destroy: function(){ + this.plugin = null; + html.destroy(this.node); + this.node = null; + this._items = null; + }, + createDnDNodes: function(dndRegion){ + this.destroyDnDNodes(); + var acceptType = ["grid/" + dndRegion.type + "s"]; + var itemNodeIdBase = this.plugin.grid.id + "_dndItem"; + array.forEach(dndRegion.selected, function(range, i){ + var id = itemNodeIdBase + i; + this._items[id] = { + "type": acceptType, + "data": range, + "dndPlugin": this.plugin + }; + this.node.appendChild(html.create("div", { + "id": id + })); + }, this); + }, + getDnDNodes: function(){ + return array.map(this.node.childNodes, function(node){ + return node; + }); + }, + destroyDnDNodes: function(){ + html.empty(this.node); + this._items = {}; + }, + getItem: function(nodeId){ + return this._items[nodeId]; + } +}); +var GridDnDSource = declare("dojox.grid.enhanced.plugins.GridDnDSource", Source,{ + accept: ["grid/cells", "grid/rows", "grid/cols"], + constructor: function(node, param){ + this.grid = param.grid; + this.dndElem = param.dndElem; + this.dndPlugin = param.dnd; + this.sourcePlugin = null; + }, + destroy: function(){ + this.inherited(arguments); + this.grid = null; + this.dndElem = null; + this.dndPlugin = null; + this.sourcePlugin = null; + }, + getItem: function(nodeId){ + return this.dndElem.getItem(nodeId); + }, + checkAcceptance: function(source, nodes){ + if(this != source && nodes[0]){ + var item = source.getItem(nodes[0].id); + if(item.dndPlugin){ + var type = item.type; + for(var j = 0; j < type.length; ++j){ + if(type[j] in this.accept){ + if(this.dndPlugin._canAccept(item.dndPlugin)){ + this.sourcePlugin = item.dndPlugin; + }else{ + return false; + } + break; + } + } + }else if("grid/rows" in this.accept){ + var rows = []; + array.forEach(nodes, function(node){ + var item = source.getItem(node.id); + if(item.data && array.indexOf(item.type, "grid/rows") >= 0){ + var rowData = item.data; + if(typeof item.data == "string"){ + rowData = json.fromJson(item.data); + } + if(rowData){ + rows.push(rowData); + } + } + }); + if(rows.length){ + this.sourcePlugin = { + _dndRegion: { + type: "row", + selected: [rows] + } + }; + }else{ + return false; + } + } + } + return this.inherited(arguments); + }, + onDraggingOver: function(){ + this.dndPlugin.onDraggingOver(this.sourcePlugin); + }, + onDraggingOut: function(){ + this.dndPlugin.onDraggingOut(this.sourcePlugin); + }, + onDndDrop: function(source, nodes, copy, target){ + //this.inherited(arguments); + this.onDndCancel(); + if(this != source && this == target){ + this.dndPlugin.onDragIn(this.sourcePlugin, copy); + } + } +}); + +var GridDnDAvatar = declare("dojox.grid.enhanced.plugins.GridDnDAvatar", Avatar, { + construct: function(){ + // summary: + // constructor function; + // it is separate so it can be (dynamically) overwritten in case of need + this._itemType = this.manager._dndPlugin._dndRegion.type; + this._itemCount = this._getItemCount(); + + this.isA11y = html.hasClass(win.body(), "dijit_a11y"); + var a = html.create("table", { + "border": "0", + "cellspacing": "0", + "class": "dojoxGridDndAvatar", + "style": { + position: "absolute", + zIndex: "1999", + margin: "0px" + } + }), + source = this.manager.source, + b = html.create("tbody", null, a), + tr = html.create("tr", null, b), + td = html.create("td", { + "class": "dojoxGridDnDIcon" + }, tr); + if(this.isA11y){ + html.create("span", { + "id" : "a11yIcon", + "innerHTML" : this.manager.copy ? '+' : "<" + }, td); + } + td = html.create("td", { + "class" : "dojoxGridDnDItemIcon " + this._getGridDnDIconClass() + }, tr); + td = html.create("td", null, tr); + html.create("span", { + "class": "dojoxGridDnDItemCount", + "innerHTML": source.generateText ? this._generateText() : "" + }, td); + // we have to set the opacity on IE only after the node is live + html.style(tr, { + "opacity": 0.9 + }); + this.node = a; + }, + _getItemCount: function(){ + var selected = this.manager._dndPlugin._dndRegion.selected, + count = 0; + switch(this._itemType){ + case "cell": + selected = selected[0]; + var cells = this.manager._dndPlugin.grid.layout.cells, + colCount = selected.max.col - selected.min.col + 1, + rowCount = selected.max.row - selected.min.row + 1; + if(colCount > 1){ + for(var i = selected.min.col; i <= selected.max.col; ++i){ + if(cells[i].hidden){ + --colCount; + } + } + } + count = colCount * rowCount; + break; + case "row": + case "col": + count = _joinToArray(selected).length; + } + return count; + }, + _getGridDnDIconClass: function(){ + return { + "row": ["dojoxGridDnDIconRowSingle", "dojoxGridDnDIconRowMulti"], + "col": ["dojoxGridDnDIconColSingle", "dojoxGridDnDIconColMulti"], + "cell": ["dojoxGridDnDIconCellSingle", "dojoxGridDnDIconCellMulti"] + }[this._itemType][this._itemCount == 1 ? 0 : 1]; + }, + _generateText: function(){ + // summary: + // generates a proper text to reflect copying or moving of items + return "(" + this._itemCount + ")"; + } +}); +var DnD = declare("dojox.grid.enhanced.plugins.DnD", _Plugin, { + // summary: + // Provide drag and drop for grid columns/rows/cells within grid and out of grid. + // The store of grid must implement dojo.data.api.Write. + // DnD selected columns: + // Support moving within grid, moving/copying out of grid to a non-grid DnD target. + // DnD selected rows: + // Support moving within grid, moving/copying out of grid to any DnD target. + // DnD selected cells (in rectangle shape only): + // Support moving/copying within grid, moving/copying out of grid to any DnD target. + // + + // name: String, + // plugin name; + name: "dnd", + + _targetAnchorBorderWidth: 2, + _copyOnly: false, + _config: { + "row":{ + "within":true, + "in":true, + "out":true + }, + "col":{ + "within":true, + "in":true, + "out":true + }, + "cell":{ + "within":true, + "in":true, + "out":true + } + }, + constructor: function(grid, args){ + this.grid = grid; + this._config = lang.clone(this._config); + args = lang.isObject(args) ? args : {}; + this.setupConfig(args.dndConfig); + this._copyOnly = !!args.copyOnly; + + //Get the plugins we are dependent on. + this._mixinGrid(); + this.selector = grid.pluginMgr.getPlugin("selector"); + this.rearranger = grid.pluginMgr.getPlugin("rearrange"); + //TODO: waiting for a better plugin framework to pass args to dependent plugins. + this.rearranger.setArgs(args); + + //Initialized the components we need. + this._clear(); + this._elem = new GridDnDElement(this); + this._source = new GridDnDSource(this._elem.node, { + "grid": grid, + "dndElem": this._elem, + "dnd": this + }); + this._container = query(".dojoxGridMasterView", this.grid.domNode)[0]; + this._initEvents(); + }, + destroy: function(){ + this.inherited(arguments); + this._clear(); + this._source.destroy(); + this._elem.destroy(); + this._container = null; + this.grid = null; + this.selector = null; + this.rearranger = null; + this._config = null; + }, + _mixinGrid: function(){ + // summary: + // Provide APIs for grid. + this.grid.setupDnDConfig = lang.hitch(this, "setupConfig"); + this.grid.dndCopyOnly = lang.hitch(this, "copyOnly"); + }, + setupConfig: function(config){ + // summary: + // Configure which DnD functionalities are needed. + // Combination of any item from type set ("row", "col", "cell") + // and any item from mode set("within", "in", "out") is configurable. + // + // "row", "col", "cell" are straitforward, while the other 3 are explained below: + // "within": DnD within grid, that is, column/row reordering and cell moving/copying. + // "in": Whether allowed to accept rows/cells (currently not support columns) from another grid. + // "out": Whether allowed to drag out of grid, to another grid or even to any other DnD target. + // + // If not provided in the config, will use the default. + // When declared together, Mode set has higher priority than type set. + // config: Object + // DnD configuration object. + // See the examples below. + // example: + // The following code disables row DnD within grid, + // but still can drag rows out of grid or drag rows from other gird. + // | setUpConfig({ + // | "row": { + // | "within": false + // | } + // | }); + // + // The opposite way is also okay: + // | setUpConfig({ + // | "within": { + // | "row": false + // | } + // | }); + // + // And if you'd like to disable/enable a whole set, here's a shortcut: + // | setUpConfig({ + // | "cell", true, + // | "out": false + // | }); + // + // Because mode has higher priority than type, the following will disable row dnd within grid: + // | setUpConfig({ + // | "within", { + // | "row": false; + // | }, + // | "row", { + // | "within": true + // | } + // | }); + if(config && lang.isObject(config)){ + var firstLevel = ["row", "col", "cell"], + secondLevel = ["within", "in", "out"], + cfg = this._config; + array.forEach(firstLevel, function(type){ + if(type in config){ + var t = config[type]; + if(t && lang.isObject(t)){ + array.forEach(secondLevel, function(mode){ + if(mode in t){ + cfg[type][mode] = !!t[mode]; + } + }); + }else{ + array.forEach(secondLevel, function(mode){ + cfg[type][mode] = !!t; + }); + } + } + }); + array.forEach(secondLevel, function(mode){ + if(mode in config){ + var m = config[mode]; + if(m && lang.isObject(m)){ + array.forEach(firstLevel, function(type){ + if(type in m){ + cfg[type][mode] = !!m[type]; + } + }); + }else{ + array.forEach(firstLevel, function(type){ + cfg[type][mode] = !!m; + }); + } + } + }); + } + }, + copyOnly: function(isCopyOnly){ + // summary: + // Setter/getter of this._copyOnly. + if(typeof isCopyOnly != "undefined"){ + this._copyOnly = !!isCopyOnly; + } + return this._copyOnly; + }, + _isOutOfGrid: function(evt){ + var gridPos = html.position(this.grid.domNode), x = evt.clientX, y = evt.clientY; + return y < gridPos.y || y > gridPos.y + gridPos.h || + x < gridPos.x || x > gridPos.x + gridPos.w; + }, + _onMouseMove: function(evt){ + if(this._dndRegion && !this._dnding && !this._externalDnd){ + this._dnding = true; + this._startDnd(evt); + }else{ + if(this._isMouseDown && !this._dndRegion){ + delete this._isMouseDown; + this._oldCursor = html.style(win.body(), "cursor"); + html.style(win.body(), "cursor", "not-allowed"); + } + //TODO: should implement as mouseenter/mouseleave + //But we have an avatar under mouse when dnd, and this will cause a lot of mouseenter in FF. + var isOut = this._isOutOfGrid(evt); + if(!this._alreadyOut && isOut){ + this._alreadyOut = true; + if(this._dnding){ + this._destroyDnDUI(true, false); + } + this._moveEvent = evt; + this._source.onOutEvent(); + }else if(this._alreadyOut && !isOut){ + this._alreadyOut = false; + if(this._dnding){ + this._createDnDUI(evt, true); + } + this._moveEvent = evt; + this._source.onOverEvent(); + } + } + }, + _onMouseUp: function(){ + if(!this._extDnding && !this._isSource){ + var isInner = this._dnding && !this._alreadyOut; + if(isInner && this._config[this._dndRegion.type]["within"]){ + this._rearrange(); + } + this._endDnd(isInner); + } + html.style(win.body(), "cursor", this._oldCursor || ""); + delete this._isMouseDown; + }, + _initEvents: function(){ + var g = this.grid, s = this.selector; + this.connect(win.doc, "onmousemove", "_onMouseMove"); + this.connect(win.doc, "onmouseup", "_onMouseUp"); + + this.connect(g, "onCellMouseOver", function(evt){ + if(!this._dnding && !s.isSelecting() && !evt.ctrlKey){ + this._dndReady = s.isSelected("cell", evt.rowIndex, evt.cell.index); + s.selectEnabled(!this._dndReady); + } + }); + this.connect(g, "onHeaderCellMouseOver", function(evt){ + if(this._dndReady){ + s.selectEnabled(true); + } + }); + this.connect(g, "onRowMouseOver", function(evt){ + if(this._dndReady && !evt.cell){ + s.selectEnabled(true); + } + }); + this.connect(g, "onCellMouseDown", function(evt){ + if(!evt.ctrlKey && this._dndReady){ + this._dndRegion = this._getDnDRegion(evt.rowIndex, evt.cell.index); + this._isMouseDown = true; + } + }); + this.connect(g, "onCellMouseUp", function(evt){ + if(!this._dndReady && !s.isSelecting() && evt.cell){ + this._dndReady = s.isSelected("cell", evt.rowIndex, evt.cell.index); + s.selectEnabled(!this._dndReady); + } + }); + this.connect(g, "onCellClick", function(evt){ + if(this._dndReady && !evt.ctrlKey && !evt.shiftKey){ + s.select("cell", evt.rowIndex, evt.cell.index); + } + }); + this.connect(g, "onEndAutoScroll", function(isVertical, isForward, view, target, evt){ + if(this._dnding){ + this._markTargetAnchor(evt); + } + }); + this.connect(win.doc, "onkeydown", function(evt){ + if(evt.keyCode == keys.ESCAPE){ + this._endDnd(false); + }else if(evt.keyCode == keys.CTRL){ + s.selectEnabled(true); + this._isCopy = true; + } + }); + this.connect(win.doc, "onkeyup", function(evt){ + if(evt.keyCode == keys.CTRL){ + s.selectEnabled(!this._dndReady); + this._isCopy = false; + } + }); + }, + _clear: function(){ + this._dndRegion = null; + this._target = null; + this._moveEvent = null; + this._targetAnchor = {}; + this._dnding = false; + this._externalDnd = false; + this._isSource = false; + this._alreadyOut = false; + this._extDnding = false; + }, + _getDnDRegion: function(rowIndex, colIndex){ + var s = this.selector, + selected = s._selected, + flag = (!!selected.cell.length) | (!!selected.row.length << 1) | (!!selected.col.length << 2), + type; + switch(flag){ + case 1: + type = "cell"; + if(!this._config[type]["within"] && !this._config[type]["out"]){ + return null; + } + var cells = this.grid.layout.cells, + getCount = function(range){ + var hiddenColCnt = 0; + for(var i = range.min.col; i <= range.max.col; ++i){ + if(cells[i].hidden){ + ++hiddenColCnt; + } + } + return (range.max.row - range.min.row + 1) * (range.max.col - range.min.col + 1 - hiddenColCnt); + }, + inRange = function(item, range){ + return item.row >= range.min.row && item.row <= range.max.row && + item.col >= range.min.col && item.col <= range.max.col; + }, + range = { + max: { + row: -1, + col: -1 + }, + min: { + row: Infinity, + col: Infinity + } + }; + + array.forEach(selected[type], function(item){ + if(item.row < range.min.row){ + range.min.row = item.row; + } + if(item.row > range.max.row){ + range.max.row = item.row; + } + if(item.col < range.min.col){ + range.min.col = item.col; + } + if(item.col > range.max.col){ + range.max.col = item.col; + } + }); + if(array.some(selected[type], function(item){ + return item.row == rowIndex && item.col == colIndex; + })){ + if(getCount(range) == selected[type].length && array.every(selected[type], function(item){ + return inRange(item, range); + })){ + return { + "type": type, + "selected": [range], + "handle": { + "row": rowIndex, + "col": colIndex + } + }; + } + } + return null; + case 2: case 4: + type = flag == 2 ? "row" : "col"; + if(!this._config[type]["within"] && !this._config[type]["out"]){ + return null; + } + var res = s.getSelected(type); + if(res.length){ + return { + "type": type, + "selected": _devideToArrays(res), + "handle": flag == 2 ? rowIndex : colIndex + }; + } + return null; + } + return null; + }, + _startDnd: function(evt){ + this._createDnDUI(evt); + }, + _endDnd: function(destroySource){ + this._destroyDnDUI(false, destroySource); + this._clear(); + }, + _createDnDUI: function(evt, isMovingIn){ + //By default the master view of grid do not have height, because the children in it are all positioned absolutely. + //But we need it to contain avatars. + var viewPos = html.position(this.grid.views.views[0].domNode); + html.style(this._container, "height", viewPos.h + "px"); + try{ + //If moving in from out side, dnd source is already created. + if(!isMovingIn){ + this._createSource(evt); + } + this._createMoveable(evt); + this._oldCursor = html.style(win.body(), "cursor"); + html.style(win.body(), "cursor", "default"); + }catch(e){ + console.warn("DnD._createDnDUI() error:", e); + } + }, + _destroyDnDUI: function(isMovingOut, destroySource){ + try{ + if(destroySource){ + this._destroySource(); + } + this._unmarkTargetAnchor(); + if(!isMovingOut){ + this._destroyMoveable(); + } + html.style(win.body(), "cursor", this._oldCursor); + }catch(e){ + console.warn("DnD._destroyDnDUI() error:", this.grid.id, e); + } + }, + _createSource: function(evt){ + this._elem.createDnDNodes(this._dndRegion); + var m = dojo.dnd.manager(); + var oldMakeAvatar = m.makeAvatar; + m._dndPlugin = this; + m.makeAvatar = function(){ + var avatar = new GridDnDAvatar(m); + delete m._dndPlugin; + return avatar; + }; + m.startDrag(this._source, this._elem.getDnDNodes(), evt.ctrlKey); + m.makeAvatar = oldMakeAvatar; + m.onMouseMove(evt); + }, + _destroySource: function(){ + connect.publish("/dnd/cancel"); + }, + _createMoveable: function(evt){ + if(!this._markTagetAnchorHandler){ + this._markTagetAnchorHandler = this.connect(win.doc, "onmousemove", "_markTargetAnchor"); + } + }, + _destroyMoveable: function(){ + this.disconnect(this._markTagetAnchorHandler); + delete this._markTagetAnchorHandler; + }, + _calcColTargetAnchorPos: function(evt, containerPos){ + // summary: + // Calculate the position of the column DnD avatar + var i, headPos, left, target, ex = evt.clientX, + cells = this.grid.layout.cells, + ltr = html._isBodyLtr(), + headers = this._getVisibleHeaders(); + for(i = 0; i < headers.length; ++i){ + headPos = html.position(headers[i].node); + if(ltr ? ((i === 0 || ex >= headPos.x) && ex < headPos.x + headPos.w) : + ((i === 0 || ex < headPos.x + headPos.w) && ex >= headPos.x)){ + left = headPos.x + (ltr ? 0 : headPos.w); + break; + }else if(ltr ? (i === headers.length - 1 && ex >= headPos.x + headPos.w) : + (i === headers.length - 1 && ex < headPos.x)){ + ++i; + left = headPos.x + (ltr ? headPos.w : 0); + break; + } + } + if(i < headers.length){ + target = headers[i].cell.index; + if(this.selector.isSelected("col", target) && this.selector.isSelected("col", target - 1)){ + var ranges = this._dndRegion.selected; + for(i = 0; i < ranges.length; ++i){ + if(array.indexOf(ranges[i], target) >= 0){ + target = ranges[i][0]; + headPos = html.position(cells[target].getHeaderNode()); + left = headPos.x + (ltr ? 0 : headPos.w); + break; + } + } + } + }else{ + target = cells.length; + } + this._target = target; + return left - containerPos.x; + }, + _calcRowTargetAnchorPos: function(evt, containerPos){ + // summary: + // Calculate the position of the row DnD avatar + var g = this.grid, top, i = 0, + cells = g.layout.cells; + while(cells[i].hidden){ ++i; } + var cell = g.layout.cells[i], + rowIndex = g.scroller.firstVisibleRow, + cellNode = cell.getNode(rowIndex); + if(!cellNode){ + //if the target grid is empty, set to -1 + //which will be processed in Rearrange + this._target = -1; + return 0; //position of the insert bar + } + var nodePos = html.position(cellNode); + while(nodePos.y + nodePos.h < evt.clientY){ + if(++rowIndex >= g.rowCount){ + break; + } + nodePos = html.position(cell.getNode(rowIndex)); + } + if(rowIndex < g.rowCount){ + if(this.selector.isSelected("row", rowIndex) && this.selector.isSelected("row", rowIndex - 1)){ + var ranges = this._dndRegion.selected; + for(i = 0; i < ranges.length; ++i){ + if(array.indexOf(ranges[i], rowIndex) >= 0){ + rowIndex = ranges[i][0]; + nodePos = html.position(cell.getNode(rowIndex)); + break; + } + } + } + top = nodePos.y; + }else{ + top = nodePos.y + nodePos.h; + } + this._target = rowIndex; + return top - containerPos.y; + }, + _calcCellTargetAnchorPos: function(evt, containerPos, targetAnchor){ + // summary: + // Calculate the position of the cell DnD avatar + var s = this._dndRegion.selected[0], + origin = this._dndRegion.handle, + g = this.grid, ltr = html._isBodyLtr(), + cells = g.layout.cells, headPos, + minPos, maxPos, headers, + height, width, left, top, + minCol, maxCol, i, + preSpan = origin.col - s.min.col, + postSpan = s.max.col - origin.col, + leftTopDiv, rightBottomDiv; + if(!targetAnchor.childNodes.length){ + leftTopDiv = html.create("div", { + "class": "dojoxGridCellBorderLeftTopDIV" + }, targetAnchor); + rightBottomDiv = html.create("div", { + "class": "dojoxGridCellBorderRightBottomDIV" + }, targetAnchor); + }else{ + leftTopDiv = query(".dojoxGridCellBorderLeftTopDIV", targetAnchor)[0]; + rightBottomDiv = query(".dojoxGridCellBorderRightBottomDIV", targetAnchor)[0]; + } + for(i = s.min.col + 1; i < origin.col; ++i){ + if(cells[i].hidden){ + --preSpan; + } + } + for(i = origin.col + 1; i < s.max.col; ++i){ + if(cells[i].hidden){ + --postSpan; + } + } + headers = this._getVisibleHeaders(); + //calc width + for(i = preSpan; i < headers.length - postSpan; ++i){ + headPos = html.position(headers[i].node); + if((evt.clientX >= headPos.x && evt.clientX < headPos.x + headPos.w) || //within in this column + //prior to this column, but within range + (i == preSpan && (ltr ? evt.clientX < headPos.x : evt.clientX >= headPos.x + headPos.w)) || + //post to this column, but within range + (i == headers.length - postSpan - 1 && (ltr ? evt.clientX >= headPos.x + headPos.w : evt < headPos.x))){ + minCol = headers[i - preSpan]; + maxCol = headers[i + postSpan]; + minPos = html.position(minCol.node); + maxPos = html.position(maxCol.node); + minCol = minCol.cell.index; + maxCol = maxCol.cell.index; + left = ltr ? minPos.x : maxPos.x; + width = ltr ? (maxPos.x + maxPos.w - minPos.x) : (minPos.x + minPos.w - maxPos.x); + break; + } + } + //calc height + i = 0; + while(cells[i].hidden){ ++i; } + var cell = cells[i], + rowIndex = g.scroller.firstVisibleRow, + nodePos = html.position(cell.getNode(rowIndex)); + while(nodePos.y + nodePos.h < evt.clientY){ + if(++rowIndex < g.rowCount){ + nodePos = html.position(cell.getNode(rowIndex)); + }else{ + break; + } + } + var minRow = rowIndex >= origin.row - s.min.row ? rowIndex - origin.row + s.min.row : 0; + var maxRow = minRow + s.max.row - s.min.row; + if(maxRow >= g.rowCount){ + maxRow = g.rowCount - 1; + minRow = maxRow - s.max.row + s.min.row; + } + minPos = html.position(cell.getNode(minRow)); + maxPos = html.position(cell.getNode(maxRow)); + top = minPos.y; + height = maxPos.y + maxPos.h - minPos.y; + this._target = { + "min":{ + "row": minRow, + "col": minCol + }, + "max":{ + "row": maxRow, + "col": maxCol + } + }; + var anchorBorderSize = (html.marginBox(leftTopDiv).w - html.contentBox(leftTopDiv).w) / 2; + var leftTopCellPos = html.position(cells[minCol].getNode(minRow)); + html.style(leftTopDiv, { + "width": (leftTopCellPos.w - anchorBorderSize) + "px", + "height": (leftTopCellPos.h - anchorBorderSize) + "px" + }); + var rightBottomCellPos = html.position(cells[maxCol].getNode(maxRow)); + html.style(rightBottomDiv, { + "width": (rightBottomCellPos.w - anchorBorderSize) + "px", + "height": (rightBottomCellPos.h - anchorBorderSize) + "px" + }); + return { + h: height, + w: width, + l: left - containerPos.x, + t: top - containerPos.y + }; + }, + _markTargetAnchor: function(evt){ + try{ + var t = this._dndRegion.type; + if(this._alreadyOut || (this._dnding && !this._config[t]["within"]) || (this._extDnding && !this._config[t]["in"])){ + return; + } + var height, width, left, top, + targetAnchor = this._targetAnchor[t], + pos = html.position(this._container); + if(!targetAnchor){ + targetAnchor = this._targetAnchor[t] = html.create("div", { + "class": (t == "cell") ? "dojoxGridCellBorderDIV" : "dojoxGridBorderDIV" + }); + html.style(targetAnchor, "display", "none"); + this._container.appendChild(targetAnchor); + } + switch(t){ + case "col": + height = pos.h; + width = this._targetAnchorBorderWidth; + left = this._calcColTargetAnchorPos(evt, pos); + top = 0; + break; + case "row": + height = this._targetAnchorBorderWidth; + width = pos.w; + left = 0; + top = this._calcRowTargetAnchorPos(evt, pos); + break; + case "cell": + var cellPos = this._calcCellTargetAnchorPos(evt, pos, targetAnchor); + height = cellPos.h; + width = cellPos.w; + left = cellPos.l; + top = cellPos.t; + } + if(typeof height == "number" && typeof width == "number" && typeof left == "number" && typeof top == "number"){ + html.style(targetAnchor, { + "height": height + "px", + "width": width + "px", + "left": left + "px", + "top": top + "px" + }); + html.style(targetAnchor, "display", ""); + }else{ + this._target = null; + } + }catch(e){ + console.warn("DnD._markTargetAnchor() error:",e); + } + }, + _unmarkTargetAnchor: function(){ + if(this._dndRegion){ + var targetAnchor = this._targetAnchor[this._dndRegion.type]; + if(targetAnchor){ + html.style(this._targetAnchor[this._dndRegion.type], "display", "none"); + } + } + }, + _getVisibleHeaders: function(){ + return array.map(array.filter(this.grid.layout.cells, function(cell){ + return !cell.hidden; + }), function(cell){ + return { + "node": cell.getHeaderNode(), + "cell": cell + }; + }); + }, + _rearrange: function(){ + if(this._target === null){ + return; + } + var t = this._dndRegion.type; + var ranges = this._dndRegion.selected; + if(t === "cell"){ + this.rearranger[(this._isCopy || this._copyOnly) ? "copyCells" : "moveCells"](ranges[0], this._target === -1 ? null : this._target); + }else{ + this.rearranger[t == "col" ? "moveColumns" : "moveRows"](_joinToArray(ranges), this._target === -1 ? null: this._target); + } + this._target = null; + }, + onDraggingOver: function(sourcePlugin){ + if(!this._dnding && sourcePlugin){ + sourcePlugin._isSource = true; + this._extDnding = true; + if(!this._externalDnd){ + this._externalDnd = true; + this._dndRegion = this._mapRegion(sourcePlugin.grid, sourcePlugin._dndRegion); + } + this._createDnDUI(this._moveEvent,true); + this.grid.pluginMgr.getPlugin("autoScroll").readyForAutoScroll = true; + } + }, + _mapRegion: function(srcGrid, dndRegion){ + if(dndRegion.type === "cell"){ + var srcRange = dndRegion.selected[0]; + var cells = this.grid.layout.cells; + var srcCells = srcGrid.layout.cells; + var c, cnt = 0; + for(c = srcRange.min.col; c <= srcRange.max.col; ++c){ + if(!srcCells[c].hidden){ + ++cnt; + } + } + for(c = 0; cnt > 0; ++c){ + if(!cells[c].hidden){ + --cnt; + } + } + var region = lang.clone(dndRegion); + region.selected[0].min.col = 0; + region.selected[0].max.col = c - 1; + for(c = srcRange.min.col; c <= dndRegion.handle.col; ++c){ + if(!srcCells[c].hidden){ + ++cnt; + } + } + for(c = 0; cnt > 0; ++c){ + if(!cells[c].hidden){ + --cnt; + } + } + region.handle.col = c; + } + return dndRegion; + }, + onDraggingOut: function(sourcePlugin){ + if(this._externalDnd){ + this._extDnding = false; + this._destroyDnDUI(true, false); + if(sourcePlugin){ + sourcePlugin._isSource = false; + } + } + }, + onDragIn: function(sourcePlugin, isCopy){ + var success = false; + if(this._target !== null){ + var type = sourcePlugin._dndRegion.type; + var ranges = sourcePlugin._dndRegion.selected; + switch(type){ + case "cell": + this.rearranger.changeCells(sourcePlugin.grid, ranges[0], this._target); + break; + case "row": + var range = _joinToArray(ranges); + this.rearranger.insertRows(sourcePlugin.grid, range, this._target); + break; + } + success = true; + } + this._endDnd(true); + if(sourcePlugin.onDragOut){ + sourcePlugin.onDragOut(success && !isCopy); + } + }, + onDragOut: function(isMove){ + if(isMove && !this._copyOnly){ + var type = this._dndRegion.type; + var ranges = this._dndRegion.selected; + switch(type){ + case "cell": + this.rearranger.clearCells(ranges[0]); + break; + case "row": + this.rearranger.removeRows(_joinToArray(ranges)); + break; + } + } + this._endDnd(true); + }, + _canAccept: function(sourcePlugin){ + if(!sourcePlugin){ + return false; + } + var srcRegion = sourcePlugin._dndRegion; + var type = srcRegion.type; + if(!this._config[type]["in"] || !sourcePlugin._config[type]["out"]){ + return false; + } + var g = this.grid; + var ranges = srcRegion.selected; + var colCnt = array.filter(g.layout.cells, function(cell){ + return !cell.hidden; + }).length; + var rowCnt = g.rowCount; + var res = true; + switch(type){ + case "cell": + ranges = ranges[0]; + res = g.store.getFeatures()["dojo.data.api.Write"] && + (ranges.max.row - ranges.min.row) <= rowCnt && + array.filter(sourcePlugin.grid.layout.cells, function(cell){ + return cell.index >= ranges.min.col && cell.index <= ranges.max.col && !cell.hidden; + }).length <= colCnt; + //intentional drop through - don't break + case "row": + if(sourcePlugin._allDnDItemsLoaded()){ + return res; + } + } + return false; + }, + _allDnDItemsLoaded: function(){ + if(this._dndRegion){ + var type = this._dndRegion.type, + ranges = this._dndRegion.selected, + rows = []; + switch(type){ + case "cell": + for(var i = ranges[0].min.row, max = ranges[0].max.row; i <= max; ++i){ + rows.push(i); + } + break; + case "row": + rows = _joinToArray(ranges); + break; + default: + return false; + } + var cache = this.grid._by_idx; + return array.every(rows, function(rowIndex){ + return !!cache[rowIndex]; + }); + } + return false; + } +}); + +EnhancedGrid.registerPlugin(DnD/*name:'dnd'*/, { + "dependency": ["selector", "rearrange"] +}); + +return DnD; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/Exporter.js b/js/dojo/dojox/grid/enhanced/plugins/Exporter.js new file mode 100644 index 0000000..fe12206 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Exporter.js @@ -0,0 +1,243 @@ +//>>built +define("dojox/grid/enhanced/plugins/Exporter", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "../_Plugin", + "../../_RowSelector", + "../../EnhancedGrid", + "../../cells/_base" +], function(declare, array, lang, _Plugin, _RowSelector, EnhancedGrid){ + +var gridCells = lang.getObject("dojox.grid.cells"); + +var Exporter = declare("dojox.grid.enhanced.plugins.Exporter", _Plugin, { + // summary: + // Provide functions to export the grid data into a given format. + // + // Acceptable plugin parameters: + // 1. exportFormatter: function(data, cell, rowIndex, item) + // Provide a way to customize how data should look in exported string. + // Note that usually the formatter of grid cell should not be used here (it can return HTML or even widget). + // example: + // | function onExported(exported_text){ + // | //custom code here... + // | } + // | dijit.byId("my_grid_id").exportTo("csv", //registered export format, mandatory + // | { //the whole object is optional. + // | fetchArgs: {start:0,count:1000}, //keywordArgs for fetch, optional + // | writerArgs: {separator:';'}, //export writer specific arguments, optional + // | }, + // | function(str){ + // | //call back function, mandatory + // | }); + // | var result = dijit.byId("my_grid_id").exportSelectedTo("table", //registered export format, mandatory + // | {separator:'|'} //export writer specific arguments, optional + // | ); + // + + // name: String + // Plugin name. + name: "exporter", + + constructor: function(grid, args){ + // summary: + // only newed by _Plugin + // grid: EnhancedGrid + // The grid to plug in to. + this.grid = grid; + this.formatter = (args && lang.isObject(args)) && args.exportFormatter; + this._mixinGrid(); + }, + _mixinGrid: function(){ + var g = this.grid; + g.exportTo = lang.hitch(this, this.exportTo); + g.exportGrid = lang.hitch(this, this.exportGrid); + g.exportSelected = lang.hitch(this, this.exportSelected); + g.setExportFormatter = lang.hitch(this, this.setExportFormatter); + }, + setExportFormatter: function(formatter){ + this.formatter = formatter; + }, + exportGrid: function(type, args, onExported){ + // summary: + // Export required rows(fetchArgs) to a kind of format(type) + // using the corresponding writer with given arguments(writerArgs), + // then pass the exported text to a given function(onExported). + // tags: + // public + // type: string + // A registered export format name + // args: object? + // includes: + // { + // fetchArgs: object? + // Any arguments for store.fetch + // writerArgs: object? + // Arguments for the given format writer + // } + // onExported: function(string) + // Call back function when export result is ready + if(lang.isFunction(args)){ + onExported = args; + args = {}; + } + if(!lang.isString(type) || !lang.isFunction(onExported)){ + return; + } + args = args || {}; + var g = this.grid, _this = this, + writer = this._getExportWriter(type, args.writerArgs), + fetchArgs = (args.fetchArgs && lang.isObject(args.fetchArgs)) ? args.fetchArgs : {}, + oldFunc = fetchArgs.onComplete; + if(g.store){ + fetchArgs.onComplete = function(items, request){ + if(oldFunc){ + oldFunc(items, request); + } + onExported(_this._goThroughGridData(items, writer)); + }; + fetchArgs.sort = fetchArgs.sort || g.getSortProps(); + g._storeLayerFetch(fetchArgs); + }else{ + //Data is defined directly in the structure; + var start = fetchArgs.start || 0, + count = fetchArgs.count || -1, + items = []; + for(var i = start; i != start + count && i < g.rowCount; ++i){ + items.push(g.getItem(i)); + } + onExported(this._goThroughGridData(items, writer)); + } + }, + exportSelected: function(type, writerArgs){ + // summary: + // Only export selected rows. + // tags: + // public + // type: string + // A registered export format name + // writerArgs: object? + // Arguments for the given format writer + // returns: string + // The exported string + if(!lang.isString(type)){ + return ""; + } + var writer = this._getExportWriter(type, writerArgs); + return this._goThroughGridData(this.grid.selection.getSelected(), writer); //String + }, + _buildRow: function(/* object */arg_obj,/* ExportWriter */writer){ + // summary: + // Use the given export writer(writer) to go through a single row + // which is given in the context object(arg_obj). + // tags: + // private + // returns: + // undefined + var _this = this; + array.forEach(arg_obj._views, function(view, vIdx){ + arg_obj.view = view; + arg_obj.viewIdx = vIdx; + if(writer.beforeView(arg_obj)){ + array.forEach(view.structure.cells, function(subrow, srIdx){ + arg_obj.subrow = subrow; + arg_obj.subrowIdx = srIdx; + if(writer.beforeSubrow(arg_obj)){ + array.forEach(subrow, function(cell, cIdx){ + if(arg_obj.isHeader && _this._isSpecialCol(cell)){ + arg_obj.spCols.push(cell.index); + } + arg_obj.cell = cell; + arg_obj.cellIdx = cIdx; + writer.handleCell(arg_obj); + }); + writer.afterSubrow(arg_obj); + } + }); + writer.afterView(arg_obj); + } + }); + }, + _goThroughGridData: function(/* Array */items,/* ExportWriter */writer){ + // summary: + // Use the given export writer(writer) to go through the grid structure + // and the given rows(items), then return the writer output. + // tags: + // private + var grid = this.grid, + views = array.filter(grid.views.views, function(view){ + return !(view instanceof _RowSelector); + }), + arg_obj = { + 'grid': grid, + 'isHeader': true, + 'spCols': [], + '_views': views, + 'colOffset': (views.length < grid.views.views.length ? -1 : 0) + }; + //go through header + if(writer.beforeHeader(grid)){ + this._buildRow(arg_obj,writer); + writer.afterHeader(); + } + //go through content + arg_obj.isHeader = false; + if(writer.beforeContent(items)){ + array.forEach(items, function(item, rIdx){ + arg_obj.row = item; + arg_obj.rowIdx = rIdx; + if(writer.beforeContentRow(arg_obj)){ + this._buildRow(arg_obj, writer); + writer.afterContentRow(arg_obj); + } + }, this); + writer.afterContent(); + } + return writer.toString(); + }, + _isSpecialCol: function(/* dojox.grid.__CellDef */header_cell){ + // summary: + // Row selectors and row indexes should be recognized and handled separately. + // tags: + // private + return header_cell.isRowSelector || header_cell instanceof gridCells.RowIndex; //Boolean + }, + _getExportWriter: function(/* string */ fileType, /* object? */ writerArgs){ + // summary: + // Use the given export format type(fileType) + // and writer arguments(writerArgs) to create + // a ExportWriter and return it. + // tags: + // private + var writerName, cls, + expCls = Exporter; + if(expCls.writerNames){ + writerName = expCls.writerNames[fileType.toLowerCase()]; + cls = lang.getObject(writerName); + if(cls){ + var writer = new cls(writerArgs); + writer.formatter = this.formatter; + return writer; //ExportWriter + }else{ + throw new Error('Please make sure class "' + writerName + '" is required.'); + } + } + throw new Error('The writer for "' + fileType + '" has not been registered.'); + } +}); + +Exporter.registerWriter = function(/* string */fileType,/* string */writerClsName){ + // summary: + // Register a writer(writerClsName) to a export format type(fileType). + // This function separates the Exporter from all kinds of writers. + // tags: + // public + Exporter.writerNames = Exporter.writerNames || {}; + Exporter.writerNames[fileType] = writerClsName; +}; + +EnhancedGrid.registerPlugin(Exporter/*name:'exporter'*/); + +return Exporter; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/Filter.js b/js/dojo/dojox/grid/enhanced/plugins/Filter.js new file mode 100644 index 0000000..a68a00f --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Filter.js @@ -0,0 +1,173 @@ +//>>built +define("dojox/grid/enhanced/plugins/Filter", [ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/i18n", + "../_Plugin", + "./Dialog", + "./filter/FilterLayer", + "./filter/FilterBar", + "./filter/FilterDefDialog", + "./filter/FilterStatusTip", + "./filter/ClearFilterConfirm", + "../../EnhancedGrid", + "dojo/i18n!../nls/Filter" +], function(declare, lang, i18n, _Plugin, Dialog, layers, FilterBar, FilterDefDialog, FilterStatusTip, ClearFilterConfirm, EnhancedGrid){ + + var Filter = declare("dojox.grid.enhanced.plugins.Filter", _Plugin, { + // summary: + // Provide filter functionality for grid. + // + // Acceptable plugin parameters: + // 1. itemsName: string + // the name shown on the filter bar. + // 2. statusTipTimeout: number + // when does the status tip show. + // 3. ruleCount: number + // default to 3, should not change to more. The Claro theme limits it. + // 4. disabledConditions: object + // If you don't need all of the conditions provided for a data type, + // you can explicitly declare them here: + // e.g.: disabledConditions: {string: ["contains", "is"], number: ["equalto"], ...} + // 5. isServerSide: boolean + // Whether to use server side filtering. Default to false. + // 6. isStateful: boolean + // If isServerSide is true, set the server side filter to be stateful or not. default to false. + // 7. url: string + // If using stateful, this is the url to send commands. default to store.url. + // 8. ruleCountToConfirmClearFilter: Integer | null |Infinity + // If the filter rule count is larger than or equal to this value, then a confirm dialog will show when clearing filter. + // If set to less than 1 or null, then always show the confirm dialog. + // If set to Infinity, then never show the confirm dialog. + // Default value is 2. + // + // Acceptable cell parameters defined in layout: + // 1. filterable: boolean + // The column is not filterable only when this is set to false explicitly. + // 2. datatype: string + // The data type of this column. Can be "string", "number", "date", "time", "boolean". + // Default to "string". + // 3. autoComplete: boolean + // If need auto-complete in the ComboBox for String type, set this to true. + // 4. dataTypeArgs: object + // Some arguments helping convert store data to something the filter UI understands. + // Different data type arguments can be provided to different data types. + // For date/time, this is a dojo.date.locale.__FormatOptions, so the DataTimeBox can understand the store data. + // For boolean, this object contains: + // trueLabel: string + // A label to display in the filter definition dialog for true value. Default to "True". + // falseLable: string + // A label to display in the filter definition dialog for false value. Default to "False". + // 5. disabledConditions: object + // If you don't need all of the conditions provided by the filter UI on this column, you can explicitly say it out here. + // e.g.: disabledConditions: ["contains", "is"] + // This will disable the "contains" condition for this column, if this column is of string type. + // For full set of conditions, please refer to dojox.grid.enhanced.plugins.filter.FilterDefDialog._setupData. + // example: + // | <div dojoType="dojox.grid.EnhancedGrid" plugins="{GridFilter: true}" ...></div> + // | or provide some parameters: + // | <div dojoType="dojox.grid.EnhancedGrid" plugins="{GridFilter: {itemsName: 'songs'}}" ...></div> + // | Customize columns for filter: + // | var layout = [ + // | ... + // | //define a column to be un-filterable in layout/structure + // | {field: "Genre", filterable: false, ...} + // | //define a column of type string and supports autoComplete when you type in filter conditions. + // | {field: "Writer", datatype: "string", autoCommplete: true, ...} + // | //define a column of type date and the data in store has format: "yyyy/M/d" + // | {field: "Publish Date", datatype: "date", dataTypeArgs: {datePattern: "yyyy/M/d"}, ...} + // | //disable some conditions for a column + // | {field: "Track", disabledConditions: ["equalto","notequalto"], ...} + // | ... + // | ]; + + // name: String + // plugin name + name: "filter", + + constructor: function(grid, args){ + // summary: + // See constructor of dojox.grid.enhanced._Plugin. + this.grid = grid; + this.nls = i18n.getLocalization("dojox.grid.enhanced", "Filter"); + + args = this.args = lang.isObject(args) ? args : {}; + if(typeof args.ruleCount != 'number' || args.ruleCount < 0){ + args.ruleCount = 3; + } + var rc = this.ruleCountToConfirmClearFilter = args.ruleCountToConfirmClearFilter; + if(rc === undefined){ + this.ruleCountToConfirmClearFilter = 2; + } + + //Install filter layer + this._wrapStore(); + + //Install UI components + var obj = { "plugin": this }; + this.clearFilterDialog = new Dialog({ + refNode: this.grid.domNode, + title: this.nls["clearFilterDialogTitle"], + content: new ClearFilterConfirm(obj) + }); + this.filterDefDialog = new FilterDefDialog(obj); + this.filterBar = new FilterBar(obj); + this.filterStatusTip = new FilterStatusTip(obj); + + //Expose the layer event to grid. + grid.onFilterDefined = function(){}; + this.connect(grid.layer("filter"), "onFilterDefined", function(filter){ + grid.onFilterDefined(grid.getFilter(), grid.getFilterRelation()); + }); + }, + destroy: function(){ + this.inherited(arguments); + try{ + this.grid.unwrap("filter"); + this.filterBar.destroyRecursive(); + this.filterBar = null; + this.clearFilterDialog.destroyRecursive(); + this.clearFilterDialog = null; + this.filterStatusTip.destroy(); + this.filterStatusTip = null; + this.filterDefDialog.destroy(); + this.filterDefDialog = null; + this.grid = null; + this.nls = null; + this.args = null; + }catch(e){ + console.warn("Filter.destroy() error:",e); + } + }, + _wrapStore: function(){ + var g = this.grid; + var args = this.args; + var filterLayer = args.isServerSide ? new layers.ServerSideFilterLayer(args) : + new layers.ClientSideFilterLayer({ + cacheSize: args.filterCacheSize, + fetchAll: args.fetchAllOnFirstFilter, + getter: this._clientFilterGetter + }); + layers.wrap(g, "_storeLayerFetch", filterLayer); + + this.connect(g, "_onDelete", lang.hitch(filterLayer, "invalidate")); + }, + onSetStore: function(store){ + this.filterDefDialog.clearFilter(true); + }, + _clientFilterGetter: function(/* data item */ datarow,/* cell */cell, /* int */rowIndex){ + // summary: + // Define the grid-specific way to get data from a row. + // Argument "cell" is provided by FilterDefDialog when defining filter expressions. + // Argument "rowIndex" is provided by FilterLayer when checking a row. + // FilterLayer also provides a forth argument: "store", which is grid.store, + // but we don't need it here. + return cell.get(rowIndex, datarow); + } + }); + + EnhancedGrid.registerPlugin(Filter/*name:'filter'*/); + + return Filter; + +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/GridSource.js b/js/dojo/dojox/grid/enhanced/plugins/GridSource.js new file mode 100644 index 0000000..18c393c --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/GridSource.js @@ -0,0 +1,155 @@ +//>>built +define("dojox/grid/enhanced/plugins/GridSource", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/dnd/Source", + "./DnD" +], function(declare, array, lang, Source, DnD){ + +var _joinToArray = function(arrays){ + var a = arrays[0]; + for(var i = 1; i < arrays.length; ++i){ + a = a.concat(arrays[i]); + } + return a; +}; + +var GridDnDSource = lang.getObject("dojox.grid.enhanced.plugins.GridDnDSource"); + +return declare("dojox.grid.enhanced.plugins.GridSource", Source, { + // summary: + // A special source that can accept grid contents. + // Only for non-grid widgets or domNodes. + accept: ["grid/cells", "grid/rows", "grid/cols", "text"], + + // insertNodesForGrid: + // If you'd like to insert some sort of nodes into your dnd source, turn this on, + // and override getCellContent/getRowContent/getColumnContent + // to populate the dnd data in your desired format. + insertNodesForGrid: false, + + markupFactory: function(params, node){ + cls = lang.getObject("dojox.grid.enhanced.plugins.GridSource"); + return new cls(node, params); + }, + checkAcceptance: function(source, nodes){ + if(source instanceof GridDnDSource){ + if(nodes[0]){ + var item = source.getItem(nodes[0].id); + if(item && (array.indexOf(item.type, "grid/rows") >= 0 || array.indexOf(item.type, "grid/cells") >= 0) && + !source.dndPlugin._allDnDItemsLoaded()){ + return false; + } + } + this.sourcePlugin = source.dndPlugin; + } + return this.inherited(arguments); + }, + onDraggingOver: function(){ + if(this.sourcePlugin){ + this.sourcePlugin._isSource = true; + } + }, + onDraggingOut: function(){ + if(this.sourcePlugin){ + this.sourcePlugin._isSource = false; + } + }, + onDropExternal: function(source, nodes, copy){ + if(source instanceof GridDnDSource){ + var ranges = array.map(nodes, function(node){ + return source.getItem(node.id).data; + }); + var item = source.getItem(nodes[0].id); + var grid = item.dndPlugin.grid; + var type = item.type[0]; + var range; + try{ + switch(type){ + case "grid/cells": + nodes[0].innerHTML = this.getCellContent(grid, ranges[0].min, ranges[0].max) || ""; + this.onDropGridCells(grid, ranges[0].min, ranges[0].max); + break; + case "grid/rows": + range = _joinToArray(ranges); + nodes[0].innerHTML = this.getRowContent(grid, range) || ""; + this.onDropGridRows(grid, range); + break; + case "grid/cols": + range = _joinToArray(ranges); + nodes[0].innerHTML = this.getColumnContent(grid, range) || ""; + this.onDropGridColumns(grid, range); + break; + } + if(this.insertNodesForGrid){ + this.selectNone(); + this.insertNodes(true, [nodes[0]], this.before, this.current); + } + item.dndPlugin.onDragOut(!copy); + }catch(e){ + console.warn("GridSource.onDropExternal() error:",e); + } + }else{ + this.inherited(arguments); + } + }, + getCellContent: function(grid, leftTopCell, rightBottomCell){ + // summary: + // Fill node innerHTML for dnd grid cells. + // sample code: + // var cells = grid.layout.cells; + // var store = grid.store; + // var cache = grid._by_idx; + // var res = "Grid Cells from " + grid.id + ":<br/>"; + // for(var r = leftTopCell.row; r <= rightBottomCell.row; ++r){ + // for(var c = leftTopCell.col; c <= rightBottomCell.col; ++c){ + // res += store.getValue(cache[r].item, cells[c].field) + ", "; + // } + // res = res.substring(0, res.length - 2) + ";<br/>"; + // } + // return res; + }, + getRowContent: function(grid, rowIndexes){ + // summary: + // Fill node innerHTML for dnd grid rows. + // sample code: + // var cells = grid.layout.cells; + // var store = grid.store; + // var cache = grid._by_idx; + // var res = "Grid Rows from " + grid.id + ":<br/>"; + // for(var i = 0; i < rowIndexes.length; ++i){ + // var r = rowIndexes[i]; + // res += "Row " + r + ": "; + // for(var j = 0; j < cells.length; ++j){ + // if(!cells[j].hidden){ + // res += store.getValue(cache[r].item, cells[j].field) + ", "; + // } + // } + // res = res.substring(0, res.length - 2) + ";<br/>"; + // } + // return res; + }, + getColumnContent: function(grid, colIndexes){ + // summary: + // Fill node innerHTML for dnd grid columns. + // sample code: + // var cells = grid.layout.cells; + // var res = "Grid Columns from " + grid.id + ":"; + // for(var i = 0; i < colIndexes.length; ++i){ + // var c = colIndexes[i]; + // res += (cells[c].name || cells[c].field) + ", "; + // } + // return res.substring(0, res.length - 2); + }, + onDropGridCells: function(grid, leftTopCell, rightBottomCell){ + + }, + onDropGridRows: function(grid, rowIndexes){ + + }, + onDropGridColumns: function(grid, colIndexes){ + + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/IndirectSelection.js b/js/dojo/dojox/grid/enhanced/plugins/IndirectSelection.js new file mode 100644 index 0000000..3b41e84 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/IndirectSelection.js @@ -0,0 +1,626 @@ +//>>built +define("dojox/grid/enhanced/plugins/IndirectSelection", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/event", + "dojo/_base/lang", + "dojo/_base/html", + "dojo/_base/window", + "dojo/_base/connect", + "dojo/_base/sniff", + "dojo/query", + "dojo/keys", + "dojo/string", + "../_Plugin", + "../../EnhancedGrid", + "../../cells/dijit" +], function(declare, array, evt, lang, html, win, connect, has, query, keys, string, _Plugin, EnhancedGrid){ + +var gridCells = lang.getObject("dojox.grid.cells"); + +var RowSelector = declare("dojox.grid.cells.RowSelector", gridCells._Widget, { + // summary: + // Common attributes & functions for row selectors(Radio|CheckBox) + + //inputType: String + // Input type - Radio|CheckBox + inputType: "", + + //map: Object + // Cache div refs of radio|checkbox to avoid querying each time + map: null, + + //disabledMap: Object + // Cache index of disabled rows + disabledMap: null, + + //isRowSelector: Boolean + // Marker of indirectSelection cell(column) + isRowSelector: true, + + //_connects: Array + // List of all connections. + _connects: null, + + //_subscribes: Array + // List of all subscribes. + _subscribes: null, + + //checkedText: String + // Checked character for high contrast mode + checkedText: '✓', + + //unCheckedText: String + // Unchecked character for high contrast mode + unCheckedText: 'O', + + constructor: function(){ + this.map = {}; this.disabledMap = {}, this.disabledCount= 0; + this._connects = []; this._subscribes = []; + this.inA11YMode = html.hasClass(win.body(), "dijit_a11y"); + + this.baseClass = "dojoxGridRowSelector dijitReset dijitInline dijit" + this.inputType; + this.checkedClass = " dijit" + this.inputType + "Checked"; + this.disabledClass = " dijit" + this.inputType + "Disabled"; + this.checkedDisabledClass = " dijit" + this.inputType + "CheckedDisabled"; + this.statusTextClass = " dojoxGridRowSelectorStatusText";//a11y use + + this._connects.push(connect.connect(this.grid, 'dokeyup', this, '_dokeyup')); + this._connects.push(connect.connect(this.grid.selection, 'onSelected', this, '_onSelected')); + this._connects.push(connect.connect(this.grid.selection, 'onDeselected', this, '_onDeselected')); + this._connects.push(connect.connect(this.grid.scroller, 'invalidatePageNode', this, '_pageDestroyed')); + this._connects.push(connect.connect(this.grid, 'onCellClick', this, '_onClick')); + this._connects.push(connect.connect(this.grid, 'updateRow', this, '_onUpdateRow')); + }, + formatter: function(data, rowIndex, scope){ + // summary: + // Overwritten, see dojox.grid.cells._Widget + var _this = scope; + var clazz = _this.baseClass; + var checked = _this.getValue(rowIndex); + var disabled = !!_this.disabledMap[rowIndex];//normalize 'undefined' + + if(checked){ + clazz += _this.checkedClass; + if(disabled){ clazz += _this.checkedDisabledClass; } + }else if(disabled){ + clazz += _this.disabledClass; + } + return ["<div tabindex = -1 ", + "id = '" + _this.grid.id + "_rowSelector_" + rowIndex + "' ", + "name = '" + _this.grid.id + "_rowSelector' class = '" + clazz + "' ", + "role = 'presentation' aria-pressed = '" + checked + "' aria-disabled = '" + disabled + + "' aria-label = '" + string.substitute(_this.grid._nls["indirectSelection" + _this.inputType], [rowIndex + 1]) + "'>", + "<span class = '" + _this.statusTextClass + "'>" + (checked ? _this.checkedText : _this.unCheckedText) + "</span>", + "</div>"].join(""); + }, + setValue: function(rowIndex, inValue){ + // summary: + // Overwritten, see dojox.grid.cells._Widget + // Simply return, no action + }, + getValue: function(rowIndex){ + // summary: + // Overwritten, see dojox.grid.cells._Widget + return this.grid.selection.isSelected(rowIndex); + }, + toggleRow: function(index, value){ + // summary: + // toggle checked | unchecked state for given row + // index: Integer + // Row index + // value: Boolean + // True - checked | False - unchecked + this._nativeSelect(index, value); + }, + setDisabled: function(index, disabled){ + // summary: + // toggle disabled | enabled state for given row + // idx: Integer + // Row index + // disabled: Boolean + // True - disabled | False - enabled + if(index < 0){ return; } + this._toggleDisabledStyle(index, disabled); + }, + disabled: function(index){ + // summary: + // Check if one row is disabled + return !!this.disabledMap[index]; + }, + _onClick: function(e){ + // summary: + // When mouse click on the selector cell, select/deselect the row. + if(e.cell === this){ + this._selectRow(e); + } + }, + _dokeyup: function(e){ + // summary: + // Event handler for key up event + // - from dojox.grid.enhanced._Events.dokeyup() + // e: Event + // Key up event + if(e.cellIndex == this.index && e.rowIndex >= 0 && e.keyCode == keys.SPACE){ + this._selectRow(e); + } + }, + focus: function(rowIndex){ + // summary: + // Set focus to given row + // rowIndex: Integer + // Target row + var selector = this.map[rowIndex]; + if(selector){ selector.focus(); } + }, + _focusEndingCell: function(rowIndex, cellIndex){ + // summary: + // Set focus to the ending grid cell(rowIndex,cellIndex) when swipe selection finished + // rowIndex: Integer + // Row index + // cellIndex: Integer + // Column index + var cell = this.grid.getCell(cellIndex); + this.grid.focus.setFocusCell(cell, rowIndex); + }, + _nativeSelect: function(index, value){ + // summary: + // Use grid's native selection + this.grid.selection[value ? 'select' : 'deselect'](index); + }, + _onSelected: function(index){ + // summary: + // Triggered when a row is selected + this._toggleCheckedStyle(index, true); + }, + _onDeselected: function(index){ + // summary: + // Triggered when a row is deselected + this._toggleCheckedStyle(index, false); + }, + _onUpdateRow: function(index){ + // summary: + // Clear cache when row is re-built. + delete this.map[index]; + }, + _toggleCheckedStyle: function(index, value){ + // summary: + // Change css styles for checked | unchecked + var selector = this._getSelector(index); + if(selector){ + html.toggleClass(selector, this.checkedClass, value); + if(this.disabledMap[index]){ + html.toggleClass(selector, this.checkedDisabledClass, value); + } + selector.setAttribute("aria-pressed", value); + if(this.inA11YMode){ + selector.firstChild.innerHTML = (value ? this.checkedText : this.unCheckedText); + } + } + }, + _toggleDisabledStyle: function(index, disabled){ + // summary: + // Change css styles for disabled | enabled + var selector = this._getSelector(index); + if(selector){ + html.toggleClass(selector, this.disabledClass, disabled); + if(this.getValue(index)){ + html.toggleClass(selector, this.checkedDisabledClass, disabled); + } + selector.setAttribute("aria-disabled", disabled); + } + this.disabledMap[index] = disabled; + if(index >= 0){ + this.disabledCount += disabled ? 1 : -1; + } + }, + _getSelector: function(index){ + // summary: + // Find selector for given row caching it if 1st time found + var selector = this.map[index]; + if(!selector){//use accurate query for better performance + var rowNode = this.view.rowNodes[index]; + if(rowNode){ + selector = query('.dojoxGridRowSelector', rowNode)[0]; + if(selector){ this.map[index] = selector; } + } + } + return selector; + }, + _pageDestroyed: function(pageIndex){ + // summary: + // Explicitly empty map cache when a page destroyed + // See dojox.grid._Scroller.invalidatePageNode() + // pageIndex: Integer + // Index of destroyed page + var rowsPerPage = this.grid.scroller.rowsPerPage; + var start = pageIndex * rowsPerPage, end = start + rowsPerPage - 1; + for(var i = start; i <= end; i++){ + if(!this.map[i]){continue;} + html.destroy(this.map[i]); + delete this.map[i]; + } + //console.log("Page ",pageIndex, " destroyed, Map=",this.map); + }, + destroy: function(){ + for(var i in this.map){ + html.destroy(this.map[i]); + delete this.map[i]; + } + for(i in this.disabledMap){ delete this.disabledMap[i]; } + array.forEach(this._connects, connect.disconnect); + array.forEach(this._subscribes, connect.unsubscribe); + delete this._connects; + delete this._subscribes; + //console.log('Single(Multiple)RowSelector.destroy() executed!'); + } +}); + +var SingleRowSelector = declare("dojox.grid.cells.SingleRowSelector", RowSelector, { + // summary: + // IndirectSelection cell(column) for single selection mode, using styles of dijit.form.RadioButton + inputType: "Radio", + + _selectRow: function(e){ + // summary: + // Select the target row + // e: Event + // Event fired on the target row + var index = e.rowIndex; + if(this.disabledMap[index]){ return; } + this._focusEndingCell(index, 0); + this._nativeSelect(index, !this.grid.selection.selected[index]); + } +}); + +var MultipleRowSelector = declare("dojox.grid.cells.MultipleRowSelector", RowSelector, { + // summary: + // Indirect selection cell for multiple or extended mode, using dijit.form.CheckBox + inputType: "CheckBox", + + //swipeStartRowIndex: Integer + // Start row index for swipe selection + swipeStartRowIndex: -1, + + //swipeMinRowIndex: Integer + // Min row index for swipe selection + swipeMinRowIndex: -1, + + //swipeMinRowIndex: Integer + // Max row index for swipe selection + swipeMaxRowIndex: -1, + + //toSelect: Boolean + // new state for selection + toSelect: false, + + //lastClickRowIdx: Integer + // Row index for last click, used for range selection via Shift + click + lastClickRowIdx: -1, + + //toggleAllTrigerred: Boolean + // Whether toggle all has been triggered or not + toggleAllTrigerred: false, + + unCheckedText: '□', + + constructor: function(){ + this._connects.push(connect.connect(win.doc, 'onmouseup', this, '_domouseup')); + this._connects.push(connect.connect(this.grid, 'onRowMouseOver', this, '_onRowMouseOver')); + this._connects.push(connect.connect(this.grid.focus, 'move', this, '_swipeByKey')); + this._connects.push(connect.connect(this.grid, 'onCellMouseDown', this, '_onMouseDown')); + if(this.headerSelector){//option set by user to add a select-all checkbox in column header + this._connects.push(connect.connect(this.grid.views, 'render', this, '_addHeaderSelector')); + this._connects.push(connect.connect(this.grid, '_onFetchComplete', this, '_addHeaderSelector')); + this._connects.push(connect.connect(this.grid, 'onSelectionChanged', this, '_onSelectionChanged')); + this._connects.push(connect.connect(this.grid, 'onKeyDown', this, function(e){ + if(e.rowIndex == -1 && e.cellIndex == this.index && e.keyCode == keys.SPACE){ + this._toggletHeader();//TBD - a better way + } + })); + } + }, + toggleAllSelection:function(checked){ + // summary: + // Toggle select all|deselect all + // checked: Boolean + // True - select all, False - deselect all + var grid = this.grid, selection = grid.selection; + if(checked){ + selection.selectRange(0, grid.rowCount-1); + }else{ + selection.deselectAll(); + } + this.toggleAllTrigerred = true; + }, + _onMouseDown: function(e){ + if(e.cell == this){ + this._startSelection(e.rowIndex); + evt.stop(e); + } + }, + _onRowMouseOver: function(e){ + // summary: + // Event fired when mouse moves over a data row(outside of this column). + // - from dojox.grid.enhanced._Events.onRowMouseOver() + // e: Event + // Decorated event object which contains reference to grid, cell, and rowIndex + this._updateSelection(e, 0); + }, + _domouseup: function(e){ + // summary: + // Event handler for mouse up event - from dojo.doc.domouseup() + // e: Event + // Mouse up event + if(has("ie")){ + this.view.content.decorateEvent(e);//TODO - why only e in IE hasn't been decorated? + } + var inSwipeSelection = e.cellIndex >= 0 && this.inSwipeSelection() && !this.grid.edit.isEditRow(e.rowIndex); + if(inSwipeSelection){ + this._focusEndingCell(e.rowIndex, e.cellIndex); + } + this._finishSelect(); + }, + _dokeyup: function(e){ + // summary: + // Event handler for key up event + // - from dojox.grid.enhanced._Events.dokeyup() + // e: Event + // Key up event + this.inherited(arguments); + if(!e.shiftKey){ + this._finishSelect(); + } + }, + _startSelection: function(rowIndex){ + // summary: + // Initialize parameters to start a new swipe selection + // rowIndex: Integer + // Index of the start row + this.swipeStartRowIndex = this.swipeMinRowIndex = this.swipeMaxRowIndex = rowIndex; + this.toSelect = !this.getValue(rowIndex); + }, + _updateSelection: function(e, delta){ + // summary: + // Update row selections, fired during a swipe selection + // e: Event + // Event of the current row, + // delta: Integer + // Row index delta, used for swipe selection via Shift + Arrow key + // 0: not via key, -1 : Shift + Up, 1 : Shift + Down + if(!this.inSwipeSelection()){ return; } + + var byKey = delta !== 0;//whether via Shift + Arrow Key + var currRow = e.rowIndex, deltaRow = currRow - this.swipeStartRowIndex + delta; + if(deltaRow > 0 && this.swipeMaxRowIndex < currRow + delta){ + this.swipeMaxRowIndex = currRow + delta; + } + if(deltaRow < 0 && this.swipeMinRowIndex > currRow + delta){ + this.swipeMinRowIndex = currRow + delta; + } + + var min = deltaRow > 0 ? this.swipeStartRowIndex : currRow + delta; + var max = deltaRow > 0 ? currRow + delta : this.swipeStartRowIndex; + for(var i = this.swipeMinRowIndex; i <= this.swipeMaxRowIndex; i++){ + if(this.disabledMap[i] || i < 0){ continue; } + if(i >= min && i <= max){//deltaRow != 0 || this.toSelect + this._nativeSelect(i, this.toSelect); + }else if(!byKey){ + this._nativeSelect(i, !this.toSelect); + } + } + }, + _swipeByKey: function(rowOffset, colOffset, e){ + // summary: + // Update row selections, fired when Shift + Cursor is used for swipe selection + // See dojox.grid.enhanced._Events.onKeyDown + // e: Event + // Event of the current row, + // rowOffset: Integer + // Row offset, used for swipe selection via Shift + Cursor + // -1 : Shift + Up, 1 : Shift + Down + if(!e || rowOffset === 0 || !e.shiftKey || e.cellIndex != this.index || + this.grid.focus.rowIndex < 0){ //TBD - e.rowIndex == 0 && delta == -1 + return; + } + var rowIndex = e.rowIndex; + if(this.swipeStartRowIndex < 0){ + //A new swipe selection starts via Shift + Arrow key + this.swipeStartRowIndex = rowIndex; + if(rowOffset > 0){//Shift + Down + this.swipeMaxRowIndex = rowIndex + rowOffset; + this.swipeMinRowIndex = rowIndex; + }else{//Shift + UP + this.swipeMinRowIndex = rowIndex + rowOffset; + this.swipeMaxRowIndex = rowIndex; + } + this.toSelect = this.getValue(rowIndex); + } + this._updateSelection(e, rowOffset); + }, + _finishSelect: function(){ + // summary: + // Reset parameters to end a swipe selection + this.swipeStartRowIndex = -1; + this.swipeMinRowIndex = -1; + this.swipeMaxRowIndex = -1; + this.toSelect = false; + }, + inSwipeSelection: function(){ + // summary: + // Check if during a swipe selection + // return: Boolean + // Whether in swipe selection + return this.swipeStartRowIndex >= 0; + }, + _nativeSelect: function(index, value){ + // summary: + // Overwritten + this.grid.selection[value ? 'addToSelection' : 'deselect'](index); + }, + _selectRow: function(e){ + // summary: + // Select the target row or range or rows + // e: Event + // Event fired on the target row + var rowIndex = e.rowIndex; + if(this.disabledMap[rowIndex]){ return; } + evt.stop(e); + this._focusEndingCell(rowIndex, 0); + + var delta = rowIndex - this.lastClickRowIdx; + var newValue = !this.grid.selection.selected[rowIndex]; + if(this.lastClickRowIdx >= 0 && !e.ctrlKey && !e.altKey && e.shiftKey){ + var min = delta > 0 ? this.lastClickRowIdx : rowIndex; + var max = delta > 0 ? rowIndex : this.lastClickRowIdx; + for(var i = min; i >= 0 && i <= max; i++){ + this._nativeSelect(i, newValue); + } + }else{ + this._nativeSelect(rowIndex, newValue); + } + this.lastClickRowIdx = rowIndex; + }, + getValue: function(rowIndex){ + // summary: + // Overwritten + if(rowIndex == -1){//header selector + var g = this.grid; + return g.rowCount > 0 && g.rowCount <= g.selection.getSelectedCount(); + } + return this.inherited(arguments); + }, + _addHeaderSelector: function(){ + // summary: + // Add selector in column header for selecting|deselecting all + var headerCellNode = this.view.getHeaderCellNode(this.index); + if(!headerCellNode){ return; } + html.empty(headerCellNode); + var g = this.grid; + var selector = headerCellNode.appendChild(html.create("div", { + 'aria-label': g._nls["selectAll"], + "tabindex": -1, "id": g.id + "_rowSelector_-1", "class": this.baseClass, "role": "presentation", + "innerHTML": "<span class = '" + this.statusTextClass + + "'></span><span style='height: 0; width: 0; overflow: hidden; display: block;'>" + + g._nls["selectAll"] + "</span>" + })); + this.map[-1] = selector; + var idx = this._headerSelectorConnectIdx; + if(idx !== undefined){ + connect.disconnect(this._connects[idx]); + this._connects.splice(idx, 1); + } + this._headerSelectorConnectIdx = this._connects.length; + this._connects.push(connect.connect(selector, 'onclick', this, '_toggletHeader')); + this._onSelectionChanged(); + }, + _toggletHeader: function(){ + // summary: + // Toggle state for head selector + if(!!this.disabledMap[-1]){ return; } + this.grid._selectingRange = true; + this.toggleAllSelection(!this.getValue(-1)); + this._onSelectionChanged(); + this.grid._selectingRange = false; + }, + _onSelectionChanged: function(){ + // summary: + // Update header selector anytime selection changed + var g = this.grid; + if(!this.map[-1] || g._selectingRange){ return; } + g.allItemsSelected = this.getValue(-1); + this._toggleCheckedStyle(-1, g.allItemsSelected); + }, + _toggleDisabledStyle: function(index, disabled){ + // summary: + // Overwritten + this.inherited(arguments); + if(this.headerSelector){ + var allDisabled = (this.grid.rowCount == this.disabledCount); + if(allDisabled != !!this.disabledMap[-1]){//only if needed + arguments[0] = -1; + arguments[1] = allDisabled; + this.inherited(arguments); + } + } + } +}); + +var IndirectSelection = declare("dojox.grid.enhanced.plugins.IndirectSelection", _Plugin, { + // summary: + // A handy way for adding check boxe/radio button for rows, and selecting rows by swiping(or keyboard) + + // description: + // For better rendering performance, div(images) are used to simulate radio button|check boxes + // + // example: + // <div dojoType="dojox.grid.EnhancedGrid" plugins="{indirectSelection: true}" ...></div> + // or <div dojoType="dojox.grid.EnhancedGrid" plugins="{indirectSelection: {name: 'xxx', width:'30px', styles:'text-align: center;'}}" ...></div> + + //name: String + // Plugin name + name: "indirectSelection", + + constructor: function(){ + //Hook layout.setStructure(), so that indirectSelection is always included + var layout = this.grid.layout; + this.connect(layout, 'setStructure', lang.hitch(layout, this.addRowSelectCell, this.option)); + }, + addRowSelectCell: function(option){ + // summary: + // Add indirectSelection cell(mapped to a column of radio button|check boxes) + if(!this.grid.indirectSelection || this.grid.selectionMode == 'none'){ + return; + } + var rowSelectCellAdded = false, inValidFields = ['get', 'formatter', 'field', 'fields'], + defaultCellDef = {type: MultipleRowSelector, name: '', width:'30px', styles:'text-align: center;'}; + if(option.headerSelector){ option.name = ''; }//mutual conflicting attrs + + if(this.grid.rowSelectCell){//remove the existed one + this.grid.rowSelectCell.destroy(); + } + + array.forEach(this.structure, function(view){ + var cells = view.cells; + if(cells && cells.length > 0 && !rowSelectCellAdded){ + var firstRow = cells[0]; + if(firstRow[0] && firstRow[0].isRowSelector){ + console.debug('addRowSelectCell() - row selector cells already added, return.'); + rowSelectCellAdded = true; + return; + } + var selectDef, cellType = this.grid.selectionMode == 'single' ? SingleRowSelector : MultipleRowSelector; + selectDef = lang.mixin(defaultCellDef, option, {type: cellType, editable: false, notselectable: true, filterable: false, navigatable: true, nosort: true}); + array.forEach(inValidFields, function(field){//remove invalid fields + if(field in selectDef){ delete selectDef[field]; } + }); + if(cells.length > 1){ selectDef.rowSpan = cells.length; }//for complicate layout + array.forEach(this.cells, function(cell, i){ + if(cell.index >= 0){ + cell.index += 1; + //console.debug('cell '+ (cell.index - 1) + ' is updated to index ' + cell.index); + }else{ + console.warn('Error:IndirectSelection.addRowSelectCell()- cell ' + i + ' has no index!'); + } + }); + var rowSelectCell = this.addCellDef(0, 0, selectDef); + rowSelectCell.index = 0; + firstRow.unshift(rowSelectCell); + this.cells.unshift(rowSelectCell); + this.grid.rowSelectCell = rowSelectCell; + rowSelectCellAdded = true; + } + }, this); + this.cellCount = this.cells.length; + }, + destroy: function(){ + this.grid.rowSelectCell.destroy(); + delete this.grid.rowSelectCell; + this.inherited(arguments); + } +}); + +EnhancedGrid.registerPlugin(IndirectSelection/*name:'indirectSelection'*/, {"preInit": true}); + +return IndirectSelection; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/Menu.js b/js/dojo/dojox/grid/enhanced/plugins/Menu.js new file mode 100644 index 0000000..e65ac7b --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Menu.js @@ -0,0 +1,134 @@ +//>>built +define("dojox/grid/enhanced/plugins/Menu", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/html", + "dojo/_base/event", + "dojo/keys", + "../_Plugin", + "../../EnhancedGrid" +], function(declare, array, lang, html, evt, keys, _Plugin, EnhancedGrid){ + +var Menu = declare("dojox.grid.enhanced.plugins.Menu", _Plugin, { + // summary: + // Provides context menu support, including header menu, row menu, cell menu and selected region menu + // example: + // <div dojoType="dojox.grid.EnhancedGrid" + // plugins="{menus:{headerMenu:"headerMenuId", rowMenu:"rowMenuId", cellMenu:"cellMenuId", + // selectedRegionMenu:"selectedRegionMenuId"}}" ...> + // </div> + + //name: String + // Plugin name + name: "menus", + + //name: [const] Array + // menu types + types: ['headerMenu', 'rowMenu', 'cellMenu', 'selectedRegionMenu'], + + constructor: function(){ + var g = this.grid; + g.showMenu = lang.hitch(g, this.showMenu); + g._setRowMenuAttr = lang.hitch(this, '_setRowMenuAttr'); + g._setCellMenuAttr = lang.hitch(this, '_setCellMenuAttr'); + g._setSelectedRegionMenuAttr = lang.hitch(this, '_setSelectedRegionMenuAttr'); + }, + onStartUp: function(){ + var type, option = this.option; + for(type in option){ + if(array.indexOf(this.types, type) >= 0 && option[type]){ + this._initMenu(type, option[type]); + } + } + }, + _initMenu: function(/*String*/menuType, /*String | Widget(dijit.Menu)*/menu){ + var g = this.grid; + if(!g[menuType]){//in case already created in _Grid.postCreate() + var m = this._getMenuWidget(menu); + if(!m){return;} + g.set(menuType, m); + if(menuType != "headerMenu"){ + m._scheduleOpen = function(){return;}; + }else{ + g.setupHeaderMenu(); + } + } + }, + _getMenuWidget: function(/*String|Widget(dijit.Menu)*/menu){ + // summary: + // Fetch the required menu widget(should already been created) + return (menu instanceof dijit.Menu) ? menu : dijit.byId(menu); + }, + _setRowMenuAttr: function(/*Widget(dijit.Menu)*/menu){ + // summary: + // Set row menu widget + this._setMenuAttr(menu, 'rowMenu'); + }, + _setCellMenuAttr: function(/*Widget(dijit.Menu)*/menu){ + // summary: + // Set cell menu widget + this._setMenuAttr(menu, 'cellMenu'); + }, + _setSelectedRegionMenuAttr: function(/*Widget(dijit.Menu)*/menu){ + // summary: + // Set row menu widget + this._setMenuAttr(menu, 'selectedRegionMenu'); + }, + _setMenuAttr: function(/*Widget(dijit.Menu)*/menu, /*String*/menuType){ + // summary: + // Bind menus to Grid. + var g = this.grid, n = g.domNode; + if(!menu || !(menu instanceof dijit.Menu)){ + console.warn(menuType, " of Grid ", g.id, " is not existed!"); + return; + } + if(g[menuType]){ + g[menuType].unBindDomNode(n); + } + g[menuType] = menu; + g[menuType].bindDomNode(n); + }, + showMenu: function(/*Event*/e){ + // summary: + // Show appropriate context menu + // Fired from dojox.grid.enhanced._Events.onRowContextMenu, 'this' scope - Grid + // TODO: test Shift-F10 + var inSelectedRegion = (e.cellNode && html.hasClass(e.cellNode, 'dojoxGridRowSelected') || + e.rowNode && (html.hasClass(e.rowNode, 'dojoxGridRowSelected') || html.hasClass(e.rowNode, 'dojoxGridRowbarSelected'))); + + if(inSelectedRegion && this.selectedRegionMenu){ + this.onSelectedRegionContextMenu(e); + return; + } + + var info = {target: e.target, coords: e.keyCode !== keys.F10 && "pageX" in e ? {x: e.pageX, y: e.pageY } : null}; + if(this.rowMenu && (!this.cellMenu || this.selection.isSelected(e.rowIndex) || e.rowNode && html.hasClass(e.rowNode, 'dojoxGridRowbar'))){ + this.rowMenu._openMyself(info); + evt.stop(e); + return; + } + + if(this.cellMenu){ + this.cellMenu._openMyself(info); + } + evt.stop(e); + }, + destroy: function(){ + // summary: + // Destroy all resources. + // _Grid.destroy() will unbind headerMenu + var g = this.grid; + if(g.headerMenu){g.headerMenu.unBindDomNode(g.viewsHeaderNode);} + if(g.rowMenu){g.rowMenu.unBindDomNode(g.domNode);} + if(g.cellMenu){g.cellMenu.unBindDomNode(g.domNode);} + if(g.selectedRegionMenu){g.selectedRegionMenu.destroy();} + this.inherited(arguments); + } +}); + +EnhancedGrid.registerPlugin(Menu/*name:'menus'*/); + +return Menu; + +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/NestedSorting.js b/js/dojo/dojox/grid/enhanced/plugins/NestedSorting.js new file mode 100644 index 0000000..a27cddb --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/NestedSorting.js @@ -0,0 +1,610 @@ +//>>built +define("dojox/grid/enhanced/plugins/NestedSorting", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/connect", + "dojo/_base/lang", + "dojo/_base/html", + "dojo/_base/event", + "dojo/_base/window", + "dojo/keys", + "dojo/query", + "dojo/string", + "../_Plugin", + "../../EnhancedGrid" +], function(declare, array, connect, lang, html, evt, win, keys, query, string, _Plugin, EnhancedGrid){ + +var NestedSorting = declare("dojox.grid.enhanced.plugins.NestedSorting", _Plugin, { + // summary: + // Provides nested sorting feature + // + // description: + // A flexible way to control multiple column sorting, including + // 1. Set default sorting order + // 2. Disable sorting for certain columns + // 3. Set sorting order dynamically with JS API + // + // example: + // | <script type="text/javascript"> + // | var grid = new dojox.grid.EnhancedGrid({plugins : {nestedSorting: true}}, + // | sortFields: [{attribute: 'col4', descending: false},...],//set default sorting order + // | canSort: function(index, field){ return true},//disable sorting for a column + // | ... }, dojo.byId('gridDiv')); + // | grid.startup(); + // | //set new sorting order + // | grid.setSortIndex([{attribute: 'col3', descending: true},...]) + // | </script> + + // name: String + // Plugin name + name: "nestedSorting", + + _currMainSort: 'none',//'none'|'asc'|'desc' + + _currRegionIdx: -1, + + _a11yText: { + 'dojoxGridDescending' : '▾', + 'dojoxGridAscending' : '▴', + 'dojoxGridAscendingTip' : '۸', + 'dojoxGridDescendingTip': '۷', + 'dojoxGridUnsortedTip' : 'x' //'✖' + }, + + constructor: function(){ + this._sortDef = []; + this._sortData = {}; + this._headerNodes = {}; + //column index that are hidden, un-sortable or indirect selection etc. + this._excludedColIdx = []; + this.nls = this.grid._nls; + this.grid.setSortInfo = function(){}; + this.grid.setSortIndex = lang.hitch(this, '_setGridSortIndex'); + this.grid.getSortIndex = function(){}; + this.grid.getSortProps = lang.hitch(this, 'getSortProps'); + if(this.grid.sortFields){ + this._setGridSortIndex(this.grid.sortFields, null, true); + } + this.connect(this.grid.views, 'render', '_initSort');//including column resize + this.initCookieHandler(); + this.subscribe("dojox/grid/rearrange/move/" + this.grid.id, lang.hitch(this, '_onColumnDnD')); + }, + onStartUp: function(){ + //overwrite base Grid functions + this.inherited(arguments); + this.connect(this.grid, 'onHeaderCellClick', '_onHeaderCellClick'); + this.connect(this.grid, 'onHeaderCellMouseOver', '_onHeaderCellMouseOver'); + this.connect(this.grid, 'onHeaderCellMouseOut', '_onHeaderCellMouseOut'); + }, + _onColumnDnD: function(type, mapping){ + // summary: + // Update nested sorting after column moved + if(type !== 'col'){return;} + var m = mapping, obj = {}, d = this._sortData, p; + var cr = this._getCurrentRegion(); + this._blurRegion(cr); + var idx = this._getRegionHeader(cr).getAttribute('idx'); + for(p in m){ + if(d[p]){ + obj[m[p]] = d[p]; + delete d[p]; + } + if(p === idx){ + idx = m[p]; + } + } + for(p in obj){ + d[p] = obj[p]; + } + var c = this._headerNodes[idx]; + this._currRegionIdx = array.indexOf(this._getRegions(), c.firstChild); + this._initSort(false); + }, + _setGridSortIndex: function(inIndex, inAsc, noRefresh){ + if(lang.isArray(inIndex)){ + var i, d, cell; + for(i = 0; i < inIndex.length; i++){ + d = inIndex[i]; + cell = this.grid.getCellByField(d.attribute); + if(!cell){ + console.warn('Invalid sorting option, column ', d.attribute, ' not found.'); + return; + } + if(cell['nosort'] || !this.grid.canSort(cell.index, cell.field)){ + console.warn('Invalid sorting option, column ', d.attribute, ' is unsortable.'); + return; + } + } + this.clearSort(); + array.forEach(inIndex, function(d, i){ + cell = this.grid.getCellByField(d.attribute); + this.setSortData(cell.index, 'index', i); + this.setSortData(cell.index, 'order', d.descending ? 'desc': 'asc'); + }, this); + }else if(!isNaN(inIndex)){ + if(inAsc === undefined){ return; }//header click from base DataGrid + this.setSortData(inIndex, 'order', inAsc ? 'asc' : 'desc'); + }else{ + return; + } + this._updateSortDef(); + if(!noRefresh){ + this.grid.sort(); + } + }, + getSortProps: function(){ + // summary: + // Overwritten, see DataGrid.getSortProps() + return this._sortDef.length ? this._sortDef : null; + }, + _initSort: function(postSort){ + // summary: + // Initiate sorting + var g = this.grid, n = g.domNode, len = this._sortDef.length; + html.toggleClass(n, 'dojoxGridSorted', !!len); + html.toggleClass(n, 'dojoxGridSingleSorted', len === 1); + html.toggleClass(n, 'dojoxGridNestSorted', len > 1); + if(len > 0){ + this._currMainSort = this._sortDef[0].descending ? 'desc' : 'asc'; + } + var idx, excluded = this._excludedCoIdx = [];//reset it + //cache column index of hidden, un-sortable or indirect selection + this._headerNodes = query("th", g.viewsHeaderNode).forEach(function(n){ + idx = parseInt(n.getAttribute('idx'), 10); + if(html.style(n, 'display') === 'none' || g.layout.cells[idx]['nosort'] || (g.canSort && !g.canSort(idx, g.layout.cells[idx]['field']))){ + excluded.push(idx); + } + }); + this._headerNodes.forEach(this._initHeaderNode, this); + this._initFocus(); + if(postSort){ + this._focusHeader(); + } + }, + _initHeaderNode: function(node){ + // summary: + // Initiate sort for each header cell node + html.toggleClass(node, 'dojoxGridSortNoWrap', true); + var sortNode = query('.dojoxGridSortNode', node)[0]; + if(sortNode){ + html.toggleClass(sortNode, 'dojoxGridSortNoWrap', true); + } + if(array.indexOf(this._excludedCoIdx, node.getAttribute('idx')) >= 0){ + html.addClass(node, 'dojoxGridNoSort'); + return; + } + if(!query('.dojoxGridSortBtn', node).length){ + //clear any previous connects + this._connects = array.filter(this._connects, function(conn){ + if(conn._sort){ + connect.disconnect(conn); + return false; + } + return true; + }); + var n = html.create('a', { + className: 'dojoxGridSortBtn dojoxGridSortBtnNested', + title: string.substitute(this.nls.sortingState, [this.nls.nestedSort, this.nls.ascending]), + innerHTML: '1' + }, node.firstChild, 'last'); + n.onmousedown = evt.stop; + n = html.create('a', { + className: 'dojoxGridSortBtn dojoxGridSortBtnSingle', + title: string.substitute(this.nls.sortingState, [this.nls.singleSort, this.nls.ascending]) + }, node.firstChild, 'last'); + n.onmousedown = evt.stop; + }else{ + //deal with small height grid which doesn't re-render the grid after refresh + var a1 = query('.dojoxGridSortBtnSingle', node)[0]; + var a2 = query('.dojoxGridSortBtnNested', node)[0]; + a1.className = 'dojoxGridSortBtn dojoxGridSortBtnSingle'; + a2.className = 'dojoxGridSortBtn dojoxGridSortBtnNested'; + a2.innerHTML = '1'; + html.removeClass(node, 'dojoxGridCellShowIndex'); + html.removeClass(node.firstChild, 'dojoxGridSortNodeSorted'); + html.removeClass(node.firstChild, 'dojoxGridSortNodeAsc'); + html.removeClass(node.firstChild, 'dojoxGridSortNodeDesc'); + html.removeClass(node.firstChild, 'dojoxGridSortNodeMain'); + html.removeClass(node.firstChild, 'dojoxGridSortNodeSub'); + } + this._updateHeaderNodeUI(node); + }, + _onHeaderCellClick: function(e){ + // summary + // See dojox.grid.enhanced._Events._onHeaderCellClick() + this._focusRegion(e.target); + if(html.hasClass(e.target, 'dojoxGridSortBtn')){ + this._onSortBtnClick(e); + evt.stop(e); + this._focusRegion(this._getCurrentRegion()); + } + }, + _onHeaderCellMouseOver: function(e){ + // summary + // See dojox.grid._Events._onHeaderCellMouseOver() + // When user mouseover other columns than sorted column in a single sorted grid, + // We need to show 1 in the sorted column + if(!e.cell){return; } + if(this._sortDef.length > 1){ return; } + if(this._sortData[e.cellIndex] && this._sortData[e.cellIndex].index === 0){ return; } + var p; + for(p in this._sortData){ + if(this._sortData[p] && this._sortData[p].index === 0){ + html.addClass(this._headerNodes[p], 'dojoxGridCellShowIndex'); + break; + } + } + if(!html.hasClass(win.body(), 'dijit_a11y')){ return; } + //a11y support + var i = e.cell.index, node = e.cellNode; + var singleSortBtn = query('.dojoxGridSortBtnSingle', node)[0]; + var nestedSortBtn = query('.dojoxGridSortBtnNested', node)[0]; + + var sortMode = 'none'; + if(html.hasClass(this.grid.domNode, 'dojoxGridSingleSorted')){ + sortMode = 'single'; + }else if(html.hasClass(this.grid.domNode, 'dojoxGridNestSorted')){ + sortMode = 'nested'; + } + var nestedIndex = nestedSortBtn.getAttribute('orderIndex'); + if(nestedIndex === null || nestedIndex === undefined){ + nestedSortBtn.setAttribute('orderIndex', nestedSortBtn.innerHTML); + nestedIndex = nestedSortBtn.innerHTML; + } + if(this.isAsc(i)){ + nestedSortBtn.innerHTML = nestedIndex + this._a11yText.dojoxGridDescending; + }else if(this.isDesc(i)){ + nestedSortBtn.innerHTML = nestedIndex + this._a11yText.dojoxGridUnsortedTip; + }else{ + nestedSortBtn.innerHTML = nestedIndex + this._a11yText.dojoxGridAscending; + } + if(this._currMainSort === 'none'){ + singleSortBtn.innerHTML = this._a11yText.dojoxGridAscending; + }else if(this._currMainSort === 'asc'){ + singleSortBtn.innerHTML = this._a11yText.dojoxGridDescending; + }else if(this._currMainSort === 'desc'){ + singleSortBtn.innerHTML = this._a11yText.dojoxGridUnsortedTip; + } + }, + _onHeaderCellMouseOut: function(e){ + // summary + // See dojox.grid.enhanced._Events._onHeaderCellMouseOut() + var p; + for(p in this._sortData){ + if(this._sortData[p] && this._sortData[p].index === 0){ + html.removeClass(this._headerNodes[p], 'dojoxGridCellShowIndex'); + break; + } + } + }, + _onSortBtnClick: function(e){ + // summary: + // If the click target is single sort button, do single sort. + // Else if the click target is nested sort button, do nest sort. + // Otherwise return. + var cellIdx = e.cell.index; + if(html.hasClass(e.target, 'dojoxGridSortBtnSingle')){ + this._prepareSingleSort(cellIdx); + }else if(html.hasClass(e.target, 'dojoxGridSortBtnNested')){ + this._prepareNestedSort(cellIdx); + }else{ + return; + } + evt.stop(e); + this._doSort(cellIdx); + }, + _doSort: function(cellIdx){ + if(!this._sortData[cellIdx] || !this._sortData[cellIdx].order){ + this.setSortData(cellIdx, 'order', 'asc'); //no sorting data + }else if(this.isAsc(cellIdx)){ + this.setSortData(cellIdx, 'order', 'desc'); //change to 'desc' + }else if(this.isDesc(cellIdx)){ + this.removeSortData(cellIdx); //remove from sorting sequence + } + this._updateSortDef(); + this.grid.sort(); + this._initSort(true); + }, + setSortData: function(cellIdx, attr, value){ + // summary: + // Set sorting data for a column. + var sd = this._sortData[cellIdx]; + if(!sd){ + sd = this._sortData[cellIdx] = {}; + } + sd[attr] = value; + }, + removeSortData: function(cellIdx){ + var d = this._sortData, i = d[cellIdx].index, p; + delete d[cellIdx]; + for(p in d){ + if(d[p].index > i){ + d[p].index--; + } + } + }, + _prepareSingleSort: function(cellIdx){ + // summary: + // Prepare the single sort, also called main sort, this will clear any existing sorting and just sort the grid by current column. + var d = this._sortData, p; + for(p in d){ + delete d[p]; + } + this.setSortData(cellIdx, 'index', 0); + this.setSortData(cellIdx, 'order', this._currMainSort === 'none' ? null : this._currMainSort); + if(!this._sortData[cellIdx] || !this._sortData[cellIdx].order){ + this._currMainSort = 'asc'; + }else if(this.isAsc(cellIdx)){ + this._currMainSort = 'desc'; + }else if(this.isDesc(cellIdx)){ + this._currMainSort = 'none'; + } + }, + _prepareNestedSort: function(cellIdx){ + // summary + // Prepare the nested sorting, this will order the column on existing sorting result. + var i = this._sortData[cellIdx] ? this._sortData[cellIdx].index : null; + if(i === 0 || !!i){ return; } + this.setSortData(cellIdx, 'index', this._sortDef.length); + }, + _updateSortDef: function(){ + this._sortDef.length = 0; + var d = this._sortData, p; + for(p in d){ + this._sortDef[d[p].index] = { + attribute: this.grid.layout.cells[p].field, + descending: d[p].order === 'desc' + }; + } + }, + _updateHeaderNodeUI: function(node){ + // summary: + // Update the column header UI based on current sorting state. + // Show indicator of the sorting order of the column, no order no indicator + var cell = this._getCellByNode(node); + var cellIdx = cell.index; + var data = this._sortData[cellIdx]; + var sortNode = query('.dojoxGridSortNode', node)[0]; + var singleSortBtn = query('.dojoxGridSortBtnSingle', node)[0]; + var nestedSortBtn = query('.dojoxGridSortBtnNested', node)[0]; + + html.toggleClass(singleSortBtn, 'dojoxGridSortBtnAsc', this._currMainSort === 'asc'); + html.toggleClass(singleSortBtn, 'dojoxGridSortBtnDesc', this._currMainSort === 'desc'); + if(this._currMainSort === 'asc'){ + singleSortBtn.title = string.substitute(this.nls.sortingState, [this.nls.singleSort, this.nls.descending]); + }else if(this._currMainSort === 'desc'){ + singleSortBtn.title = string.substitute(this.nls.sortingState, [this.nls.singleSort, this.nls.unsorted]); + }else{ + singleSortBtn.title = string.substitute(this.nls.sortingState, [this.nls.singleSort, this.nls.ascending]); + } + + var _this = this; + function setWaiState(){ + var columnInfo = 'Column ' + (cell.index + 1) + ' ' + cell.field; + var orderState = 'none'; + var orderAction = 'ascending'; + if(data){ + orderState = data.order === 'asc' ? 'ascending' : 'descending'; + orderAction = data.order === 'asc' ? 'descending' : 'none'; + } + var a11ySingleLabel = columnInfo + ' - is sorted by ' + orderState; + var a11yNestedLabel = columnInfo + ' - is nested sorted by ' + orderState; + var a11ySingleLabelHover = columnInfo + ' - choose to sort by ' + orderAction; + var a11yNestedLabelHover = columnInfo + ' - choose to nested sort by ' + orderAction; + + singleSortBtn.setAttribute("aria-label", a11ySingleLabel); + nestedSortBtn.setAttribute("aria-label", a11yNestedLabel); + + var handles = [ + _this.connect(singleSortBtn, "onmouseover", function(){ + singleSortBtn.setAttribute("aria-label", a11ySingleLabelHover); + }), + _this.connect(singleSortBtn, "onmouseout", function(){ + singleSortBtn.setAttribute("aria-label", a11ySingleLabel); + }), + _this.connect(nestedSortBtn, "onmouseover", function(){ + nestedSortBtn.setAttribute("aria-label", a11yNestedLabelHover); + }), + _this.connect(nestedSortBtn, "onmouseout", function(){ + nestedSortBtn.setAttribute("aria-label", a11yNestedLabel); + }) + ]; + array.forEach(handles, function(handle){ handle._sort = true; }); + } + setWaiState(); + + var a11y = html.hasClass(win.body(), "dijit_a11y"); + if(!data){ + nestedSortBtn.innerHTML = this._sortDef.length + 1; + nestedSortBtn.title = string.substitute(this.nls.sortingState, [this.nls.nestedSort, this.nls.ascending]); + if(a11y){sortNode.innerHTML = this._a11yText.dojoxGridUnsortedTip;} + return; + } + if(data.index || (data.index === 0 && this._sortDef.length > 1)){ + nestedSortBtn.innerHTML = data.index + 1; + } + html.addClass(sortNode, 'dojoxGridSortNodeSorted'); + if(this.isAsc(cellIdx)){ + html.addClass(sortNode, 'dojoxGridSortNodeAsc'); + nestedSortBtn.title = string.substitute(this.nls.sortingState, [this.nls.nestedSort, this.nls.descending]); + if(a11y){sortNode.innerHTML = this._a11yText.dojoxGridAscendingTip;} + }else if(this.isDesc(cellIdx)){ + html.addClass(sortNode, 'dojoxGridSortNodeDesc'); + nestedSortBtn.title = string.substitute(this.nls.sortingState, [this.nls.nestedSort, this.nls.unsorted]); + if(a11y){sortNode.innerHTML = this._a11yText.dojoxGridDescendingTip;} + } + html.addClass(sortNode, (data.index === 0 ? 'dojoxGridSortNodeMain' : 'dojoxGridSortNodeSub')); + }, + isAsc: function(cellIndex){ + return this._sortData[cellIndex].order === 'asc'; + }, + isDesc: function(cellIndex){ + return this._sortData[cellIndex].order === 'desc'; + }, + _getCellByNode: function(node){ + var i; + for(i = 0; i < this._headerNodes.length; i++){ + if(this._headerNodes[i] === node){ + return this.grid.layout.cells[i]; + } + } + return null; + }, + clearSort: function(){ + this._sortData = {}; + this._sortDef.length = 0; + }, + + //persistence + initCookieHandler: function(){ + if(this.grid.addCookieHandler){ + this.grid.addCookieHandler({ + name: "sortOrder", + onLoad: lang.hitch(this, '_loadNestedSortingProps'), + onSave: lang.hitch(this, '_saveNestedSortingProps') + }); + } + }, + _loadNestedSortingProps: function(sortInfo, grid){ + this._setGridSortIndex(sortInfo); + }, + _saveNestedSortingProps: function(grid){ + return this.getSortProps(); + }, + + //focus & keyboard + _initFocus: function(){ + var f = this.focus = this.grid.focus; + this._focusRegions = this._getRegions(); + if(!this._headerArea){ + var area = this._headerArea = f.getArea('header'); + area.onFocus = f.focusHeader = lang.hitch(this, '_focusHeader'); + area.onBlur = f.blurHeader = f._blurHeader = lang.hitch(this, '_blurHeader'); + area.onMove = lang.hitch(this, '_onMove'); + area.onKeyDown = lang.hitch(this, '_onKeyDown'); + area._regions = []; + area.getRegions = null; + this.connect(this.grid, 'onBlur', '_blurHeader'); + } + }, + _focusHeader: function(e){ + // summary: + // Overwritten, see _FocusManager.focusHeader() + //delayed: Boolean + // If called from "this.focus._delayedHeaderFocus()" + if(this._currRegionIdx === -1){ + this._onMove(0, 1, null); + }else{ + this._focusRegion(this._getCurrentRegion()); + } + try{ + evt.stop(e); + }catch(e){} + return true; + }, + _blurHeader: function(e){ + this._blurRegion(this._getCurrentRegion()); + return true; + }, + _onMove: function(rowStep, colStep, e){ + var curr = this._currRegionIdx || 0, regions = this._focusRegions; + var region = regions[curr + colStep]; + if(!region){ + return; + }else if(html.style(region, 'display') === 'none' || html.style(region, 'visibility') === 'hidden'){ + //if the region is invisible, keep finding next + this._onMove(rowStep, colStep + (colStep > 0 ? 1 : -1), e); + return; + } + this._focusRegion(region); + //keep grid body scrolled by header + var view = this._getRegionView(region); + view.scrollboxNode.scrollLeft = view.headerNode.scrollLeft; + }, + _onKeyDown: function(e, isBubble){ + if(isBubble){ + switch(e.keyCode){ + case keys.ENTER: + case keys.SPACE: + if(html.hasClass(e.target, 'dojoxGridSortBtnSingle') || + html.hasClass(e.target, 'dojoxGridSortBtnNested')){ + this._onSortBtnClick(e); + } + } + } + }, + _getRegionView: function(region){ + var header = region; + while(header && !html.hasClass(header, 'dojoxGridHeader')){ header = header.parentNode; } + if(header){ + return array.filter(this.grid.views.views, function(view){ + return view.headerNode === header; + })[0] || null; + } + return null; + }, + _getRegions: function(){ + var regions = [], cells = this.grid.layout.cells; + this._headerNodes.forEach(function(n, i){ + if(html.style(n, 'display') === 'none'){return;} + if(cells[i]['isRowSelector']){ + regions.push(n); + return; + } + query('.dojoxGridSortNode,.dojoxGridSortBtnNested,.dojoxGridSortBtnSingle', n).forEach(function(node){ + node.setAttribute('tabindex', 0); + regions.push(node); + }); + },this); + return regions; + }, + _focusRegion: function(region){ + // summary + // Focus the given region + if(!region){return;} + var currRegion = this._getCurrentRegion(); + if(currRegion && region !== currRegion){ + this._blurRegion(currRegion); + } + var header = this._getRegionHeader(region); + html.addClass(header, 'dojoxGridCellSortFocus'); + if(html.hasClass(region, 'dojoxGridSortNode')){ + html.addClass(region, 'dojoxGridSortNodeFocus'); + }else if(html.hasClass(region, 'dojoxGridSortBtn')){ + html.addClass(region, 'dojoxGridSortBtnFocus'); + } + region.focus(); + this.focus.currentArea('header'); + this._currRegionIdx = array.indexOf(this._focusRegions, region); + }, + _blurRegion: function(region){ + if(!region){return;} + var header = this._getRegionHeader(region); + html.removeClass(header, 'dojoxGridCellSortFocus'); + if(html.hasClass(region, 'dojoxGridSortNode')){ + html.removeClass(region, 'dojoxGridSortNodeFocus'); + }else if(html.hasClass(region, 'dojoxGridSortBtn')){ + html.removeClass(region, 'dojoxGridSortBtnFocus'); + } + region.blur(); + }, + _getCurrentRegion: function(){ + return this._focusRegions ? this._focusRegions[this._currRegionIdx] : null; + }, + _getRegionHeader: function(region){ + while(region && !html.hasClass(region, 'dojoxGridCell')){ + region = region.parentNode; + } + return region; + }, + destroy: function(){ + this._sortDef = this._sortData = null; + this._headerNodes = this._focusRegions = null; + this.inherited(arguments); + } +}); + +EnhancedGrid.registerPlugin(NestedSorting); + +return NestedSorting; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/Pagination.js b/js/dojo/dojox/grid/enhanced/plugins/Pagination.js new file mode 100644 index 0000000..244d79d --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Pagination.js @@ -0,0 +1,974 @@ +//>>built +require({cache:{ +'url:dojox/grid/enhanced/templates/Pagination.html':"<div dojoAttachPoint=\"paginatorBar\"\n\t><table cellpadding=\"0\" cellspacing=\"0\" class=\"dojoxGridPaginator\"\n\t\t><tr\n\t\t\t><td dojoAttachPoint=\"descriptionTd\" class=\"dojoxGridDescriptionTd\"\n\t\t\t\t><div dojoAttachPoint=\"descriptionDiv\" class=\"dojoxGridDescription\"></div\n\t\t\t></div></td\n\t\t\t><td dojoAttachPoint=\"sizeSwitchTd\"></td\n\t\t\t><td dojoAttachPoint=\"pageStepperTd\" class=\"dojoxGridPaginatorFastStep\"\n\t\t\t\t><div dojoAttachPoint=\"pageStepperDiv\" class=\"dojoxGridPaginatorStep\"></div\n\t\t\t></td\n\t\t\t><td dojoAttachPoint=\"gotoPageTd\" class=\"dojoxGridPaginatorGotoTd\"\n\t\t\t\t><div dojoAttachPoint=\"gotoPageDiv\" class=\"dojoxGridPaginatorGotoDiv\" dojoAttachEvent=\"onclick:_openGotopageDialog, onkeydown:_openGotopageDialog\"\n\t\t\t\t\t><span class=\"dojoxGridWardButtonInner\">⊥</span\n\t\t\t\t></div\n\t\t\t></td\n\t\t></tr\n\t></table\n></div>\n"}}); +define("dojox/grid/enhanced/plugins/Pagination", [ + "dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/connect", + "dojo/_base/lang", + "dojo/_base/html", + "dojo/_base/event", + "dojo/_base/window", + "dojo/query", + "dojo/string", + "dojo/i18n", + "dojo/keys", + "dojo/text!../templates/Pagination.html", + "./Dialog", + "./_StoreLayer", + "../_Plugin", + "../../EnhancedGrid", + "dijit/form/Button", + "dijit/form/NumberTextBox", + "dijit/focus", + "dijit/_Widget", + "dijit/_TemplatedMixin", + "dijit/_WidgetsInTemplateMixin", + "dojox/html/metrics", + "dojo/i18n!../nls/Pagination" +], function(kernel, declare, array, connect, lang, html, event, win, query, + string, i18n, keys, template, Dialog, layers, _Plugin, EnhancedGrid, + Button, NumberTextBox, dijitFocus, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, metrics){ + +var _GotoPagePane = declare("dojox.grid.enhanced.plugins.pagination._GotoPagePane", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], { + templateString: "<div>" + + "<div class='dojoxGridDialogMargin' dojoAttachPoint='_mainMsgNode'></div>" + + "<div class='dojoxGridDialogMargin'>" + + "<input dojoType='dijit.form.NumberTextBox' style='width: 50px;' dojoAttachPoint='_pageInputBox' dojoAttachEvent='onKeyUp: _onKey'></input>" + + "<label dojoAttachPoint='_pageLabelNode'></label>" + + "</div>" + + "<div class='dojoxGridDialogButton'>" + + "<button dojoType='dijit.form.Button' dojoAttachPoint='_confirmBtn' dojoAttachEvent='onClick: _onConfirm'></button>" + + "<button dojoType='dijit.form.Button' dojoAttachPoint='_cancelBtn' dojoAttachEvent='onClick: _onCancel'></button>" + + "</div>" + + "</div>", + widgetsInTemplate: true, + dlg: null, + postMixInProperties: function(){ + this.plugin = this.dlg.plugin; + }, + postCreate: function(){ + this.inherited(arguments); + this._mainMsgNode.innerHTML = this.plugin._nls[12]; + this._confirmBtn.set("label", this.plugin._nls[14]); + this._confirmBtn.set("disabled", true); + this._cancelBtn.set("label", this.plugin._nls[15]); + }, + _onConfirm: function(evt){ + if(this._pageInputBox.isValid() && this._pageInputBox.getDisplayedValue() !== ""){ + this.plugin.currentPage(this._pageInputBox.parse(this._pageInputBox.getDisplayedValue())); + this.dlg._gotoPageDialog.hide(); + this._pageInputBox.reset(); + } + stopEvent(evt); + }, + _onCancel: function(evt){ + this._pageInputBox.reset(); + this.dlg._gotoPageDialog.hide(); + stopEvent(evt); + }, + _onKey: function(evt){ + this._confirmBtn.set("disabled", !this._pageInputBox.isValid() || this._pageInputBox.getDisplayedValue() == ""); + if(!evt.altKey && !evt.metaKey && evt.keyCode === keys.ENTER){ + this._onConfirm(evt); + } + } +}); + +var _GotoPageDialog = declare("dojox.grid.enhanced.plugins.pagination._GotoPageDialog", null, { + pageCount: 0, + dlgPane: null, + constructor: function(plugin){ + this.plugin = plugin; + this.dlgPane = new _GotoPagePane({"dlg": this}); + this.dlgPane.startup(); + this._gotoPageDialog = new Dialog({ + "refNode": plugin.grid.domNode, + "title": this.plugin._nls[11], + "content": this.dlgPane + }); + this._gotoPageDialog.startup(); + }, + _updatePageCount: function(){ + this.pageCount = this.plugin.getTotalPageNum(); + this.dlgPane._pageInputBox.constraints = {fractional:false, min:1, max:this.pageCount}; + this.dlgPane._pageLabelNode.innerHTML = string.substitute(this.plugin._nls[13], [this.pageCount]); + }, + showDialog: function(){ + this._updatePageCount(); + this._gotoPageDialog.show(); + }, + destroy: function(){ + this._gotoPageDialog.destroy(); + } +}); + +var _ForcedPageStoreLayer = declare("dojox.grid.enhanced.plugins._ForcedPageStoreLayer", layers._StoreLayer, { + tags: ["presentation"], + constructor: function(plugin){ + this._plugin = plugin; + }, + _fetch: function(request){ + var _this = this, + plugin = _this._plugin, + grid = plugin.grid, + scope = request.scope || win.global, + onBegin = request.onBegin; + request.start = (plugin._currentPage - 1) * plugin._currentPageSize + request.start; + _this.startIdx = request.start; + _this.endIdx = request.start + plugin._currentPageSize - 1; + var p = plugin._paginator; + if(!plugin._showAll){ + plugin._showAll = !p.sizeSwitch && !p.pageStepper && !p.gotoButton; + } + if(onBegin && plugin._showAll){ + request.onBegin = function(size, req){ + plugin._maxSize = plugin._currentPageSize = size; + _this.startIdx = 0; + _this.endIdx = size - 1; + plugin._paginator._update(); + req.onBegin = onBegin; + req.onBegin.call(scope, size, req); + }; + }else if(onBegin){ + request.onBegin = function(size, req){ + req.start = 0; + req.count = plugin._currentPageSize; + plugin._maxSize = size; + _this.endIdx = _this.endIdx >= size ? (size - 1) : _this.endIdx; + if(_this.startIdx > size && size !== 0){ + grid._pending_requests[req.start] = false; + plugin.firstPage(); + } + plugin._paginator._update(); + req.onBegin = onBegin; + req.onBegin.call(scope, Math.min(plugin._currentPageSize, (size - _this.startIdx)), req); + }; + } + return lang.hitch(this._store, this._originFetch)(request); + } +}); + +var stopEvent = function(evt){ + try{ + event.stop(evt); + }catch(e){} +}; + +var _Focus = declare("dojox.grid.enhanced.plugins.pagination._Focus", null, { + _focusedNode: null, + _isFocused: false, + constructor: function(paginator){ + this._pager = paginator; + var focusMgr = paginator.plugin.grid.focus; + paginator.plugin.connect(paginator, 'onSwitchPageSize', lang.hitch(this, '_onActive')); + paginator.plugin.connect(paginator, 'onPageStep', lang.hitch(this, '_onActive')); + paginator.plugin.connect(paginator, 'onShowGotoPageDialog', lang.hitch(this, '_onActive')); + paginator.plugin.connect(paginator, '_update', lang.hitch(this, '_moveFocus')); + }, + _onFocus: function(evt, step){ + var node, nodes; + if(!this._isFocused){ + node = this._focusedNode || query('[tabindex]', this._pager.domNode)[0]; + }else if(step && this._focusedNode){ + var dir = step > 0 ? -1 : 1, + tabindex = parseInt(this._focusedNode.getAttribute('tabindex'), 10) + dir; + while(tabindex >= -3 && tabindex < 0){ + node = query('[tabindex=' + tabindex + ']', this._pager.domNode)[0]; + if(node){ + break; + }else{ + tabindex += dir; + } + } + } + return this._focus(node, evt); + }, + _onBlur: function(evt, step){ + if(!step || !this._focusedNode){ + this._isFocused = false; + if(this._focusedNode && html.hasClass(this._focusedNode, 'dojoxGridButtonFocus')){ + html.removeClass(this._focusedNode, 'dojoxGridButtonFocus'); + } + return true; + } + var node, dir = step > 0 ? -1 : 1, + tabindex = parseInt(this._focusedNode.getAttribute('tabindex'), 10) + dir; + while(tabindex >= -3 && tabindex < 0){ + node = query('[tabindex=' + tabindex + ']', this._pager.domNode)[0]; + if(node){ + break; + }else{ + tabindex += dir; + } + } + if(!node){ + this._isFocused = false; + if(html.hasClass(this._focusedNode, 'dojoxGridButtonFocus')){ + html.removeClass(this._focusedNode, 'dojoxGridButtonFocus'); + } + } + return node ? false : true; + }, + _onMove: function(rowDelta, colDelta, evt){ + if(this._focusedNode){ + var tabindex = this._focusedNode.getAttribute('tabindex'), + delta = colDelta == 1 ? "nextSibling" : "previousSibling", + node = this._focusedNode[delta]; + while(node){ + if(node.getAttribute('tabindex') == tabindex){ + this._focus(node); + break; + } + node = node[delta]; + } + } + }, + _focus: function(node, evt){ + if(node){ + this._isFocused = true; + if(kernel.isIE && this._focusedNode){ + html.removeClass(this._focusedNode, 'dojoxGridButtonFocus'); + } + this._focusedNode = node; + node.focus(); + if(kernel.isIE){ + html.addClass(node, 'dojoxGridButtonFocus'); + } + stopEvent(evt); + return true; + } + return false; + }, + _onActive: function(e){ + this._focusedNode = e.target; + if(!this._isFocused){ + this._pager.plugin.grid.focus.focusArea('pagination' + this._pager.position); + } + }, + _moveFocus: function(){ + if(this._focusedNode && !this._focusedNode.getAttribute('tabindex')){ + var next = this._focusedNode.nextSibling; + while(next){ + if(next.getAttribute('tabindex')){ + this._focus(next); + return; + } + next = next.nextSibling; + } + var prev = this._focusedNode.previousSibling; + while(prev){ + if(prev.getAttribute('tabindex')){ + this._focus(prev); + return; + } + prev = prev.previousSibling; + } + this._focusedNode = null; + this._onBlur(); + }else if(kernel.isIE && this._focusedNode){ + html.addClass(this._focusedNode, 'dojoxGridButtonFocus'); + } + } +}); + +var _Paginator = declare("dojox.grid.enhanced.plugins._Paginator", [_Widget, _TemplatedMixin], { + templateString: template, + constructor: function(params){ + lang.mixin(this, params); + this.grid = this.plugin.grid; + }, + postCreate: function(){ + this.inherited(arguments); + var _this = this, g = this.grid; + this.plugin.connect(g, "_resize", lang.hitch(this, "_resetGridHeight")); + this._originalResize = g.resize; + g.resize = function(changeSize, resultSize){ + _this._changeSize = changeSize; + _this._resultSize = resultSize; + _this._originalResize.apply(g, arguments); + }; + this.focus = _Focus(this); + this._placeSelf(); + }, + destroy: function(){ + this.inherited(arguments); + this.grid.focus.removeArea("pagination" + this.position); + if(this._gotoPageDialog){ + this._gotoPageDialog.destroy(); + } + this.grid.resize = this._originalResize; + }, + onSwitchPageSize: function(/*Event*/evt){ + + }, + onPageStep: function(/*Event*/evt){ + + }, + onShowGotoPageDialog: function(/*Event*/evt){ + + }, + _update: function(){ + // summary: + // Function to update paging information and update + // pagination bar display. + this._updateDescription(); + this._updatePageStepper(); + this._updateSizeSwitch(); + this._updateGotoButton(); + }, + _registerFocus: function(isTop){ + // summary: + // Function to register pagination bar to focus manager. + var focusMgr = this.grid.focus, + name = "pagination" + this.position, + f = this.focus; + focusMgr.addArea({ + name: name, + onFocus: lang.hitch(this.focus, "_onFocus"), + onBlur: lang.hitch(this.focus, "_onBlur"), + onMove: lang.hitch(this.focus, "_onMove") + }); + focusMgr.placeArea(name, isTop ? "before" : "after", isTop ? "header" : "content"); + }, + _placeSelf: function(){ + // summary: + // Place pagination bar to a position. + // There are two options, top of the grid, bottom of the grid. + var g = this.grid, + isTop = this.position == "top"; + this.placeAt(isTop ? g.viewsHeaderNode : g.viewsNode, isTop ? "before" : "after"); + this._registerFocus(isTop); + }, + _resetGridHeight: function(changeSize, resultSize){ + // summary: + // Function of resize grid height to place this pagination bar. + // Since the grid would be able to add other element in its domNode, we have + // change the grid view size to place the pagination bar. + // This function will resize the grid viewsNode height, scorllboxNode height + var g = this.grid; + changeSize = changeSize || this._changeSize; + resultSize = resultSize || this._resultSize; + delete this._changeSize; + delete this._resultSize; + if(g._autoHeight){ + return; + } + var padBorder = g._getPadBorder().h; + if(!this.plugin.gh){ + this.plugin.gh = html.contentBox(g.domNode).h + 2 * padBorder; + } + if(resultSize){ + changeSize = resultSize; + } + if(changeSize){ + this.plugin.gh = html.contentBox(g.domNode).h + 2 * padBorder; + } + var gh = this.plugin.gh, + hh = g._getHeaderHeight(), + ph = html.marginBox(this.domNode).h; + // ph = this.plugin._paginator.position == "bottom" ? ph * 2 : ph; + if(typeof g.autoHeight === "number"){ + var cgh = gh + ph - padBorder; + html.style(g.domNode, "height", cgh + "px"); + html.style(g.viewsNode, "height", (cgh - ph - hh) + "px"); + this._styleMsgNode(hh, html.marginBox(g.viewsNode).w, cgh - ph - hh); + }else{ + var h = gh - ph - hh - padBorder; + html.style(g.viewsNode, "height", h + "px"); + var hasHScroller = array.some(g.views.views, function(v){ + return v.hasHScrollbar(); + }); + array.forEach(g.viewsNode.childNodes, function(c){ + html.style(c, "height", h + "px"); + }); + array.forEach(g.views.views, function(v){ + if(v.scrollboxNode){ + if(!v.hasHScrollbar() && hasHScroller){ + html.style(v.scrollboxNode, "height", (h - metrics.getScrollbar().h) + "px"); + }else{ + html.style(v.scrollboxNode, "height", h + "px"); + } + } + }); + this._styleMsgNode(hh, html.marginBox(g.viewsNode).w, h); + } + }, + _styleMsgNode: function(top, width, height){ + var messagesNode = this.grid.messagesNode; + html.style(messagesNode, {"position": "absolute", "top": top + "px", "width": width + "px", "height": height + "px", "z-Index": "100"}); + }, + _updateDescription: function(){ + // summary: + // Update size information. + var s = this.plugin.forcePageStoreLayer, + maxSize = this.plugin._maxSize, + nls = this.plugin._nls, + getItemTitle = function(){ + return maxSize <= 0 || maxSize == 1 ? nls[5] : nls[4]; + }; + if(this.description && this.descriptionDiv){ + this.descriptionDiv.innerHTML = maxSize > 0 ? string.substitute(nls[0], [getItemTitle(), maxSize, s.startIdx + 1, s.endIdx + 1]) : "0 " + getItemTitle(); + } + }, + _updateSizeSwitch: function(){ + // summary: + // Update "items per page" information. + html.style(this.sizeSwitchTd, "display", this.sizeSwitch ? "" : "none"); + if(!this.sizeSwitch){ + return; + } + if(this.sizeSwitchTd.childNodes.length < 1){ + this._createSizeSwitchNodes(); + } + this._updateSwitchNodesStyle(); + }, + _createSizeSwitchNodes: function(){ + // summary: + // The function to create the size switch nodes + var node = null, + nls = this.plugin._nls, + connect = lang.hitch(this.plugin, 'connect'); + array.forEach(this.pageSizes, function(size){ + // create page size switch node + var labelValue = isFinite(size) ? string.substitute(nls[2], [size]) : nls[1], + value = isFinite(size) ? size : nls[16]; + node = html.create("span", {innerHTML: value, title: labelValue, value: size, tabindex: "-1"}, this.sizeSwitchTd, "last"); + // for accessibility + node.setAttribute("aria-label", labelValue); + // connect event + connect(node, "onclick", lang.hitch(this, "_onSwitchPageSize")); + connect(node, "onkeydown", lang.hitch(this, "_onSwitchPageSize")); + connect(node, "onmouseover", function(e){ + html.addClass(e.target, "dojoxGridPageTextHover"); + }); + connect(node, "onmouseout", function(e){ + html.removeClass(e.target, "dojoxGridPageTextHover"); + }); + // create a separation node + node = html.create("span", {innerHTML: "|"}, this.sizeSwitchTd, "last"); + html.addClass(node, "dojoxGridSeparator"); + }, this); + // delete last separation node + html.destroy(node); + }, + _updateSwitchNodesStyle: function(){ + // summary: + // Update the switch nodes style + var size = null; + var styleNode = function(node, status){ + if(status){ + html.addClass(node, "dojoxGridActivedSwitch"); + html.removeAttr(node, "tabindex"); + }else{ + html.addClass(node, "dojoxGridInactiveSwitch"); + node.setAttribute("tabindex", "-1"); + } + }; + array.forEach(this.sizeSwitchTd.childNodes, function(node){ + if(node.value){ + html.removeClass(node); + size = node.value; + if(this.plugin._showAll){ + styleNode(node, isNaN(parseInt(size, 10))); + }else{ + styleNode(node, this.plugin._currentPageSize == size); + } + } + }, this); + }, + _updatePageStepper: function(){ + // summary: + // Update the page step nodes + html.style(this.pageStepperTd, "display", this.pageStepper ? "" : "none"); + if(!this.pageStepper){ + return; + } + if(this.pageStepperDiv.childNodes.length < 1){ + this._createPageStepNodes(); + this._createWardBtns(); + }else{ + this._resetPageStepNodes(); + } + this._updatePageStepNodesStyle(); + }, + _createPageStepNodes: function(){ + // summary: + // Create the page step nodes if they do not exist + var startPage = this._getStartPage(), + stepSize = this._getStepPageSize(), + label = "", node = null, i = startPage, + connect = lang.hitch(this.plugin, 'connect'); + for(; i < startPage + this.maxPageStep + 1; i++){ + label = string.substitute(this.plugin._nls[3], [i]); + node = html.create("div", {innerHTML: i, value: i, title: label}, this.pageStepperDiv, "last"); + node.setAttribute("aria-label", label); + // connect event + connect(node, "onclick", lang.hitch(this, "_onPageStep")); + connect(node, "onkeydown", lang.hitch(this, "_onPageStep")); + connect(node, "onmouseover", function(e){ + html.addClass(e.target, "dojoxGridPageTextHover"); + }); + connect(node, "onmouseout", function(e){ + html.removeClass(e.target, "dojoxGridPageTextHover"); + }); + html.style(node, "display", i < startPage + stepSize ? "" : "none"); + } + }, + _createWardBtns: function(){ + // summary: + // Create the previous/next/first/last button + var _this = this, nls = this.plugin._nls; + var highContrastLabel = {prevPage: "<", firstPage: "«", nextPage: ">", lastPage: "»"}; + var createWardBtn = function(value, label, position){ + var node = html.create("div", {value: value, title: label, tabindex: "-2"}, _this.pageStepperDiv, position); + _this.plugin.connect(node, "onclick", lang.hitch(_this, "_onPageStep")); + _this.plugin.connect(node, "onkeydown", lang.hitch(_this, "_onPageStep")); + node.setAttribute("aria-label", label); + // for high contrast + var highConrastNode = html.create("span", {value: value, title: label, innerHTML: highContrastLabel[value]}, node, position); + html.addClass(highConrastNode, "dojoxGridWardButtonInner"); + }; + createWardBtn("prevPage", nls[6], "first"); + createWardBtn("firstPage", nls[7], "first"); + createWardBtn("nextPage", nls[8], "last"); + createWardBtn("lastPage", nls[9], "last"); + }, + _resetPageStepNodes: function(){ + // summary: + // The page step nodes might be changed when fetch data, we need to + // update/reset them + var startPage = this._getStartPage(), + stepSize = this._getStepPageSize(), + stepNodes = this.pageStepperDiv.childNodes, + node = null, i = startPage, j = 2, tip; + for(; j < stepNodes.length - 2; j++, i++){ + node = stepNodes[j]; + if(i < startPage + stepSize){ + tip = string.substitute(this.plugin._nls[3], [i]); + html.attr(node, { + "innerHTML": i, + "title": tip, + "value": i + }); + html.style(node, "display", ""); + node.setAttribute("aria-label", tip); + }else{ + html.style(node, "display", "none"); + } + } + }, + _updatePageStepNodesStyle: function(){ + // summary: + // Update the style of the page step nodes + var value = null, + curPage = this.plugin.currentPage(), + pageCount = this.plugin.getTotalPageNum(); + var updateClass = function(node, isWardBtn, status){ + var value = node.value, + enableClass = isWardBtn ? "dojoxGrid" + value + "Btn" : "dojoxGridInactived", + disableClass = isWardBtn ? "dojoxGrid" + value + "BtnDisable" : "dojoxGridActived"; + if(status){ + html.addClass(node, disableClass); + html.removeAttr(node, "tabindex"); + }else{ + html.addClass(node, enableClass); + node.setAttribute("tabindex", "-2"); + } + }; + array.forEach(this.pageStepperDiv.childNodes, function(node){ + html.removeClass(node); + if(isNaN(parseInt(node.value, 10))){ + html.addClass(node, "dojoxGridWardButton"); + var disablePageNum = node.value == "prevPage" || node.value == "firstPage" ? 1 : pageCount; + updateClass(node, true, (curPage === disablePageNum)); + }else{ + value = parseInt(node.value, 10); + updateClass(node, false, (value === curPage || html.style(node, "display") === "none")); + } + }, this); + }, + _showGotoButton: function(flag){ + this.gotoButton = flag; + this._updateGotoButton(); + }, + _updateGotoButton: function(){ + // summary: + // Create/destroy the goto page button + if(!this.gotoButton){ + if(this._gotoPageDialog){ + this._gotoPageDialog.destroy(); + } + html.removeAttr(this.gotoPageDiv, "tabindex"); + html.style(this.gotoPageTd, 'display', 'none'); + return; + } + if(html.style(this.gotoPageTd, 'display') == 'none'){ + html.style(this.gotoPageTd, 'display', ''); + } + this.gotoPageDiv.setAttribute('title', this.plugin._nls[10]); + html.toggleClass(this.gotoPageDiv, "dojoxGridPaginatorGotoDivDisabled", this.plugin.getTotalPageNum() <= 1); + if(this.plugin.getTotalPageNum() <= 1){ + html.removeAttr(this.gotoPageDiv, "tabindex"); + }else{ + this.gotoPageDiv.setAttribute("tabindex", "-3"); + } + }, + _openGotopageDialog: function(e){ + // summary: + // Show the goto page dialog + if(this.plugin.getTotalPageNum() <= 1){ + return; + } + if(e.type === "keydown" && e.keyCode !== keys.ENTER && e.keyCode !== keys.SPACE){ + return; + } + if(!this._gotoPageDialog){ + this._gotoPageDialog = new _GotoPageDialog(this.plugin); + } + this._gotoPageDialog.showDialog(); + this.onShowGotoPageDialog(e); + }, + _onSwitchPageSize: function(/*Event*/e){ + // summary: + // The handler of switch the page size + if(e.type === "keydown" && e.keyCode !== keys.ENTER && e.keyCode !== keys.SPACE){ + return; + } + this.onSwitchPageSize(e); + this.plugin.currentPageSize(e.target.value); + }, + _onPageStep: function(/*Event*/e){ + // summary: + // The handler jump page event + if(e.type === "keydown" && e.keyCode !== keys.ENTER && e.keyCode !== keys.SPACE){ + return; + } + var p = this.plugin, + value = e.target.value; + this.onPageStep(e); + if(!isNaN(parseInt(value, 10))){ + p.currentPage(parseInt(value, 10)); + }else{ + p[value](); + } + }, + _getStartPage: function(){ + var cp = this.plugin.currentPage(), + ms = this.maxPageStep, + hs = parseInt(ms / 2, 10), + tp = this.plugin.getTotalPageNum(); + if(cp < hs || (cp - hs) < 1 || tp <= ms){ + return 1; + }else{ + return tp - cp < hs && cp - ms >= 0 ? tp - ms + 1 : cp - hs; + } + }, + _getStepPageSize: function(){ + var sp = this._getStartPage(), + tp = this.plugin.getTotalPageNum(), + ms = this.maxPageStep; + return sp + ms > tp ? tp - sp + 1 : ms; + } +}); + +var Pagination = declare("dojox.grid.enhanced.plugins.Pagination", _Plugin, { + // summary: + // The typical pagination way to deal with huge dataset + // an alternative for the default virtual scrolling manner. + name: "pagination", + // defaultPageSize: Integer + // Number of rows in a page, 25 by default. + defaultPageSize: 25, + // defaultPage: Integer + // Which page will be displayed initially, 1st page by default. + defaultPage: 1, + // description: boolean + // Whether the description information will be displayed, true by default. + description: true, + // sizeSwitch: boolean + // Whether the page size switch options will be displayed, true by default. + sizeSwitch: true, + // pageStepper: boolean + // Whether the page switch options will be displayed, true by default. + pageStepper: true, + // gotoButton: boolean + // Whether the goto page button will be displayed, false by default. + gotoButton: false, + // pageSizes: Array + // Array of page sizes for switching, e.g. [10, 25, 50, 100, Infinity] by default, + // Infinity or any NaN value will be treated as "all". + pageSizes: [10, 25, 50, 100, Infinity], + // maxPageStep: Integer + // The max number of page sizes to be displayed, 7 by default. + maxPageStep: 7, + // position: string + // The position of the pagination bar - "top"|"bottom", "bottom" by default. + position: 'bottom', + + init: function(){ + var g = this.grid; + g.usingPagination = true; + this._initOptions(); + this._currentPage = this.defaultPage; + this._currentPageSize = this.grid.rowsPerPage = this.defaultPageSize; + // wrap store layer + this._store = g.store; + this.forcePageStoreLayer = new _ForcedPageStoreLayer(this); + layers.wrap(g, "_storeLayerFetch", this.forcePageStoreLayer); + // create pagination bar + this._paginator = this.option.position != "top" ? + new _Paginator(lang.mixin(this.option, {position: "bottom", plugin: this})) : + new _Paginator(lang.mixin(this.option, {position: "top", plugin: this})); + this._regApis(); + }, + destroy: function(){ + this.inherited(arguments); + this._paginator.destroy(); + var g = this.grid; + g.unwrap(this.forcePageStoreLayer.name()); + g.scrollToRow = this._gridOriginalfuncs[0]; + g._onNew = this._gridOriginalfuncs[1]; + g.removeSelectedRows = this._gridOriginalfuncs[2]; + this._paginator = null; + this._nls = null; + }, + currentPage: function(page){ + // summary: + // Shift to the given page, return current page number. If there + // is no valid page was passed in, just return current page num. + // page: Integer + // The page to go to, starting at 1. + // return: + // Current page number + if(page <= this.getTotalPageNum() && page > 0 && this._currentPage !== page){ + this._currentPage = page; + this.grid._refresh(true); + this.grid.resize(); + } + return this._currentPage; + }, + nextPage: function(){ + // summary: + // Go to the next page. + this.currentPage(this._currentPage + 1); + }, + prevPage: function(){ + // summary: + // Go to the previous page. + this.currentPage(this._currentPage - 1); + }, + firstPage: function(){ + // summary: + // Go to the first page + this.currentPage(1); + }, + lastPage: function(){ + // summary: + // Go to the last page + this.currentPage(this.getTotalPageNum()); + }, + currentPageSize: function(size){ + // summary: + // Change the size of current page or return the current page size. + // size: Integer || null + // An integer identifying the number of rows per page. If the size + // is an Infinity, all rows will be displayed; if an invalid value pssed + // in, the current page size will be returned. + // return + // Current size of items per page. + if(!isNaN(size)){ + var g = this.grid, + startIndex = this._currentPageSize * (this._currentPage - 1), endIndex; + this._showAll = !isFinite(size); + this.grid.usingPagination = !this._showAll; + this._currentPageSize = this._showAll ? this._maxSize : size; + g.rowsPerPage = this._showAll ? this._defaultRowsPerPage : size; + endIndex = startIndex + Math.min(this._currentPageSize, this._maxSize); + if(endIndex > this._maxSize){ + this.lastPage(); + }else{ + var cp = Math.ceil(startIndex / this._currentPageSize) + 1; + if(cp !== this._currentPage){ + this.currentPage(cp); + }else{ + this.grid._refresh(true); + } + } + this.grid.resize(); + } + return this._currentPageSize; + }, + getTotalPageNum: function(){ + // summary: + // Get total page number + return Math.ceil(this._maxSize / this._currentPageSize); + }, + getTotalRowCount: function(){ + // summary: + // Function for get total row count + return this._maxSize; + }, + scrollToRow: function(inRowIndex){ + // summary: + // Override the grid.scrollToRow(), could jump to the right page + // and scroll to the specific row + // inRowIndex: integer + // The row index + var page = parseInt(inRowIndex / this._currentPageSize, 10) + 1; + if(page > this.getTotalPageNum()){ + return; + } + this.currentPage(page); + var rowIdx = inRowIndex % this._currentPageSize; + return this._gridOriginalfuncs[0](rowIdx); + }, + removeSelectedRows: function(){ + this._multiRemoving = true; + this._gridOriginalfuncs[2].apply(); + this._multiRemoving = false; + this.grid.resize(); + this.grid._refresh(); + }, + showGotoPageButton: function(flag){ + // summary: + // For show/hide the go to page button dynamically + // flag: boolean + // Show the go to page button when flag is true, otherwise hide it + this._paginator.gotoButton = flag; + this._paginator._updateGotoButton(); + }, + // [DEPRECATED] ============ + gotoPage: function(page){ + kernel.deprecated("dojox.grid.enhanced.EnhancedGrid.gotoPage(page)", "use dojox.grid.enhanced.EnhancedGrid.currentPage(page) instead", "1.8"); + this.currentPage(page); + }, + gotoFirstPage: function(){ + kernel.deprecated("dojox.grid.enhanced.EnhancedGrid.gotoFirstPage()", "use dojox.grid.enhanced.EnhancedGrid.firstPage() instead", "1.8"); + this.firstPage(); + }, + gotoLastPage: function(){ + kernel.deprecated("dojox.grid.enhanced.EnhancedGrid.gotoLastPage()", "use dojox.grid.enhanced.EnhancedGrid.lastPage() instead", "1.8"); + this.lastPage(); + }, + changePageSize: function(size){ + kernel.deprecated("dojox.grid.enhanced.EnhancedGrid.changePageSize(size)", "use dojox.grid.enhanced.EnhancedGrid.currentPageSize(size) instead", "1.8"); + this.currentPageSize(size); + }, + // =============== Protected ================ + _nls: null, + _showAll: false, + _maxSize: 0, + // =============== Private =============== + _defaultRowsPerPage: 25, + _currentPage: 1, + _currentPageSize: 25, + + _initOptions: function(){ + this._defaultRowsPerPage = this.grid.rowsPerPage || 25; + this.defaultPage = this.option.defaultPage >= 1 ? parseInt(this.option.defaultPage, 10) : 1; + this.option.description = this.option.description !== undefined ? !!this.option.description : this.description; + this.option.sizeSwitch = this.option.sizeSwitch !== undefined ? !!this.option.sizeSwitch : this.sizeSwitch; + this.option.pageStepper = this.option.pageStepper !== undefined ? !!this.option.pageStepper : this.pageStepper; + this.option.gotoButton = this.option.gotoButton !== undefined ? !!this.option.gotoButton : this.gotoButton; + if(lang.isArray(this.option.pageSizes)){ + var pageSizes = []; + array.forEach(this.option.pageSizes, function(size){ + size = typeof size == 'number' ? size : parseInt(size, 10); + if(!isNaN(size) && size > 0){ + pageSizes.push(size); + }else if(array.indexOf(pageSizes, Infinity) < 0){ + pageSizes.push(Infinity); + } + }, this); + this.option.pageSizes = pageSizes.sort(function(a, b){return a - b;}); + }else{ + this.option.pageSizes = this.pageSizes; + } + this.defaultPageSize = this.option.defaultPageSize >= 1 ? parseInt(this.option.defaultPageSize, 10) : this.pageSizes[0]; + this.option.maxPageStep = this.option.maxPageStep > 0 ? this.option.maxPageStep : this.maxPageStep; + this.option.position = lang.isString(this.option.position) ? this.option.position.toLowerCase() : this.position; + var nls = i18n.getLocalization("dojox.grid.enhanced", "Pagination"); + this._nls = [ + nls.descTemplate, + nls.allItemsLabelTemplate, + nls.pageSizeLabelTemplate, + nls.pageStepLabelTemplate, + nls.itemTitle, + nls.singularItemTitle, + nls.prevTip, + nls.firstTip, + nls.nextTip, + nls.lastTip, + nls.gotoButtonTitle, + nls.dialogTitle, + nls.dialogIndication, + nls.pageCountIndication, + nls.dialogConfirm, + nls.dialogCancel, + nls.all + ]; + }, + _regApis: function(){ + var g = this.grid; + // New added APIs + g.currentPage = lang.hitch(this, this.currentPage); + g.nextPage = lang.hitch(this, this.nextPage); + g.prevPage = lang.hitch(this, this.prevPage); + g.firstPage = lang.hitch(this, this.firstPage); + g.lastPage = lang.hitch(this, this.lastPage); + g.currentPageSize = lang.hitch(this, this.currentPageSize); + g.showGotoPageButton = lang.hitch(this, this.showGotoPageButton); + g.getTotalRowCount = lang.hitch(this, this.getTotalRowCount); + g.getTotalPageNum = lang.hitch(this, this.getTotalPageNum); + + g.gotoPage = lang.hitch(this, this.gotoPage); + g.gotoFirstPage = lang.hitch(this, this.gotoFirstPage); + g.gotoLastPage = lang.hitch(this, this.gotoLastPage); + g.changePageSize = lang.hitch(this, this.changePageSize); + // Changed APIs + this._gridOriginalfuncs = [ + lang.hitch(g, g.scrollToRow), + lang.hitch(g, g._onNew), + lang.hitch(g, g.removeSelectedRows) + ]; + g.scrollToRow = lang.hitch(this, this.scrollToRow); + g.removeSelectedRows = lang.hitch(this, this.removeSelectedRows); + g._onNew = lang.hitch(this, this._onNew); + this.connect(g, "_onDelete", lang.hitch(this, this._onDelete)); + }, + _onNew: function(item, parentInfo){ + var totalPages = this.getTotalPageNum(); + if(((this._currentPage === totalPages || totalPages === 0) && this.grid.get('rowCount') < this._currentPageSize) || this._showAll){ + lang.hitch(this.grid, this._gridOriginalfuncs[1])(item, parentInfo); + this.forcePageStoreLayer.endIdx++; + } + this._maxSize++; + if(this._showAll){ + this._currentPageSize++; + } + if(this._showAll && this.grid.autoHeight){ + this.grid._refresh(); + }else{ + this._paginator._update(); + } + }, + _onDelete: function(){ + if(!this._multiRemoving){ + this.grid.resize(); + if(this._showAll){ + this.grid._refresh(); + } + } + if(this.grid.get('rowCount') === 0){ + this.prevPage(); + } + } +}); + +EnhancedGrid.registerPlugin(Pagination/*name:'pagination'*/); + +return Pagination; + +});
\ No newline at end of file diff --git a/js/dojo/dojox/grid/enhanced/plugins/Printer.js b/js/dojo/dojox/grid/enhanced/plugins/Printer.js new file mode 100644 index 0000000..09ab390 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Printer.js @@ -0,0 +1,292 @@ +//>>built +define("dojox/grid/enhanced/plugins/Printer", [ + "dojo/_base/declare", + "dojo/_base/html", + "dojo/_base/Deferred", + "dojo/_base/lang", + "dojo/_base/sniff", + "dojo/_base/xhr", + "dojo/_base/array", + "dojo/query", + "dojo/DeferredList", + "../_Plugin", + "../../EnhancedGrid", + "./exporter/TableWriter" +], function(declare, html, Deferred, lang, has, xhr, array, query, DeferredList, _Plugin, EnhancedGrid, TableWriter){ + +var Printer = declare("dojox.grid.enhanced.plugins.Printer", _Plugin, { + // summary: + // Provide printGrid function to the grid. + // example: + // | dojo.require("dojox.grid.enhanced.plugins.Printer"); + // | dijit.byId("grid1").printGrid("my grid", //A title for the grid,optional + // | ["cssfile1.css","cssfile2.css"],//An array of css files to decorate the printed gird,optional + // | {table:"border='border'"} //tagName:"attrbuteList" pairs, optional, + // | //control the html tags in the generated html + // | ); + + // __printArgs: { + // title: String + // A title of the printed page can be specified. Optional. + // If given, it's shown in an <h1> tag at the top of the page. + // cssFiles: Array | String + // CSS file paths. Optional. + // Every row and column is given CSS classes, including: + // grid_row_{row-number}, grid_odd_row, grid_even_row, grid_header, + // grid_col_{col-number}, grid_odd_col, grid_even_col + // {row_number} and {col-number} are both integers starting from 1. + // Row classes are for <thead> and <tbody> tags. + // Column classes are for <th> and <td> tags. + // Users can use these classes in the CSS files, but cannot define their own. + // writerArgs: Object (Association Array) + // Arguments for TableWriter. + // fetchArgs: object? + // Any arguments for store.fetch + // } + + // name: String + // Plugin name + name: "printer", + + constructor: function(grid){ + // summary: + // only newed by _Plugin + // inGrid: EnhancedGrid + // The grid to plug in to. + this.grid = grid; + this._mixinGrid(grid); + + //For print, we usually need the HTML instead of raw data. + grid.setExportFormatter(function(data, cell, rowIndex, rowItem){ + return cell.format(rowIndex, rowItem); + }); + }, + _mixinGrid: function(){ + var g = this.grid; + g.printGrid = lang.hitch(this, this.printGrid); + g.printSelected = lang.hitch(this, this.printSelected); + g.exportToHTML = lang.hitch(this, this.exportToHTML); + g.exportSelectedToHTML = lang.hitch(this, this.exportSelectedToHTML); + g.normalizePrintedGrid = lang.hitch(this, this.normalizeRowHeight); + }, + printGrid: function(args){ + // summary: + // Print all the data in the grid, using title as a title, + // decorating generated html by cssFiles, + // using tagName:"attrbuteList" pairs(writerArgs) to control html tags + // in the generated html string. + // tags: + // public + // args: __printArgs? + // Arguments for print. + this.exportToHTML(args, lang.hitch(this, this._print)); + }, + printSelected: function(args){ + // summary: + // Print selected data. All other features are the same as printGrid. + // For meaning of arguments see function *printGrid* + // tags: + // public + // args: __printArgs? + // Arguments for print. + this.exportSelectedToHTML(args, lang.hitch(this, this._print)); + }, + exportToHTML: function(args, onExported){ + // summary: + // Export to HTML string, but do NOT print. + // Users can use this to implement print preview. + // For meaning of the 1st-3rd arguments see function *printGrid*. + // tags: + // public + // args: __printArgs? + // Arguments for print. + // onExported: function(string) + // call back function + args = this._formalizeArgs(args); + var _this = this; + this.grid.exportGrid("table", args, function(str){ + _this._wrapHTML(args.title, args.cssFiles, args.titleInBody + str).then(onExported); + }); + }, + exportSelectedToHTML: function(args, onExported){ + // summary: + // Export selected rows to HTML string, but do NOT print. + // Users can use this to implement print preview. + // For meaning of arguments see function *printGrid* + // tags: + // public + // args: __printArgs? + // Arguments for print. + args = this._formalizeArgs(args); + var _this = this; + this.grid.exportSelected("table", args.writerArgs, function(str){ + _this._wrapHTML(args.title, args.cssFiles, args.titleInBody + str).then(onExported); + }); + }, + + _loadCSSFiles: function(cssFiles){ + var dl = array.map(cssFiles, function(cssFile){ + cssFile = lang.trim(cssFile); + if(cssFile.substring(cssFile.length - 4).toLowerCase() === '.css'){ + return xhr.get({ + url: cssFile + }); + }else{ + var d = new Deferred(); + d.callback(cssFile); + return d; + } + }); + return DeferredList.prototype.gatherResults(dl); + }, + _print: function(/* string */htmlStr){ + // summary: + // Do the print job. + // tags: + // private + // htmlStr: String + // The html content string to be printed. + // returns: + // undefined + var win, _this = this, + fillDoc = function(w){ + var doc = w.document; + doc.open(); + doc.write(htmlStr); + doc.close(); + _this.normalizeRowHeight(doc); + }; + if(!window.print){ + //We don't have a print facility. + return; + }else if(has("chrome") || has("opera")){ + //referred from dijit._editor.plugins.Print._print() + //In opera and chrome the iframe.contentWindow.print + //will also print the outside window. So we must create a + //stand-alone new window. + win = window.open("javascript: ''", "", + "status=0,menubar=0,location=0,toolbar=0,width=1,height=1,resizable=0,scrollbars=0"); + fillDoc(win); + win.print(); + //Opera will stop at this point, showing the popping-out window. + //If the user closes the window, the following codes will not execute. + //If the user returns focus to the main window, the print function + // is executed, but still a no-op. + win.close(); + }else{ + //Put private things in deeper namespace to avoid poluting grid namespace. + var fn = this._printFrame, + dn = this.grid.domNode; + if(!fn){ + var frameId = dn.id + "_print_frame"; + if(!(fn = html.byId(frameId))){ + //create an iframe to store the grid data. + fn = html.create("iframe"); + fn.id = frameId; + fn.frameBorder = 0; + html.style(fn, { + width: "1px", + height: "1px", + position: "absolute", + right: 0, + bottom: 0, + border: "none", + overflow: "hidden" + }); + if(!has("ie")){ + html.style(fn, "visibility", "hidden"); + } + dn.appendChild(fn); + } + //Reuse this iframe + this._printFrame = fn; + } + win = fn.contentWindow; + fillDoc(win); + //IE requires the frame to be focused for print to work, and it's harmless for FF. + win.focus(); + win.print(); + } + }, + _wrapHTML: function(/* string */title, /* Array */cssFiles, /* string */body_content){ + // summary: + // Put title, cssFiles, and body_content together into an HTML string. + // tags: + // private + // title: String + // A title for the html page. + // cssFiles: Array + // css file pathes. + // body_content: String + // Content to print, not including <head></head> part and <html> tags + // returns: + // the wrapped HTML string ready for print + return this._loadCSSFiles(cssFiles).then(function(cssStrs){ + var i, sb = ['<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">', + '<html ', html._isBodyLtr() ? '' : 'dir="rtl"', '><head><title>', title, + '</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>']; + for(i = 0; i < cssStrs.length; ++i){ + sb.push('<style type="text/css">', cssStrs[i], '</style>'); + } + sb.push('</head>'); + if(body_content.search(/^\s*<body/i) < 0){ + body_content = '<body>' + body_content + '</body>'; + } + sb.push(body_content, '</html>'); + return sb.join(''); + }); + }, + normalizeRowHeight: function(doc){ + var views = query(".grid_view", doc.body); + var headPerView = array.map(views, function(view){ + return query(".grid_header", view)[0]; + }); + var rowsPerView = array.map(views, function(view){ + return query(".grid_row", view); + }); + var rowCount = rowsPerView[0].length; + var i, v, h, maxHeight = 0; + for(v = views.length - 1; v >= 0; --v){ + h = html.contentBox(headPerView[v]).h; + if(h > maxHeight){ + maxHeight = h; + } + } + for(v = views.length - 1; v >= 0; --v){ + html.style(headPerView[v], "height", maxHeight + "px"); + } + for(i = 0; i < rowCount; ++i){ + maxHeight = 0; + for(v = views.length - 1; v >= 0; --v){ + h = html.contentBox(rowsPerView[v][i]).h; + if(h > maxHeight){ + maxHeight = h; + } + } + for(v = views.length - 1; v >= 0; --v){ + html.style(rowsPerView[v][i], "height", maxHeight + "px"); + } + } + var left = 0, ltr = html._isBodyLtr(); + for(v = 0; v < views.length; ++v){ + html.style(views[v], ltr ? "left" : "right", left + "px"); + left += html.marginBox(views[v]).w; + } + }, + _formalizeArgs: function(args){ + args = (args && lang.isObject(args)) ? args : {}; + args.title = String(args.title) || ""; + if(!lang.isArray(args.cssFiles)){ + args.cssFiles = [args.cssFiles]; + } + args.titleInBody = args.title ? ['<h1>', args.title, '</h1>'].join('') : ''; + return args; //Object + } +}); + +EnhancedGrid.registerPlugin(Printer/*name:'printer'*/, { + "dependency": ["exporter"] +}); + +return Printer; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/Rearrange.js b/js/dojo/dojox/grid/enhanced/plugins/Rearrange.js new file mode 100644 index 0000000..1c3bac3 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Rearrange.js @@ -0,0 +1,506 @@ +//>>built +define("dojox/grid/enhanced/plugins/Rearrange", [ + "dojo/_base/kernel", + "dojo/_base/lang", + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/connect", + "../../EnhancedGrid", + "../_Plugin", + "./_RowMapLayer" +], function(dojo, lang, declare, array, connect, EnhancedGrid, _Plugin, _RowMapLayer){ + +var Rearrange = declare("dojox.grid.enhanced.plugins.Rearrange", _Plugin, { + // summary: + // Provides a set of method to re-arrange the structure of grid. + + // name: String + // plugin name + name: "rearrange", + + constructor: function(grid, args){ + this.grid = grid; + this.setArgs(args); + var rowMapLayer = new _RowMapLayer(grid); + dojox.grid.enhanced.plugins.wrap(grid, "_storeLayerFetch", rowMapLayer); + }, + setArgs: function(args){ + this.args = lang.mixin(this.args || {}, args || {}); + this.args.setIdentifierForNewItem = this.args.setIdentifierForNewItem || function(v){return v;}; + }, + destroy: function(){ + this.inherited(arguments); + this.grid.unwrap("rowmap"); + }, + onSetStore: function(store){ + this.grid.layer("rowmap").clearMapping(); + }, + _hasIdentity: function(points){ + var g = this.grid, s = g.store, cells = g.layout.cells; + if(s.getFeatures()["dojo.data.api.Identity"]){ + if(array.some(points, function(point){ + return s.getIdentityAttributes(g._by_idx[point.r].item) == cells[point.c].field; + })){ + return true; + } + } + return false; + }, + moveColumns: function(colsToMove, targetPos){ + // summary: + // Move a set of columns to a given position. + // tag: + // public + // colsToMove: Integer[] + // Array of column indexes. + // targetPos: Integer + // The target position + var g = this.grid, + layout = g.layout, + cells = layout.cells, + colIndex, i, delta = 0, + before = true, tmp = {}, mapping = {}; + colsToMove.sort(function(a, b){ + return a - b; + }); + for(i = 0; i < colsToMove.length; ++i){ + tmp[colsToMove[i]] = i; + if(colsToMove[i] < targetPos){ + ++delta; + } + } + var leftCount = 0, rightCount = 0; + var maxCol = Math.max(colsToMove[colsToMove.length - 1], targetPos); + if(maxCol == cells.length){ + --maxCol; + } + var minCol = Math.min(colsToMove[0], targetPos); + for(i = minCol; i <= maxCol; ++i){ + var j = tmp[i]; + if(j >= 0){ + mapping[i] = targetPos - delta + j; + }else if(i < targetPos){ + mapping[i] = minCol + leftCount; + ++leftCount; + }else if(i >= targetPos){ + mapping[i] = targetPos + colsToMove.length - delta + rightCount; + ++rightCount; + } + } + //console.log("mapping:", mapping, ", colsToMove:", colsToMove,", target:", targetPos); + delta = 0; + if(targetPos == cells.length){ + --targetPos; + before = false; + } + g._notRefreshSelection = true; + for(i = 0; i < colsToMove.length; ++i){ + colIndex = colsToMove[i]; + if(colIndex < targetPos){ + colIndex -= delta; + } + ++delta; + if(colIndex != targetPos){ + layout.moveColumn(cells[colIndex].view.idx, cells[targetPos].view.idx, colIndex, targetPos, before); + cells = layout.cells; + } + if(targetPos <= colIndex){ + ++targetPos; + } + } + delete g._notRefreshSelection; + connect.publish("dojox/grid/rearrange/move/" + g.id, ["col", mapping, colsToMove]); + }, + moveRows: function(rowsToMove, targetPos){ + // summary: + // Move a set of rows to a given position + // tag: + // public + // rowsToMove: Integer[] + // Array of row indexes. + // targetPos: Integer + // The target position + var g = this.grid, + mapping = {}, + preRowsToMove = [], + postRowsToMove = [], + len = rowsToMove.length, + i, r, k, arr, rowMap, lastPos; + + for(i = 0; i < len; ++i){ + r = rowsToMove[i]; + if(r >= targetPos){ + break; + } + preRowsToMove.push(r); + } + postRowsToMove = rowsToMove.slice(i); + + arr = preRowsToMove; + len = arr.length; + if(len){ + rowMap = {}; + array.forEach(arr, function(r){ + rowMap[r] = true; + }); + mapping[arr[0]] = targetPos - len; + for(k = 0, i = arr[k] + 1, lastPos = i - 1; i < targetPos; ++i){ + if(!rowMap[i]){ + mapping[i] = lastPos; + ++lastPos; + }else{ + ++k; + mapping[i] = targetPos - len + k; + } + } + } + arr = postRowsToMove; + len = arr.length; + if(len){ + rowMap = {}; + array.forEach(arr, function(r){ + rowMap[r] = true; + }); + mapping[arr[len - 1]] = targetPos + len - 1; + for(k = len - 1, i = arr[k] - 1, lastPos = i + 1; i >= targetPos; --i){ + if(!rowMap[i]){ + mapping[i] = lastPos; + --lastPos; + }else{ + --k; + mapping[i] = targetPos + k; + } + } + } + var tmpMapping = lang.clone(mapping); + g.layer("rowmap").setMapping(mapping); + g.forEachLayer(function(layer){ + if(layer.name() != "rowmap"){ + layer.invalidate(); + return true; + }else{ + return false; + } + }, false); + g.selection.selected = []; + g._noInternalMapping = true; + g._refresh(); + setTimeout(function(){ + connect.publish("dojox/grid/rearrange/move/" + g.id, ["row", tmpMapping, rowsToMove]); + g._noInternalMapping = false; + }, 0); + }, + moveCells: function(cellsToMove, target){ + var g = this.grid, + s = g.store; + if(s.getFeatures()["dojo.data.api.Write"]){ + if(cellsToMove.min.row == target.min.row && cellsToMove.min.col == target.min.col){ + //Same position, no need to move + return; + } + var cells = g.layout.cells, + cnt = cellsToMove.max.row - cellsToMove.min.row + 1, + r, c, tr, tc, + sources = [], targets = []; + for(r = cellsToMove.min.row, tr = target.min.row; r <= cellsToMove.max.row; ++r, ++tr){ + for(c = cellsToMove.min.col, tc = target.min.col; c <= cellsToMove.max.col; ++c, ++tc){ + while(cells[c] && cells[c].hidden){ + ++c; + } + while(cells[tc] && cells[tc].hidden){ + ++tc; + } + sources.push({ + "r": r, + "c": c + }); + targets.push({ + "r": tr, + "c": tc, + "v": cells[c].get(r, g._by_idx[r].item) + }); + } + } + if(this._hasIdentity(sources.concat(targets))){ + console.warn("Can not write to identity!"); + return; + } + array.forEach(sources, function(point){ + s.setValue(g._by_idx[point.r].item, cells[point.c].field, ""); + }); + array.forEach(targets, function(point){ + s.setValue(g._by_idx[point.r].item, cells[point.c].field, point.v); + }); + s.save({ + onComplete: function(){ + connect.publish("dojox/grid/rearrange/move/" + g.id, ["cell", { + "from": cellsToMove, + "to": target + }]); + } + }); + } + }, + copyCells: function(cellsToMove, target){ + var g = this.grid, + s = g.store; + if(s.getFeatures()["dojo.data.api.Write"]){ + if(cellsToMove.min.row == target.min.row && cellsToMove.min.col == target.min.col){ + return; + } + var cells = g.layout.cells, + cnt = cellsToMove.max.row - cellsToMove.min.row + 1, + r, c, tr, tc, + targets = []; + for(r = cellsToMove.min.row, tr = target.min.row; r <= cellsToMove.max.row; ++r, ++tr){ + for(c = cellsToMove.min.col, tc = target.min.col; c <= cellsToMove.max.col; ++c, ++tc){ + while(cells[c] && cells[c].hidden){ + ++c; + } + while(cells[tc] && cells[tc].hidden){ + ++tc; + } + targets.push({ + "r": tr, + "c": tc, + "v": cells[c].get(r, g._by_idx[r].item) + }); + } + } + if(this._hasIdentity(targets)){ + console.warn("Can not write to identity!"); + return; + } + array.forEach(targets, function(point){ + s.setValue(g._by_idx[point.r].item, cells[point.c].field, point.v); + }); + s.save({ + onComplete: function(){ + setTimeout(function(){ + connect.publish("dojox/grid/rearrange/copy/" + g.id, ["cell", { + "from": cellsToMove, + "to": target + }]); + }, 0); + } + }); + } + }, + changeCells: function(sourceGrid, cellsToMove, target){ + var g = this.grid, + s = g.store; + if(s.getFeatures()["dojo.data.api.Write"]){ + var srcg = sourceGrid, + cells = g.layout.cells, + srccells = srcg.layout.cells, + cnt = cellsToMove.max.row - cellsToMove.min.row + 1, + r, c, tr, tc, targets = []; + for(r = cellsToMove.min.row, tr = target.min.row; r <= cellsToMove.max.row; ++r, ++tr){ + for(c = cellsToMove.min.col, tc = target.min.col; c <= cellsToMove.max.col; ++c, ++tc){ + while(srccells[c] && srccells[c].hidden){ + ++c; + } + while(cells[tc] && cells[tc].hidden){ + ++tc; + } + targets.push({ + "r": tr, + "c": tc, + "v": srccells[c].get(r, srcg._by_idx[r].item) + }); + } + } + if(this._hasIdentity(targets)){ + console.warn("Can not write to identity!"); + return; + } + array.forEach(targets, function(point){ + s.setValue(g._by_idx[point.r].item, cells[point.c].field, point.v); + }); + s.save({ + onComplete: function(){ + connect.publish("dojox/grid/rearrange/change/" + g.id, ["cell", target]); + } + }); + } + }, + clearCells: function(cellsToClear){ + var g = this.grid, + s = g.store; + if(s.getFeatures()["dojo.data.api.Write"]){ + var cells = g.layout.cells, + cnt = cellsToClear.max.row - cellsToClear.min.row + 1, + r, c, targets = []; + for(r = cellsToClear.min.row; r <= cellsToClear.max.row; ++r){ + for(c = cellsToClear.min.col; c <= cellsToClear.max.col; ++c){ + while(cells[c] && cells[c].hidden){ + ++c; + } + targets.push({ + "r": r, + "c": c + }); + } + } + if(this._hasIdentity(targets)){ + console.warn("Can not write to identity!"); + return; + } + array.forEach(targets, function(point){ + s.setValue(g._by_idx[point.r].item, cells[point.c].field, ""); + }); + s.save({ + onComplete: function(){ + connect.publish("dojox/grid/rearrange/change/" + g.id, ["cell", cellsToClear]); + } + }); + } + }, + insertRows: function(sourceGrid, rowsToMove, targetPos){ + try{ + var g = this.grid, + s = g.store, + rowCnt = g.rowCount, + mapping = {}, + obj = {idx: 0}, + newRows = [], i, + emptyTarget = targetPos < 0; + _this = this; + var len = rowsToMove.length; + if(emptyTarget){ + targetPos = 0; + }else{ + for(i = targetPos; i < g.rowCount; ++i){ + mapping[i] = i + len; + } + } + if(s.getFeatures()['dojo.data.api.Write']){ + if(sourceGrid){ + var srcg = sourceGrid, + srcs = srcg.store, + thisItem, attrs; + if(!emptyTarget){ + for(i = 0; !thisItem; ++i){ + thisItem = g._by_idx[i]; + } + attrs = s.getAttributes(thisItem.item); + }else{ + //If the target grid is empty, there is no way to retrieve attributes. + //So try to get attrs from grid.layout.cells[], but this might not be right + //since some fields may be missed(e.g ID fields), please use "setIdentifierForNewItem()" + //to add those missed fields + attrs = array.map(g.layout.cells, function(cell){ + return cell.field; + }); + } + var rowsToFetch = []; + array.forEach(rowsToMove, function(rowIndex, i){ + var item = {}; + var srcItem = srcg._by_idx[rowIndex]; + if(srcItem){ + array.forEach(attrs, function(attr){ + item[attr] = srcs.getValue(srcItem.item, attr); + }); + item = _this.args.setIdentifierForNewItem(item, s, rowCnt + obj.idx) || item; + try{ + s.newItem(item); + newRows.push(targetPos + i); + mapping[rowCnt + obj.idx] = targetPos + i; + ++obj.idx; + }catch(e){ + console.log("insertRows newItem:",e,item); + } + }else{ + rowsToFetch.push(rowIndex); + } + }); + }else if(rowsToMove.length && lang.isObject(rowsToMove[0])){ + array.forEach(rowsToMove, function(rowData, i){ + var item = _this.args.setIdentifierForNewItem(rowData, s, rowCnt + obj.idx) || rowData; + try{ + s.newItem(item); + newRows.push(targetPos + i); + mapping[rowCnt + obj.idx] = targetPos + i; + ++obj.idx; + }catch(e){ + console.log("insertRows newItem:",e,item); + } + }); + }else{ + return; + } + g.layer("rowmap").setMapping(mapping); + s.save({ + onComplete: function(){ + g._refresh(); + setTimeout(function(){ + connect.publish("dojox/grid/rearrange/insert/" + g.id, ["row", newRows]); + }, 0); + } + }); + } + }catch(e){ + console.log("insertRows:",e); + } + }, + removeRows: function(rowsToRemove){ + var g = this.grid; + var s = g.store; + try{ + array.forEach(array.map(rowsToRemove, function(rowIndex){ + return g._by_idx[rowIndex]; + }), function(row){ + if(row){ + s.deleteItem(row.item); + } + }); + s.save({ + onComplete: function(){ + connect.publish("dojox/grid/rearrange/remove/" + g.id, ["row", rowsToRemove]); + } + }); + }catch(e){ + console.log("removeRows:",e); + } + }, + _getPageInfo: function(){ + // summary: + // Find pages that contain visible rows + // return: Object + // {topPage: xx, bottomPage: xx, invalidPages: [xx,xx,...]} + var scroller = this.grid.scroller, + topPage = scroller.page, + bottomPage = scroller.page, + firstVisibleRow = scroller.firstVisibleRow, + lastVisibleRow = scroller.lastVisibleRow, + rowsPerPage = scroller.rowsPerPage, + renderedPages = scroller.pageNodes[0], + topRow, bottomRow, matched, + invalidPages = []; + + array.forEach(renderedPages, function(page, pageIndex){ + if(!page){ return; } + matched = false; + topRow = pageIndex * rowsPerPage; + bottomRow = (pageIndex + 1) * rowsPerPage - 1; + if(firstVisibleRow >= topRow && firstVisibleRow <= bottomRow){ + topPage = pageIndex; + matched = true; + } + if(lastVisibleRow >= topRow && lastVisibleRow <= bottomRow){ + bottomPage = pageIndex; + matched = true; + } + if(!matched && (topRow > lastVisibleRow || bottomRow < firstVisibleRow)){ + invalidPages.push(pageIndex); + } + }); + return {topPage: topPage, bottomPage: bottomPage, invalidPages: invalidPages}; + } +}); + +EnhancedGrid.registerPlugin(Rearrange/*name:'rearrange'*/); + +return Rearrange; + +});
\ No newline at end of file diff --git a/js/dojo/dojox/grid/enhanced/plugins/Search.js b/js/dojo/dojox/grid/enhanced/plugins/Search.js new file mode 100644 index 0000000..2c0284c --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Search.js @@ -0,0 +1,123 @@ +//>>built +define("dojox/grid/enhanced/plugins/Search", [ + "dojo/_base/kernel", + "dojo/_base/lang", + "dojo/_base/declare", + "dojo/_base/array", + "dojo/data/util/filter", + "../../EnhancedGrid", + "../_Plugin" +], function(dojo, lang, declare, array, dFilter, EnhancedGrid, _Plugin){ + +var Search = declare("dojox.grid.enhanced.plugins.Search", _Plugin, { + // summary: + // Search the grid using wildcard string or Regular Expression. + + // name: String + // plugin name + name: "search", + + constructor: function(grid, args){ + this.grid = grid; + args = (args && lang.isObject(args)) ? args : {}; + this._cacheSize = args.cacheSize || -1; + grid.searchRow = lang.hitch(this, "searchRow"); + }, + searchRow: function(/* Object|RegExp|String */searchArgs, /* function(Integer, item) */onSearched){ + if(!lang.isFunction(onSearched)){ return; } + if(lang.isString(searchArgs)){ + searchArgs = dFilter.patternToRegExp(searchArgs); + } + var isGlobal = false; + if(searchArgs instanceof RegExp){ + isGlobal = true; + }else if(lang.isObject(searchArgs)){ + var isEmpty = true; + for(var field in searchArgs){ + if(lang.isString(searchArgs[field])){ + searchArgs[field] = dFilter.patternToRegExp(searchArgs[field]); + } + isEmpty = false; + } + if(isEmpty){ return; } + }else{ + return; + } + this._search(searchArgs, 0, onSearched, isGlobal); + }, + _search: function(/* Object|RegExp */searchArgs, /* Integer */start, /* function(Integer, item) */onSearched, /* Boolean */isGlobal){ + var _this = this, + cnt = this._cacheSize, + args = { + start: start, + query: this.grid.query, + sort: this.grid.getSortProps(), + queryOptions: this.grid.queryOptions, + onBegin: function(size){ + _this._storeSize = size; + }, + onComplete: function(items){ + if(!array.some(items, function(item, i){ + if(_this._checkRow(item, searchArgs, isGlobal)){ + onSearched(start + i, item); + return true; + } + return false; + })){ + if(cnt > 0 && start + cnt < _this._storeSize){ + _this._search(searchArgs, start + cnt, onSearched, isGlobal); + }else{ + onSearched(-1, null); + } + } + } + }; + if(cnt > 0){ + args.count = cnt; + } + this.grid._storeLayerFetch(args); + }, + _checkRow: function(/* store item */item, /* Object|RegExp */searchArgs, /* Boolean */isGlobal){ + var g = this.grid, s = g.store, i, field, + cells = array.filter(g.layout.cells, function(cell){ + return !cell.hidden; + }); + if(isGlobal){ + return array.some(cells, function(cell){ + try{ + if(cell.field){ + return String(s.getValue(item, cell.field)).search(searchArgs) >= 0; + } + }catch(e){ + console.log("Search._checkRow() error: ", e); + } + return false; + }); + }else{ + for(field in searchArgs){ + if(searchArgs[field] instanceof RegExp){ + for(i = cells.length - 1; i >= 0; --i){ + if(cells[i].field == field){ + try{ + if(String(s.getValue(item, field)).search(searchArgs[field]) < 0){ + return false; + } + break; + }catch(e){ + return false; + } + } + } + if(i < 0){ return false; } + } + } + return true; + } + } +}); + +EnhancedGrid.registerPlugin(Search/*name:'search'*/); + +return Search; + +});
\ No newline at end of file diff --git a/js/dojo/dojox/grid/enhanced/plugins/Selector.js b/js/dojo/dojox/grid/enhanced/plugins/Selector.js new file mode 100644 index 0000000..c9e2574 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/Selector.js @@ -0,0 +1,1477 @@ +//>>built +define("dojox/grid/enhanced/plugins/Selector", [ + "dojo/_base/kernel", + "dojo/_base/lang", + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/event", + "dojo/keys", + "dojo/query", + "dojo/_base/html", + "dojo/_base/window", + "dijit/focus", + "../../_RowSelector", + "../_Plugin", + "../../EnhancedGrid", + "../../cells/_base", + "./AutoScroll" +], function(dojo, lang, declare, array, event, keys, query, html, win, dijitFocus, _RowSelector, _Plugin, EnhancedGrid){ + +/*===== +dojo.declare("__SelectItem", null,{ + // summary: + // An abstract representation of an item. +}); +dojo.declare("__SelectCellItem", __SelectItem,{ + // summary: + // An abstract representation of a cell. + + // row: Integer + // Row index of this cell + row: 0, + + // col: Integer + // Column index of this cell + col: 0 +}); +dojo.declare("__SelectRowItem", __SelectItem,{ + // summary: + // An abstract representation of a row. + + // row: Integer + // Row index of this row + row: 0, + + // except: Integer[] + // An array of column indexes of all the unselected cells in this row. + except: [] +}); +dojo.declare("__SelectColItem", __SelectItem,{ + // summary: + // An abstract representation of a column. + + // col: Integer + // Column index of this column + col: 0, + + // except: Integer[] + // An array of row indexes of all the unselected cells in this column. + except: [] +}); +=====*/ + +var DISABLED = 0, SINGLE = 1, MULTI = 2, + _theOther = { col: "row", row: "col" }, + _inRange = function(type, value, start, end, halfClose){ + if(type !== "cell"){ + value = value[type]; + start = start[type]; + end = end[type]; + if(typeof value !== "number" || typeof start !== "number" || typeof end !== "number"){ + return false; + } + return halfClose ? ((value >= start && value < end) || (value > end && value <= start)) + : ((value >= start && value <= end) || (value >= end && value <= start)); + }else{ + return _inRange("col", value, start, end, halfClose) && _inRange("row", value, start, end, halfClose); + } + }, + _isEqual = function(type, v1, v2){ + try{ + if(v1 && v2){ + switch(type){ + case "col": case "row": + return v1[type] == v2[type] && typeof v1[type] == "number" && + !(_theOther[type] in v1) && !(_theOther[type] in v2); + case "cell": + return v1.col == v2.col && v1.row == v2.row && typeof v1.col == "number" && typeof v1.row == "number"; + } + } + }catch(e){} + return false; + }, + _stopEvent = function(evt){ + try{ + if(evt && evt.preventDefault){ + event.stop(evt); + } + }catch(e){} + }, + _createItem = function(type, rowIndex, colIndex){ + switch(type){ + case "col": + return { + "col": typeof colIndex == "undefined" ? rowIndex : colIndex, + "except": [] + }; + case "row": + return { + "row": rowIndex, + "except": [] + }; + case "cell": + return { + "row": rowIndex, + "col": colIndex + }; + } + return null; + }; +var Selector = declare("dojox.grid.enhanced.plugins.Selector", _Plugin, { + // summary: + // Provides standard extended selection for grid. + // Supports mouse/keyboard selection, multi-selection, and de-selection. + // Acceptable plugin parameters: + // The whole plugin parameter object is a config object passed to the setupConfig function. + // + // Acceptable cell parameters defined in layout: + // 1. notselectable: boolean + // Whether this column is (and all the cells in it are) selectable. + + // name: String + // plugin name + name: "selector", + + // noClear: Boolean + // Not to clear rows selected by IndirectSelection. +/* + // _config: null, + // _enabled: true, + // _selecting: { + // row: false, + // col: false, + // cell: false + // }, + // _selected: { + // row: [], + // col: [], + // cell: [] + // }, + // _startPoint: {}, + // _currentPoint: {}, + // _lastAnchorPoint: {}, + // _lastEndPoint: {}, + // _lastSelectedAnchorPoint: {}, + // _lastSelectedEndPoint: {}, + // _keyboardSelect: { + // row: 0, + // col: 0, + // cell: 0 + // }, + // _curType: null, + // _lastType: null, + // _usingKeyboard: false, + // _toSelect: true, +*/ + + constructor: function(grid, args){ + this.grid = grid; + this._config = { + row: MULTI, + col: MULTI, + cell: MULTI + }; + this.noClear = args && args.noClear; + this.setupConfig(args); + if(grid.selectionMode === "single"){ + this._config.row = SINGLE; + } + this._enabled = true; + this._selecting = {}; + this._selected = { + "col": [], + "row": [], + "cell": [] + }; + this._startPoint = {}; + this._currentPoint = {}; + this._lastAnchorPoint = {}; + this._lastEndPoint = {}; + this._lastSelectedAnchorPoint = {}; + this._lastSelectedEndPoint = {}; + this._keyboardSelect = {}; + this._lastType = null; + this._selectedRowModified = {}; + this._hacks(); + this._initEvents(); + this._initAreas(); + this._mixinGrid(); + }, + destroy: function(){ + this.inherited(arguments); + }, + //------------public-------------------- + setupConfig: function(config){ + // summary: + // Set selection mode for row/col/cell. + // config: Object + // An object with the following structure (all properties are optional): + // { + // //Default is "multi", all other values are same as "multi". + // row: false|"disabled"|"single", + // col: false|"disabled"|"single", + // cell: false|"disabled"|"single" + // } + if(!config || !lang.isObject(config)){ + return; + } + var types = ["row", "col", "cell"]; + for(var type in config){ + if(array.indexOf(types, type) >= 0){ + if(!config[type] || config[type] == "disabled"){ + this._config[type] = DISABLED; + }else if(config[type] == "single"){ + this._config[type] = SINGLE; + }else{ + this._config[type] = MULTI; + } + } + } + + //Have to set mode to default grid selection. + var mode = ["none","single","extended"][this._config.row]; + this.grid.selection.setMode(mode); + }, + isSelected: function(type, rowIndex, colIndex){ + // summary: + // Check whether a location (a cell, a column or a row) is selected. + // tag: + // public + // type: String + // "row" or "col" or "cell" + // rowIndex: Integer + // If type is "row" or "cell", this is the row index. + // If type if "col", this is the column index. + // colIndex: Integer? + // Only valid when type is "cell" + // return: Boolean + // true if selected, false if not. If cell is covered by a selected column, it's selected. + return this._isSelected(type, _createItem(type, rowIndex, colIndex)); + }, + toggleSelect: function(type, rowIndex, colIndex){ + this._startSelect(type, _createItem(type, rowIndex, colIndex), this._config[type] === MULTI, false, false, !this.isSelected(type, rowIndex, colIndex)); + this._endSelect(type); + }, + select: function(type, rowIndex, colIndex){ + // summary: + // Select a location (a cell, a column or a row). + // tag: + // public + // type: String + // "row" or "col" or "cell" + // rowIndex: Integer + // If type is "row" or "cell", this is the row index. + // If type if "col", this is the column index. + // colIndex: Integer? + // Only valid when type is "cell" + if(!this.isSelected(type, rowIndex, colIndex)){ + this.toggleSelect(type, rowIndex, colIndex); + } + }, + deselect: function(type, rowIndex, colIndex){ + if(this.isSelected(type, rowIndex, colIndex)){ + this.toggleSelect(type, rowIndex, colIndex); + } + }, + selectRange: function(type, start, end, toSelect){ + // summary: + // Select a continuous range (a block of cells, a set of continuous columns or rows) + // tag: + // public + // type: String + // "row" or "col" or "cell" + // start: Integer | Object + // If type is "row" or "col", this is the index of the starting row or column. + // If type if "cell", this is the left-top cell of the range. + // end: Integer | Object + // If type is "row" or "col", this is the index of the ending row or column. + // If type if "cell", this is the right-bottom cell of the range. + this.grid._selectingRange = true; + var startPoint = type == "cell" ? _createItem(type, start.row, start.col) : _createItem(type, start), + endPoint = type == "cell" ? _createItem(type, end.row, end.col) : _createItem(type, end); + this._startSelect(type, startPoint, false, false, false, toSelect); + this._highlight(type, endPoint, toSelect === undefined ? true : toSelect); + this._endSelect(type); + this.grid._selectingRange = false; + }, + clear: function(type){ + // summary: + // Clear all selections. + // tag: + // public + // type: String? + // "row" or "col" or "cell". If omitted, clear all. + this._clearSelection(type || "all"); + }, + isSelecting: function(type){ + // summary: + // Check whether the user is currently selecting something. + // tag: + // public + // type: String + // "row" or "col" or "cell" + // return: Boolean + // true if is selection, false otherwise. + if(typeof type == "undefined"){ + return this._selecting.col || this._selecting.row || this._selecting.cell; + } + return this._selecting[type]; + }, + selectEnabled: function(toEnable){ + // summary: + // Turn on/off this selection functionality if *toEnable* is provided. + // Check whether this selection functionality is enabled if nothing is passed in. + // tag: + // public + // toEnable: Boolean? + // To enable or not. Optional. + // return: Boolean | undefined + // Enabled or not. + if(typeof toEnable != "undefined" && !this.isSelecting()){ + this._enabled = !!toEnable; + } + return this._enabled; + }, + getSelected: function(type, includeExceptions){ + // summary: + // Get an array of selected locations. + // tag: + // public + // type: String + // "row" or "col" or "cell" + // includeExceptions: Boolean + // Only meaningful for rows/columns. If true, all selected rows/cols, even they are partly selected, are all returned. + // return: __SelectItem[] + switch(type){ + case "cell": + return array.map(this._selected[type], function(item){ return item; }); + case "col": case "row": + return array.map(includeExceptions ? this._selected[type] + : array.filter(this._selected[type], function(item){ + return item.except.length === 0; + }), function(item){ + return includeExceptions ? item : item[type]; + }); + } + return []; + }, + getSelectedCount: function(type, includeExceptions){ + // summary: + // Get the number of selected items. + // tag: + // public + // type: String + // "row" or "col" or "cell" + // includeExceptions: Boolean + // Only meaningful for rows/columns. If true, all selected rows/cols, even they are partly selected, are all returned. + // return: Integer + // The number of selected items. + switch(type){ + case "cell": + return this._selected[type].length; + case "col": case "row": + return (includeExceptions ? this._selected[type] + : array.filter(this._selected[type], function(item){ + return item.except.length === 0; + })).length; + } + return 0; + }, + getSelectedType: function(){ + // summary: + // Get the type of selected items. + // tag: + // public + // return: String + // "row" or "col" or "cell", or any mix of these (separator is | ). + var s = this._selected; + return ["", "cell", "row", "row|cell", + "col", "col|cell", "col|row", "col|row|cell" + ][(!!s.cell.length) | (!!s.row.length << 1) | (!!s.col.length << 2)]; + }, + getLastSelectedRange: function(type){ + // summary: + // Get last selected range of the given type. + // tag: + // public + // return: Object + // {start: __SelectItem, end: __SelectItem} + // return null if nothing is selected. + return this._lastAnchorPoint[type] ? { + "start": this._lastAnchorPoint[type], + "end": this._lastEndPoint[type] + } : null; + }, + + //--------------------------private---------------------------- + _hacks: function(){ + // summary: + // Complete the event system of grid, hack some grid functions to prevent default behavior. + var g = this.grid; + var doContentMouseUp = function(e){ + if(e.cellNode){ + g.onMouseUp(e); + } + g.onMouseUpRow(e); + }; + var mouseUp = lang.hitch(g, "onMouseUp"); + var mouseDown = lang.hitch(g, "onMouseDown"); + var doRowSelectorFocus = function(e){ + e.cellNode.style.border = "solid 1px"; + }; + array.forEach(g.views.views, function(view){ + view.content.domouseup = doContentMouseUp; + view.header.domouseup = mouseUp; + if(view.declaredClass == "dojox.grid._RowSelector"){ + view.domousedown = mouseDown; + view.domouseup = mouseUp; + view.dofocus = doRowSelectorFocus; + } + }); + //Disable default selection. + g.selection.clickSelect = function(){}; + + this._oldDeselectAll = g.selection.deselectAll; + var _this = this; + g.selection.selectRange = function(from, to){ + _this.selectRange("row", from, to, true); + if(g.selection.preserver){ + g.selection.preserver._updateMapping(true, true, false, from, to); + } + g.selection.onChanged(); + }; + g.selection.deselectRange = function(from, to){ + _this.selectRange("row", from, to, false); + if(g.selection.preserver){ + g.selection.preserver._updateMapping(true, false, false, from, to); + } + g.selection.onChanged(); + }; + g.selection.deselectAll = function(){ + g._selectingRange = true; + _this._oldDeselectAll.apply(g.selection, arguments); + _this._clearSelection("all"); + g._selectingRange = false; + if(g.selection.preserver){ + g.selection.preserver._updateMapping(true, false, true); + } + g.selection.onChanged(); + }; + + var rowSelector = g.views.views[0]; + //The default function re-write the whole className, so can not insert any other classes. + if(rowSelector instanceof _RowSelector){ + rowSelector.doStyleRowNode = function(inRowIndex, inRowNode){ + html.removeClass(inRowNode, "dojoxGridRow"); + html.addClass(inRowNode, "dojoxGridRowbar"); + html.addClass(inRowNode, "dojoxGridNonNormalizedCell"); + html.toggleClass(inRowNode, "dojoxGridRowbarOver", g.rows.isOver(inRowIndex)); + html.toggleClass(inRowNode, "dojoxGridRowbarSelected", !!g.selection.isSelected(inRowIndex)); + }; + } + this.connect(g, "updateRow", function(rowIndex){ + array.forEach(g.layout.cells, function(cell){ + if(this.isSelected("cell", rowIndex, cell.index)){ + this._highlightNode(cell.getNode(rowIndex), true); + } + }, this); + }); + }, + _mixinGrid: function(){ + // summary: + // Expose events to grid. + var g = this.grid; + g.setupSelectorConfig = lang.hitch(this, this.setupConfig); + g.onStartSelect = function(){}; + g.onEndSelect = function(){}; + g.onStartDeselect = function(){}; + g.onEndDeselect = function(){}; + g.onSelectCleared = function(){}; + }, + _initEvents: function(){ + // summary: + // Connect events, create event handlers. + var g = this.grid, + _this = this, + dp = lang.partial, + starter = function(type, e){ + if(type === "row"){ + _this._isUsingRowSelector = true; + } + //only left mouse button can select. + if(_this.selectEnabled() && _this._config[type] && e.button != 2){ + if(_this._keyboardSelect.col || _this._keyboardSelect.row || _this._keyboardSelect.cell){ + _this._endSelect("all"); + _this._keyboardSelect.col = _this._keyboardSelect.row = _this._keyboardSelect.cell = 0; + } + if(_this._usingKeyboard){ + _this._usingKeyboard = false; + } + var target = _createItem(type, e.rowIndex, e.cell && e.cell.index); + _this._startSelect(type, target, e.ctrlKey, e.shiftKey); + } + }, + ender = lang.hitch(this, "_endSelect"); + this.connect(g, "onHeaderCellMouseDown", dp(starter, "col")); + this.connect(g, "onHeaderCellMouseUp", dp(ender, "col")); + + this.connect(g, "onRowSelectorMouseDown", dp(starter, "row")); + this.connect(g, "onRowSelectorMouseUp", dp(ender, "row")); + + this.connect(g, "onCellMouseDown", function(e){ + if(e.cell && e.cell.isRowSelector){ return; } + if(g.singleClickEdit){ + _this._singleClickEdit = true; + g.singleClickEdit = false; + } + starter(_this._config["cell"] == DISABLED ? "row" : "cell", e); + }); + this.connect(g, "onCellMouseUp", function(e){ + if(_this._singleClickEdit){ + delete _this._singleClickEdit; + g.singleClickEdit = true; + } + ender("all", e); + }); + + this.connect(g, "onCellMouseOver", function(e){ + if(_this._curType != "row" && _this._selecting[_this._curType] && _this._config[_this._curType] == MULTI){ + _this._highlight("col", _createItem("col", e.cell.index), _this._toSelect); + if(!_this._keyboardSelect.cell){ + _this._highlight("cell", _createItem("cell", e.rowIndex, e.cell.index), _this._toSelect); + } + } + }); + this.connect(g, "onHeaderCellMouseOver", function(e){ + if(_this._selecting.col && _this._config.col == MULTI){ + _this._highlight("col", _createItem("col", e.cell.index), _this._toSelect); + } + }); + this.connect(g, "onRowMouseOver", function(e){ + if(_this._selecting.row && _this._config.row == MULTI){ + _this._highlight("row", _createItem("row", e.rowIndex), _this._toSelect); + } + }); + + //When row order has changed in a unpredictable way (sorted or filtered), map the new rowindex. + this.connect(g, "onSelectedById", "_onSelectedById"); + + //When the grid refreshes, all those selected should still appear selected. + this.connect(g, "_onFetchComplete", function(){ + //console.debug("refresh after buildPage:", g._notRefreshSelection); + if(!g._notRefreshSelection){ + this._refreshSelected(true); + } + }); + + //Small scroll might not refresh the grid. + this.connect(g.scroller, "buildPage", function(){ + //console.debug("refresh after buildPage:", g._notRefreshSelection); + if(!g._notRefreshSelection){ + this._refreshSelected(true); + } + }); + + //Whenever the mouse is up, end selecting. + this.connect(win.doc, "onmouseup", dp(ender, "all")); + + //If autoscroll is enabled, connect to it. + this.connect(g, "onEndAutoScroll", function(isVertical, isForward, view, target){ + var selectCell = _this._selecting.cell, + type, current, dir = isForward ? 1 : -1; + if(isVertical && (selectCell || _this._selecting.row)){ + type = selectCell ? "cell" : "row"; + current = _this._currentPoint[type]; + _this._highlight(type, _createItem(type, current.row + dir, current.col), _this._toSelect); + }else if(!isVertical && (selectCell || _this._selecting.col)){ + type = selectCell ? "cell" : "col"; + current = _this._currentPoint[type]; + _this._highlight(type, _createItem(type, current.row, target), _this._toSelect); + } + }); + //If the grid is changed, selection should be consistent. + this.subscribe("dojox/grid/rearrange/move/" + g.id, "_onInternalRearrange"); + this.subscribe("dojox/grid/rearrange/copy/" + g.id, "_onInternalRearrange"); + this.subscribe("dojox/grid/rearrange/change/" + g.id, "_onExternalChange"); + this.subscribe("dojox/grid/rearrange/insert/" + g.id, "_onExternalChange"); + this.subscribe("dojox/grid/rearrange/remove/" + g.id, "clear"); + + //have to also select when the grid's default select is used. + this.connect(g, "onSelected", function(rowIndex){ + if(this._selectedRowModified && this._isUsingRowSelector){ + delete this._selectedRowModified; + }else if(!this.grid._selectingRange){ + this.select("row", rowIndex); + } + }); + this.connect(g, "onDeselected", function(rowIndex){ + if(this._selectedRowModified && this._isUsingRowSelector){ + delete this._selectedRowModified; + }else if(!this.grid._selectingRange){ + this.deselect("row", rowIndex); + } + }); + }, + _onSelectedById: function(id, newIndex, isSelected){ + if(this.grid._noInternalMapping){ + return; + } + var pointSet = [this._lastAnchorPoint.row, this._lastEndPoint.row, + this._lastSelectedAnchorPoint.row, this._lastSelectedEndPoint.row]; + pointSet = pointSet.concat(this._selected.row); + var found = false; + array.forEach(pointSet, function(item){ + if(item){ + if(item.id === id){ + found = true; + item.row = newIndex; + }else if(item.row === newIndex && item.id){ + item.row = -1; + } + } + }); + if(!found && isSelected){ + array.some(this._selected.row, function(item){ + if(item && !item.id && !item.except.length){ + item.id = id; + item.row = newIndex; + return true; + } + return false; + }); + } + found = false; + pointSet = [this._lastAnchorPoint.cell, this._lastEndPoint.cell, + this._lastSelectedAnchorPoint.cell, this._lastSelectedEndPoint.cell]; + pointSet = pointSet.concat(this._selected.cell); + array.forEach(pointSet, function(item){ + if(item){ + if(item.id === id){ + found = true; + item.row = newIndex; + }else if(item.row === newIndex && item.id){ + item.row = -1; + } + } + }); + }, + onSetStore: function(){ + this._clearSelection("all"); + }, + _onInternalRearrange: function(type, mapping){ + try{ + //The column can not refresh it self! + this._refresh("col", false); + + array.forEach(this._selected.row, function(item){ + array.forEach(this.grid.layout.cells, function(cell){ + this._highlightNode(cell.getNode(item.row), false); + }, this); + }, this); + //The rowbar must be cleaned manually + query(".dojoxGridRowSelectorSelected").forEach(function(node){ + html.removeClass(node, "dojoxGridRowSelectorSelected"); + html.removeClass(node, "dojoxGridRowSelectorSelectedUp"); + html.removeClass(node, "dojoxGridRowSelectorSelectedDown"); + }); + + var cleanUp = function(item){ + if(item){ + delete item.converted; + } + }, + pointSet = [this._lastAnchorPoint[type], this._lastEndPoint[type], + this._lastSelectedAnchorPoint[type], this._lastSelectedEndPoint[type]]; + + if(type === "cell"){ + this.selectRange("cell", mapping.to.min, mapping.to.max); + var cells = this.grid.layout.cells; + array.forEach(pointSet, function(item){ + if(item.converted){ return; } + for(var r = mapping.from.min.row, tr = mapping.to.min.row; r <= mapping.from.max.row; ++r, ++tr){ + for(var c = mapping.from.min.col, tc = mapping.to.min.col; c <= mapping.from.max.col; ++c, ++tc){ + while(cells[c].hidden){ ++c; } + while(cells[tc].hidden){ ++tc; } + if(item.row == r && item.col == c){ + //console.log('mapping found: (', item.row, ",",item.col,") to (", tr, ",", tc,")"); + item.row = tr; + item.col = tc; + item.converted = true; + return; + } + } + } + }); + }else{ + pointSet = this._selected.cell.concat(this._selected[type]).concat(pointSet).concat( + [this._lastAnchorPoint.cell, this._lastEndPoint.cell, + this._lastSelectedAnchorPoint.cell, this._lastSelectedEndPoint.cell]); + array.forEach(pointSet, function(item){ + if(item && !item.converted){ + var from = item[type]; + if(from in mapping){ + item[type] = mapping[from]; + } + item.converted = true; + } + }); + array.forEach(this._selected[_theOther[type]], function(item){ + for(var i = 0, len = item.except.length; i < len; ++i){ + var from = item.except[i]; + if(from in mapping){ + item.except[i] = mapping[from]; + } + } + }); + } + + array.forEach(pointSet, cleanUp); + + this._refreshSelected(true); + this._focusPoint(type, this._lastEndPoint); + }catch(e){ + console.warn("Selector._onInternalRearrange() error",e); + } + }, + _onExternalChange: function(type, target){ + var start = type == "cell" ? target.min : target[0], + end = type == "cell" ? target.max : target[target.length - 1]; + this.selectRange(type, start, end); + }, + _refresh: function(type, toHighlight){ + if(!this._keyboardSelect[type]){ + array.forEach(this._selected[type], function(item){ + this._highlightSingle(type, toHighlight, item, undefined, true); + }, this); + } + }, + _refreshSelected: function(){ + this._refresh("col", true); + this._refresh("row", true); + this._refresh("cell", true); + }, + _initAreas: function(){ + var g = this.grid, f = g.focus, _this = this, + keyboardSelectReady = 1, duringKeyboardSelect = 2, + onmove = function(type, createNewEnd, rowStep, colStep, evt){ + //Keyboard swipe selection is SHIFT + Direction Keys. + var ks = _this._keyboardSelect; + //Tricky, rely on valid status not being 0. + if(evt.shiftKey && ks[type]){ + if(ks[type] === keyboardSelectReady){ + if(type === "cell"){ + var item = _this._lastEndPoint[type]; + if(f.cell != g.layout.cells[item.col + colStep] || f.rowIndex != item.row + rowStep){ + ks[type] = 0; + return; + } + } + //If selecting is not started, start it + _this._startSelect(type, _this._lastAnchorPoint[type], true, false, true); + _this._highlight(type, _this._lastEndPoint[type], _this._toSelect); + ks[type] = duringKeyboardSelect; + } + //Highlight to the new end point. + var newEnd = createNewEnd(type, rowStep, colStep, evt); + if(_this._isValid(type, newEnd, g)){ + _this._highlight(type, newEnd, _this._toSelect); + } + _stopEvent(evt); + } + }, + onkeydown = function(type, getTarget, evt, isBubble){ + if(isBubble && _this.selectEnabled() && _this._config[type] != DISABLED){ + switch(evt.keyCode){ + case keys.SPACE: + //Keyboard single point selection is SPACE. + _this._startSelect(type, getTarget(), evt.ctrlKey, evt.shiftKey); + _this._endSelect(type); + break; + case keys.SHIFT: + //Keyboard swipe selection starts with SHIFT. + if(_this._config[type] == MULTI && _this._isValid(type, _this._lastAnchorPoint[type], g)){ + //End last selection if any. + _this._endSelect(type); + _this._keyboardSelect[type] = keyboardSelectReady; + _this._usingKeyboard = true; + } + } + } + }, + onkeyup = function(type, evt, isBubble){ + if(isBubble && evt.keyCode == keys.SHIFT && _this._keyboardSelect[type]){ + _this._endSelect(type); + _this._keyboardSelect[type] = 0; + } + }; + //TODO: this area "rowHeader" should be put outside, same level as header/content. + if(g.views.views[0] instanceof _RowSelector){ + this._lastFocusedRowBarIdx = 0; + f.addArea({ + name:"rowHeader", + onFocus: function(evt, step){ + var view = g.views.views[0]; + if(view instanceof _RowSelector){ + var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0); + if(rowBarNode){ + html.toggleClass(rowBarNode, f.focusClass, false); + } + //evt might not be real event, it may be a mock object instead. + if(evt && "rowIndex" in evt){ + if(evt.rowIndex >= 0){ + _this._lastFocusedRowBarIdx = evt.rowIndex; + }else if(!_this._lastFocusedRowBarIdx){ + _this._lastFocusedRowBarIdx = 0; + } + } + rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0); + if(rowBarNode){ + dijitFocus.focus(rowBarNode); + html.toggleClass(rowBarNode, f.focusClass, true); + } + f.rowIndex = _this._lastFocusedRowBarIdx; + _stopEvent(evt); + return true; + } + return false; + }, + onBlur: function(evt, step){ + var view = g.views.views[0]; + if(view instanceof _RowSelector){ + var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0); + if(rowBarNode){ + html.toggleClass(rowBarNode, f.focusClass, false); + } + _stopEvent(evt); + } + return true; + }, + onMove: function(rowStep, colStep, evt){ + var view = g.views.views[0]; + if(rowStep && view instanceof _RowSelector){ + var next = _this._lastFocusedRowBarIdx + rowStep; + if(next >= 0 && next < g.rowCount){ + //TODO: these logic require a better Scroller. + _stopEvent(evt); + var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0); + html.toggleClass(rowBarNode, f.focusClass, false); + //If the row is not fetched, fetch it. + var sc = g.scroller; + var lastPageRow = sc.getLastPageRow(sc.page); + var rc = g.rowCount - 1, row = Math.min(rc, next); + if(next > lastPageRow){ + g.setScrollTop(g.scrollTop + sc.findScrollTop(row) - sc.findScrollTop(_this._lastFocusedRowBarIdx)); + } + //Now we have fetched the row. + rowBarNode = view.getCellNode(next, 0); + dijitFocus.focus(rowBarNode); + html.toggleClass(rowBarNode, f.focusClass, true); + _this._lastFocusedRowBarIdx = next; + //If the row is out of view, scroll to it. + f.cell = rowBarNode; + f.cell.view = view; + f.cell.getNode = function(index){ + return f.cell; + }; + f.rowIndex = _this._lastFocusedRowBarIdx; + f.scrollIntoView(); + f.cell = null; + } + } + } + }); + f.placeArea("rowHeader","before","content"); + } + //Support keyboard selection. + f.addArea({ + name:"cellselect", + onMove: lang.partial(onmove, "cell", function(type, rowStep, colStep, evt){ + var current = _this._currentPoint[type]; + return _createItem("cell", current.row + rowStep, current.col + colStep); + }), + onKeyDown: lang.partial(onkeydown, "cell", function(){ + return _createItem("cell", f.rowIndex, f.cell.index); + }), + onKeyUp: lang.partial(onkeyup, "cell") + }); + f.placeArea("cellselect","below","content"); + f.addArea({ + name:"colselect", + onMove: lang.partial(onmove, "col", function(type, rowStep, colStep, evt){ + var current = _this._currentPoint[type]; + return _createItem("col", current.col + colStep); + }), + onKeyDown: lang.partial(onkeydown, "col", function(){ + return _createItem("col", f.getHeaderIndex()); + }), + onKeyUp: lang.partial(onkeyup, "col") + }); + f.placeArea("colselect","below","header"); + f.addArea({ + name:"rowselect", + onMove: lang.partial(onmove, "row", function(type, rowStep, colStep, evt){ + return _createItem("row", f.rowIndex); + }), + onKeyDown: lang.partial(onkeydown, "row", function(){ + return _createItem("row", f.rowIndex); + }), + onKeyUp: lang.partial(onkeyup, "row") + }); + f.placeArea("rowselect","below","rowHeader"); + }, + _clearSelection: function(type, reservedItem){ + // summary: + // Clear selection for given type and fire events, but retain the highlight for *reservedItem*, + // thus avoid "flashing". + // tag: + // private + // type: String + // "row", "col", or "cell + // reservedItem: __SelectItem + // The item to retain highlight. + if(type == "all"){ + this._clearSelection("cell", reservedItem); + this._clearSelection("col", reservedItem); + this._clearSelection("row", reservedItem); + return; + } + this._isUsingRowSelector = true; + array.forEach(this._selected[type], function(item){ + if(!_isEqual(type, reservedItem, item)){ + this._highlightSingle(type, false, item); + } + }, this); + this._blurPoint(type, this._currentPoint); + this._selecting[type] = false; + this._startPoint[type] = this._currentPoint[type] = null; + this._selected[type] = []; + + //Have to also deselect default grid selection. + if(type == "row" && !this.grid._selectingRange){ + this._oldDeselectAll.call(this.grid.selection); + this.grid.selection._selectedById = {}; + } + + //Fire events. + this.grid.onEndDeselect(type, null, null, this._selected); + this.grid.onSelectCleared(type); + }, + _startSelect: function(type, start, extending, isRange, mandatarySelect, toSelect){ + // summary: + // Start selection, setup start point and current point, fire events. + // tag: + // private + // type: String + // "row", "col", or "cell" + // extending: Boolean + // Whether this is a multi selection + // isRange: Boolean + // Whether this is a range selection (i.e. select from the last end point to this point) + // start: __SelectItem + // The start point + // mandatarySelect: Boolean + // If true, toSelect will be same as the original selection status. + if(!this._isValid(type, start)){ + return; + } + var lastIsSelected = this._isSelected(type, this._lastEndPoint[type]), + isSelected = this._isSelected(type, start); + + if(this.noClear && !extending){ + this._toSelect = toSelect === undefined ? true : toSelect; + }else{ + //If we are modifying the selection using keyboard, retain the old status. + this._toSelect = mandatarySelect ? isSelected : !isSelected; + } + + //If CTRL is not pressed or it's SINGLE mode, this is a brand new selection. + if(!extending || (!isSelected && this._config[type] == SINGLE)){ + this._clearSelection("col", start); + this._clearSelection("cell", start); + if(!this.noClear || (type === 'row' && this._config[type] == SINGLE)){ + this._clearSelection('row', start); + } + this._toSelect = toSelect === undefined ? true : toSelect; + } + + this._selecting[type] = true; + this._currentPoint[type] = null; + + //We're holding SHIFT while clicking, it's a Click-Range selection. + if(isRange && this._lastType == type && lastIsSelected == this._toSelect && this._config[type] == MULTI){ + if(type === "row"){ + this._isUsingRowSelector = true; + } + this._startPoint[type] = this._lastEndPoint[type]; + this._highlight(type, this._startPoint[type]); + this._isUsingRowSelector = false; + }else{ + this._startPoint[type] = start; + } + //Now start selection + this._curType = type; + this._fireEvent("start", type); + this._isStartFocus = true; + this._isUsingRowSelector = true; + this._highlight(type, start, this._toSelect); + this._isStartFocus = false; + }, + _endSelect: function(type){ + // summary: + // End selection. Keep records, fire events and cleanup status. + // tag: + // private + // type: String + // "row", "col", or "cell" + if(type === "row"){ + delete this._isUsingRowSelector; + } + if(type == "all"){ + this._endSelect("col"); + this._endSelect("row"); + this._endSelect("cell"); + }else if(this._selecting[type]){ + this._addToSelected(type); + this._lastAnchorPoint[type] = this._startPoint[type]; + this._lastEndPoint[type] = this._currentPoint[type]; + if(this._toSelect){ + this._lastSelectedAnchorPoint[type] = this._lastAnchorPoint[type]; + this._lastSelectedEndPoint[type] = this._lastEndPoint[type]; + } + this._startPoint[type] = this._currentPoint[type] = null; + this._selecting[type] = false; + this._lastType = type; + this._fireEvent("end", type); + } + }, + _fireEvent: function(evtName, type){ + switch(evtName){ + case "start": + this.grid[this._toSelect ? "onStartSelect" : "onStartDeselect"](type, this._startPoint[type], this._selected); + break; + case "end": + this.grid[this._toSelect ? "onEndSelect" : "onEndDeselect"](type, this._lastAnchorPoint[type], this._lastEndPoint[type], this._selected); + break; + } + }, + _calcToHighlight: function(type, target, toHighlight, toSelect){ + // summary: + // Calculate what status should *target* have. + // If *toSelect* is not provided, this is a no op. + // This function is time-critical!! + if(toSelect !== undefined){ + var sltd; + if(this._usingKeyboard && !toHighlight){ + var last = this._isInLastRange(this._lastType, target); + if(last){ + sltd = this._isSelected(type, target); + //This 2 cases makes the keyboard swipe selection valid! + if(toSelect && sltd){ + return false; + } + if(!toSelect && !sltd && this._isInLastRange(this._lastType, target, true)){ + return true; + } + } + } + return toHighlight ? toSelect : (sltd || this._isSelected(type, target)); + } + return toHighlight; + }, + _highlightNode: function(node, toHighlight){ + // summary: + // Do the actual highlight work. + if(node){ + var selectCSSClass = "dojoxGridRowSelected"; + var selectCellClass = "dojoxGridCellSelected"; + html.toggleClass(node, selectCSSClass, toHighlight); + html.toggleClass(node, selectCellClass, toHighlight); + } + }, + _highlightHeader: function(colIdx, toHighlight){ + var cells = this.grid.layout.cells; + var node = cells[colIdx].getHeaderNode(); + var selectedClass = "dojoxGridHeaderSelected"; + html.toggleClass(node, selectedClass, toHighlight); + }, + _highlightRowSelector: function(rowIdx, toHighlight){ + //var t1 = (new Date()).getTime(); + var rowSelector = this.grid.views.views[0]; + if(rowSelector instanceof _RowSelector){ + var node = rowSelector.getRowNode(rowIdx); + if(node){ + var selectedClass = "dojoxGridRowSelectorSelected"; + html.toggleClass(node, selectedClass, toHighlight); + } + } + //console.log((new Date()).getTime() - t1); + }, + _highlightSingle: function(type, toHighlight, target, toSelect, isRefresh){ + // summary: + // Highlight a single item. + // This function is time critical!! + var _this = this, toHL, g = _this.grid, cells = g.layout.cells; + switch(type){ + case "cell": + toHL = this._calcToHighlight(type, target, toHighlight, toSelect); + var c = cells[target.col]; + if(!c.hidden && !c.notselectable){ + this._highlightNode(target.node || c.getNode(target.row), toHL); + } + break; + case "col": + toHL = this._calcToHighlight(type, target, toHighlight, toSelect); + this._highlightHeader(target.col, toHL); + query("td[idx='" + target.col + "']", g.domNode).forEach(function(cellNode){ + var rowNode = cells[target.col].view.content.findRowTarget(cellNode); + if(rowNode){ + var rowIndex = rowNode[dojox.grid.util.rowIndexTag]; + _this._highlightSingle("cell", toHL, { + "row": rowIndex, + "col": target.col, + "node": cellNode + }); + } + }); + break; + case "row": + toHL = this._calcToHighlight(type, target, toHighlight, toSelect); + this._highlightRowSelector(target.row, toHL); + if(this._config.cell){ + array.forEach(cells, function(cell){ + _this._highlightSingle("cell", toHL, { + "row": target.row, + "col": cell.index, + "node": cell.getNode(target.row) + }); + }); + } + //To avoid dead lock + this._selectedRowModified = true; + if(!isRefresh){ + g.selection.setSelected(target.row, toHL); + } + } + }, + _highlight: function(type, target, toSelect){ + // summary: + // Highlight from start point to target. + // toSelect: Boolean + // Whether we are selecting or deselecting. + // This function is time critical!! + if(this._selecting[type] && target !== null){ + var start = this._startPoint[type], + current = this._currentPoint[type], + _this = this, + highlight = function(from, to, toHL){ + _this._forEach(type, from, to, function(item){ + _this._highlightSingle(type, toHL, item, toSelect); + }, true); + }; + switch(type){ + case "col": case "row": + if(current !== null){ + if(_inRange(type, target, start, current, true)){ + //target is between start and current, some selected should be deselected. + highlight(current, target, false); + }else{ + if(_inRange(type, start, target, current, true)){ + //selection has jumped to different direction, all should be deselected. + highlight(current, start, false); + current = start; + } + highlight(target, current, true); + } + }else{ + //First time select. + this._highlightSingle(type, true, target, toSelect); + } + break; + case "cell": + if(current !== null){ + if(_inRange("row", target, start, current, true) || + _inRange("col", target, start, current, true) || + _inRange("row", start, target, current, true) || + _inRange("col", start, target, current, true)){ + highlight(start, current, false); + } + } + highlight(start, target, true); + } + this._currentPoint[type] = target; + this._focusPoint(type, this._currentPoint); + } + }, + _focusPoint: function(type, point){ + // summary: + // Focus the current point, so when you move mouse, the focus indicator follows you. + if(!this._isStartFocus){ + var current = point[type], + f = this.grid.focus; + if(type == "col"){ + f._colHeadFocusIdx = current.col; + f.focusArea("header"); + }else if(type == "row"){ + f.focusArea("rowHeader", { + "rowIndex": current.row + }); + }else if(type == "cell"){ + f.setFocusIndex(current.row, current.col); + } + } + }, + _blurPoint: function(type, point){ + // summary: + // Blur the current point. + var f = this.grid.focus; + if(type == "col"){ + f._blurHeader(); + }else if(type == "cell"){ + f._blurContent(); + } + }, + _addToSelected: function(type){ + // summary: + // Record the selected items. + var toSelect = this._toSelect, _this = this, + toAdd = [], toRemove = [], + start = this._startPoint[type], + end = this._currentPoint[type]; + if(this._usingKeyboard){ + //If using keyboard, selection will be ended after every move. But we have to remember the original selection status, + //so as to return to correct status when we shrink the selection region. + this._forEach(type, this._lastAnchorPoint[type], this._lastEndPoint[type], function(item){ + //If the original selected item is not in current range, change its status. + if(!_inRange(type, item, start, end)){ + (toSelect ? toRemove : toAdd).push(item); + } + }); + } + this._forEach(type, start, end, function(item){ + var isSelected = _this._isSelected(type, item); + if(toSelect && !isSelected){ + //Add new selected items + toAdd.push(item); + }else if(!toSelect){ + //Remove deselected items. + toRemove.push(item); + } + }); + this._add(type, toAdd); + this._remove(type, toRemove); + + // have to keep record in original grid selection + array.forEach(this._selected.row, function(item){ + if(item.except.length > 0){ + //to avoid dead lock + this._selectedRowModified = true; + this.grid.selection.setSelected(item.row, false); + } + }, this); + }, + _forEach: function(type, start, end, func, halfClose){ + // summary: + // Go through items from *start* point to *end* point. + // This function is time critical!! + if(!this._isValid(type, start, true) || !this._isValid(type, end, true)){ + return; + } + switch(type){ + case "col": case "row": + start = start[type]; + end = end[type]; + var dir = end > start ? 1 : -1; + if(!halfClose){ + end += dir; + } + for(; start != end; start += dir){ + func(_createItem(type, start)); + } + break; + case "cell": + var colDir = end.col > start.col ? 1 : -1, + rowDir = end.row > start.row ? 1 : -1; + for(var i = start.row, p = end.row + rowDir; i != p; i += rowDir){ + for(var j = start.col, q = end.col + colDir; j != q; j += colDir){ + func(_createItem(type, i, j)); + } + } + } + }, + _makeupForExceptions: function(type, newCellItems){ + // summary: + // When new cells is selected, maybe they will fill in the "holes" in selected rows and columns. + var makedUps = []; + array.forEach(this._selected[type], function(v1){ + array.forEach(newCellItems, function(v2){ + if(v1[type] == v2[type]){ + var pos = array.indexOf(v1.except, v2[_theOther[type]]); + if(pos >= 0){ + v1.except.splice(pos, 1); + } + makedUps.push(v2); + } + }); + }); + return makedUps; + }, + _makeupForCells: function(type, newItems){ + // summary: + // When some rows/cols are selected, maybe they can cover some of the selected cells, + // and fill some of the "holes" in the selected cols/rows. + var toRemove = []; + array.forEach(this._selected.cell, function(v1){ + array.some(newItems, function(v2){ + if(v1[type] == v2[type]){ + toRemove.push(v1); + return true; + } + return false; + }); + }); + this._remove("cell", toRemove); + array.forEach(this._selected[_theOther[type]], function(v1){ + array.forEach(newItems, function(v2){ + var pos = array.indexOf(v1.except, v2[type]); + if(pos >= 0){ + v1.except.splice(pos, 1); + } + }); + }); + }, + _addException: function(type, items){ + // summary: + // If some rows/cols are deselected, maybe they have created "holes" in selected cols/rows. + array.forEach(this._selected[type], function(v1){ + array.forEach(items, function(v2){ + v1.except.push(v2[_theOther[type]]); + }); + }); + }, + _addCellException: function(type, items){ + // summary: + // If some cells are deselected, maybe they have created "holes" in selected rows/cols. + array.forEach(this._selected[type], function(v1){ + array.forEach(items, function(v2){ + if(v1[type] == v2[type]){ + v1.except.push(v2[_theOther[type]]); + } + }); + }); + }, + _add: function(type, items){ + // summary: + // Add to the selection record. + var cells = this.grid.layout.cells; + if(type == "cell"){ + var colMakedup = this._makeupForExceptions("col", items); + var rowMakedup = this._makeupForExceptions("row", items); + //Step over hidden columns. + items = array.filter(items, function(item){ + return array.indexOf(colMakedup, item) < 0 && array.indexOf(rowMakedup, item) < 0 && + !cells[item.col].hidden && !cells[item.col].notselectable; + }); + }else{ + if(type == "col"){ + //Step over hidden columns. + items = array.filter(items, function(item){ + return !cells[item.col].hidden && !cells[item.col].notselectable; + }); + } + this._makeupForCells(type, items); + this._selected[type] = array.filter(this._selected[type], function(v){ + return array.every(items, function(item){ + return v[type] !== item[type]; + }); + }); + } + if(type != "col" && this.grid._hasIdentity){ + array.forEach(items, function(item){ + var record = this.grid._by_idx[item.row]; + if(record){ + item.id = record.idty; + } + }, this); + } + this._selected[type] = this._selected[type].concat(items); + }, + _remove: function(type, items){ + // summary: + // Remove from the selection record. + var comp = lang.partial(_isEqual, type); + this._selected[type] = array.filter(this._selected[type], function(v1){ + return !array.some(items, function(v2){ + return comp(v1, v2); + }); + }); + if(type == "cell"){ + this._addCellException("col", items); + this._addCellException("row", items); + }else if(this._config.cell){ + this._addException(_theOther[type], items); + } + }, + _isCellNotInExcept: function(type, item){ + // summary: + // Return true only when a cell is covered by selected row/col, and its not a "hole". + var attr = item[type], corres = item[_theOther[type]]; + return array.some(this._selected[type], function(v){ + return v[type] == attr && array.indexOf(v.except, corres) < 0; + }); + }, + _isSelected: function(type, item){ + // summary: + // Return true when the item is selected. (or logically selected, i.e, covered by a row/col). + if(!item){ return false; } + var res = array.some(this._selected[type], function(v){ + var ret = _isEqual(type, item, v); + if(ret && type !== "cell"){ + return v.except.length === 0; + } + return ret; + }); + if(!res && type === "cell"){ + res = (this._isCellNotInExcept("col", item) || this._isCellNotInExcept("row", item)); + if(type === "cell"){ + res = res && !this.grid.layout.cells[item.col].notselectable; + } + } + return res; + }, + _isInLastRange: function(type, item, isSelected){ + // summary: + // Return true only when the item is in the last seletion/deseletion range. + var start = this[isSelected ? "_lastSelectedAnchorPoint" : "_lastAnchorPoint"][type], + end = this[isSelected ? "_lastSelectedEndPoint" : "_lastEndPoint"][type]; + if(!item || !start || !end){ return false; } + return _inRange(type, item, start, end); + }, + _isValid: function(type, item, allowNotSelectable){ + // summary: + // Check whether the item is a valid __SelectItem for the given type. + if(!item){ return false; } + try{ + var g = this.grid, index = item[type]; + switch(type){ + case "col": + return index >= 0 && index < g.layout.cells.length && lang.isArray(item.except) && + (allowNotSelectable || !g.layout.cells[index].notselectable); + case "row": + return index >= 0 && index < g.rowCount && lang.isArray(item.except); + case "cell": + return item.col >= 0 && item.col < g.layout.cells.length && + item.row >= 0 && item.row < g.rowCount && + (allowNotSelectable || !g.layout.cells[item.col].notselectable); + } + }catch(e){} + return false; + } +}); + +EnhancedGrid.registerPlugin(Selector/*name:'selector'*/, { + "dependency": ["autoScroll"] +}); + +return Selector; + +});
\ No newline at end of file diff --git a/js/dojo/dojox/grid/enhanced/plugins/_RowMapLayer.js b/js/dojo/dojox/grid/enhanced/plugins/_RowMapLayer.js new file mode 100644 index 0000000..bf19ce3 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/_RowMapLayer.js @@ -0,0 +1,207 @@ +//>>built +define("dojox/grid/enhanced/plugins/_RowMapLayer", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "./_StoreLayer" +], function(declare, array, lang, layers){ + +var _devideToArrays = function(a){ + a.sort(function(v1, v2){ + return v1 - v2; + }); + var arr = [[a[0]]]; + for(var i = 1, j = 0; i < a.length; ++i){ + if(a[i] == a[i-1] + 1){ + arr[j].push(a[i]); + }else{ + arr[++j] = [a[i]]; + } + } + return arr; +}, +hitchIfCan = function(scope, func){ + return func ? lang.hitch(scope || lang.global, func) : function(){}; +}; + +return declare("dojox.grid.enhanced.plugins._RowMapLayer", layers._StoreLayer, { + tags: ["reorder"], + constructor: function(grid){ + this._map = {}; + this._revMap = {}; + this.grid = grid; + this._oldOnDelete = grid._onDelete; + var _this = this; + grid._onDelete = function(item){ + _this._onDelete(item); + _this._oldOnDelete.call(grid, item); + }; + this._oldSort = grid.sort; + grid.sort = function(){ + _this.clearMapping(); + _this._oldSort.apply(grid, arguments); + }; + }, + uninitialize: function(){ + this.grid._onDelete = this._oldOnDelete; + this.grid.sort = this._oldSort; + }, + setMapping: function(mapping){ + // summary: + // Remember the row mapping. + // mapping: Object + // keys are original rowIndexes, values are new rowIndexes. + this._store.forEachLayer(function(layer){ + if(layer.name() === "rowmap"){ + return false; + }else if(layer.onRowMappingChange){ + layer.onRowMappingChange(mapping); + } + return true; + }, false); + var from, to, origin, revmap = {}; + for(from in mapping){ + from = parseInt(from, 10); + to = mapping[from]; + if(typeof to == "number"){ + if(from in this._revMap){ + origin = this._revMap[from]; + delete this._revMap[from]; + }else{ + origin = from; + } + if(origin == to){ + delete this._map[origin]; + revmap[to] = "eq"; + }else{ + this._map[origin] = to; + revmap[to] = origin; + } + } + } + for(to in revmap){ + if(revmap[to] === "eq"){ + delete this._revMap[parseInt(to, 10)]; + }else{ + this._revMap[parseInt(to, 10)] = revmap[to]; + } + } + }, + clearMapping: function(){ + this._map = {}; + this._revMap = {}; + }, + _onDelete: function(item){ + var idx = this.grid._getItemIndex(item, true); + if(idx in this._revMap){ + var rowIdxArr = [], r, i, origin = this._revMap[idx]; + delete this._map[origin]; + delete this._revMap[idx]; + for(r in this._revMap){ + r = parseInt(r, 10); + if(this._revMap[r] > origin){ + --this._revMap[r]; + } + } + for(r in this._revMap){ + r = parseInt(r, 10); + if(r > idx){ + rowIdxArr.push(r); + } + } + rowIdxArr.sort(function(a, b){ + return b - a; + }); + for(i = rowIdxArr.length - 1; i >= 0; --i){ + r = rowIdxArr[i]; + this._revMap[r - 1] = this._revMap[r]; + delete this._revMap[r]; + } + this._map = {}; + for(r in this._revMap){ + this._map[this._revMap[r]] = r; + } + } + }, + _fetch: function(userRequest){ + var mapCount = 0, r; + var start = userRequest.start || 0; + for(r in this._revMap){ + r = parseInt(r, 10); + if(r >= start){ + ++mapCount; + } + } + if(mapCount > 0){ + //Row mapping is in use. + var rows = [], i, map = {}, + count = userRequest.count > 0 ? userRequest.count : -1; + if(count > 0){ + for(i = 0; i < count; ++i){ + r = start + i; + r = r in this._revMap ? this._revMap[r] : r; + map[r] = i; + rows.push(r); + } + }else{ + //We don't have a count, must create our own. + for(i = 0;; ++i){ + r = start + i; + if(r in this._revMap){ + --mapCount; + r = this._revMap[r]; + } + map[r] = i; + rows.push(r); + if(mapCount <= 0){ + break; + } + } + } + this._subFetch(userRequest, this._getRowArrays(rows), 0, [], map, userRequest.onComplete, start, count); + return userRequest; + }else{ + //No row mapping at all. + return lang.hitch(this._store, this._originFetch)(userRequest); + } + }, + _getRowArrays: function(rows){ + return _devideToArrays(rows); + }, + _subFetch: function(userRequest, rowArrays, index, result, map, oldOnComplete, start, count){ + var arr = rowArrays[index], _this = this; + var urstart = userRequest.start = arr[0]; + userRequest.count = arr[arr.length - 1] - arr[0] + 1; + userRequest.onComplete = function(items){ + array.forEach(items, function(item, i){ + var r = urstart + i; + if(r in map){ + result[map[r]] = item; + } + }); + if(++index == rowArrays.length){ + //mapped rows are all fetched. + if(count > 0){ + userRequest.start = start; + userRequest.count = count; + userRequest.onComplete = oldOnComplete; + hitchIfCan(userRequest.scope, oldOnComplete)(result, userRequest); + }else{ + userRequest.start = userRequest.start + items.length; + delete userRequest.count; + userRequest.onComplete = function(items){ + result = result.concat(items); + userRequest.start = start; + userRequest.onComplete = oldOnComplete; + hitchIfCan(userRequest.scope, oldOnComplete)(result, userRequest); + }; + _this.originFetch(userRequest); + } + }else{ + _this._subFetch(userRequest, rowArrays, index, result, map, oldOnComplete, start, count); + } + }; + _this.originFetch(userRequest); + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/_SelectionPreserver.js b/js/dojo/dojox/grid/enhanced/plugins/_SelectionPreserver.js new file mode 100644 index 0000000..dff96c1 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/_SelectionPreserver.js @@ -0,0 +1,109 @@ +//>>built +define("dojox/grid/enhanced/plugins/_SelectionPreserver", [ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/_base/connect", + '../../_SelectionPreserver' +], function(declare, lang, connect, _SelectionPreserver){ + +return declare("dojox.grid.enhanced.plugins._SelectionPreserver", _SelectionPreserver, { + // summary: + // Preserve selections across various user actions. + // + // description: + // Extends dojox.grid._SelectionPreserver adding a bit more support to make selection persistence working well + // with various EnhancedGrid features, e.g. filtering, nested sorting, pagination, select all etc. + // + // Precondition - Identifier(id) is required for store, as id is used for differentiating row items. + // Known issue - The preserved selections might be inaccurate if some unloaded rows are previously selected by range(e.g.SHIFT + click) + // + // example: + // | //To turn on this - set 'keepSelection' attribute to true + // | <div dojoType="dojox.grid.EnhancedGrid" keepSelection = true .../> + + constructor: function(selection){ + var grid = this.grid; + grid.onSelectedById = this.onSelectedById; + this._oldClearData = grid._clearData; + var self = this; + grid._clearData = function(){ + self._updateMapping(!grid._noInternalMapping); + self._trustSelection = []; + self._oldClearData.apply(grid, arguments); + }; + this._connects.push( + connect.connect(selection, 'selectRange', lang.hitch(this, '_updateMapping', true, true, false)), + connect.connect(selection, 'deselectRange', lang.hitch(this, '_updateMapping', true, false, false)), + connect.connect(selection, 'deselectAll', lang.hitch(this, '_updateMapping', true, false, true)) + ); + }, + destroy: function(){ + this.inherited(arguments); + this.grid._clearData = this._oldClearData; + }, + reset: function(){ + this.inherited(arguments); + this._idMap = []; + this._trustSelection = []; + this._defaultSelected = false; + }, + _reSelectById: function(item, index){ + // summary: + // Overwritten + var s = this.selection, g = this.grid; + if(item && g._hasIdentity){ + var id = g.store.getIdentity(item); + if(this._selectedById[id] === undefined){ + if(!this._trustSelection[index]){ + s.selected[index] = this._defaultSelected; + } + }else{ + s.selected[index] = this._selectedById[id]; + } + this._idMap.push(id); + g.onSelectedById(id, index, s.selected[index]); + } + }, + _selectById: function(toSelect, inItemOrIndex){ + // summary: + // Overwritten + if(!this.inherited(arguments)){ + this._trustSelection[inItemOrIndex] = true; + } + }, + onSelectedById: function(id, rowIndex, value){}, + + _updateMapping: function(trustSelection, isSelect, isForAll, from, to){ + // summary: + // This function try to keep the selection info updated when range selection is performed. + // 1. Calculate how many unloaded rows are there; + // 2. update _selectedById data if grid.selection._selected can be trusted, so loaded but unselected rows can + // be properly recorded. + var s = this.selection, g = this.grid, flag = 0, unloaded = 0, i, id; + for(i = g.rowCount - 1; i >= 0; --i){ + if(!g._by_idx[i]){ + ++unloaded; + flag += s.selected[i] ? 1 : -1; + }else{ + id = g._by_idx[i].idty; + if(id && (trustSelection || this._selectedById[id] === undefined)){ + this._selectedById[id] = !!s.selected[i]; + } + } + } + if(unloaded){ + this._defaultSelected = flag > 0; + } + if(!isForAll && from !== undefined && to !== undefined){ + isForAll = !g.usingPagination && Math.abs(to - from + 1) === g.rowCount; + } + // When deselectAll, make sure every thing is deselected, even if it was selected but not loaded now. + // This occurs only when pagination's "All" is used. + if(isForAll && (!g.usingPagination || g.selectionMode === 'single')){ + for(i = this._idMap.length - 1; i >= 0; --i){ + this._selectedById[this._idMap[i]] = isSelect; + } + } + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/_StoreLayer.js b/js/dojo/dojox/grid/enhanced/plugins/_StoreLayer.js new file mode 100644 index 0000000..bf73a6d --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/_StoreLayer.js @@ -0,0 +1,395 @@ +//>>built +define("dojox/grid/enhanced/plugins/_StoreLayer", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/xhr" +], function(declare, array, lang, xhr){ +// summary: +// The dojo.data.api.Read API is powerful, but it's difficult to give the store some special commands before +// fetch, so that the store content can be temporarily modified or transformed, and acts as another store. The +// parameter *query* or *queryOptions* in keywordArgs for *fetch* is not enough because: +// 1. users do not have the opportunity to response to the store actions when these options or queries are applied, +// especially when the real store is at server side. +// 2. the store implementation must be changed to support any new options in 'query' or 'queryOptions', so it'll be +// difficult if this implementation is not able to or very hard to be changed, or some new options are required to +// be valid for all stores. +// This *StoreLayer* framework is dedicated to provide a uniform way for configuring an existing store, so that +// it can be easily extended to have special behaviors or act like a totally different store. +// The major approach is to wrap the *fetch* function of store, layer by layer. Every layer treats the incoming +// store.fetch as a 'black box', thus maintaining the independence between layers. +// *fetch* is the most important data retriever in the Read API, almost all other functions are used for a single +// item, and require that this item is already retrieved (by and only by *fetch*). So once we've controlled this +// *fetch* function, we've controlled almost the whole store. This fact simplifies our implementation of StoreLayer. +// example: +// //ns is for namespace, i.e.:dojox.grid.enhanced.plugins +// ns.wrap(ns.wrap(ns.wrap(store, new ns.FilterLayer()), new ns.UniqueLayer()), new ns.TransformLayer()); +// +// //every layer has a name, it should be given in the document of this layer. +// //if you don't know it's name, you can get it by: ns.SomeLayer.prototype.name(); +// store.layer("filter").filterDef(...); +// store.layer("unique").setUniqueColumns(...); +// store.layer("transform").setScheme(...); +// +// //now use the store as usual... +// +// store.unwrap("transform"); //remove the transform layer but retain the other two. +// +// //now use the store as usual... +// +// store.unwrap(); //remove all the layers, get the original store back. + + var ns = lang.getObject("grid.enhanced.plugins", true, dojox); + + var getPrevTags = function(tags){ + var tagList = ["reorder", "sizeChange", "normal", "presentation"]; + var idx = tagList.length; + for(var i = tags.length - 1; i >= 0; --i){ + var p = array.indexOf(tagList, tags[i]); + if(p >= 0 && p <= idx){ + idx = p; + } + } + if(idx < tagList.length - 1){ + return tagList.slice(0, idx + 1); + }else{ + return tagList; + } + }, + + unwrap = function(/* string? */layerName){ + // summary: + // Unwrap the layers of the store + // tags: + // public + // returns: + // The unwrapped store, for nested use only. + var i, layers = this._layers, len = layers.length; + if(layerName){ + for(i = len-1; i >= 0; --i){ + if(layers[i].name() == layerName){ + layers[i]._unwrap(layers[i + 1]); + break; + } + } + layers.splice(i, 1); + }else{ + for(i = len - 1; i >= 0; --i){ + layers[i]._unwrap(); + } + } + if(!layers.length){ + delete this._layers; + delete this.layer; + delete this.unwrap; + delete this.forEachLayer; + } + //console.log("layers:",this._layers); + return this; //Read-store + }, + + getLayer = function(layerName){ + // summary: + // Get a layer of the store, so we can configure that layer. + // tags: + // public (scope is store) + // layerName: string + // the name of the layer + // returns: + // the store layer object + var i, layers = this._layers; + if(typeof layerName == "undefined"){ + return layers.length; //Integer + } + if(typeof layerName == "number"){ + return layers[layerName]; //_StoreLayer + } + for(i = layers.length - 1; i >= 0; --i){ + if(layers[i].name() == layerName){ + return layers[i]; //_StoreLayer + } + } + return null; //_StoreLayer + }, + + forEachLayer = function(callback, isInnerToOuter){ + // summary: + // Visit the layers one by one. From the outer most to inner most by default. + // callback: Function + // The function to callback. + // If return false, break the loop. + // isInnerToOuter: Boolean + // Whether visit from the inner most layer to the outer most layer. + var len = this._layers.length, start, end, dir; + if(isInnerToOuter){ + start = 0; + end = len; + dir = 1; + }else{ + start = len - 1; + end = -1; + dir = -1; + } + for(var i = start; i != end; i += dir){ + if(callback(this._layers[i], i) === false){ + return i; + } + } + return end; + }; + ns.wrap = function(store, funcName, layer, layerFuncName){ + // summary: + // Wrap the store with the given layer. + // tags: + // public + // store: Read-store + // The store to be wrapped. + // layer: _StoreLayer + // The layer to be used + // returns + // The wrapped store, for nested use only. + if(!store._layers){ + store._layers = []; + store.layer = lang.hitch(store, getLayer); + store.unwrap = lang.hitch(store, unwrap); + store.forEachLayer = lang.hitch(store, forEachLayer); + } + var prevTags = getPrevTags(layer.tags); + if(!array.some(store._layers, function(lyr, i){ + if(array.some(lyr.tags, function(tag){ + return array.indexOf(prevTags, tag) >= 0; + })){ + return false; + }else{ + store._layers.splice(i, 0, layer); + layer._wrap(store, funcName, layerFuncName, lyr); + return true; + } + })){ + store._layers.push(layer); + layer._wrap(store, funcName, layerFuncName); + } + //console.log("wrapped layers:", dojo.map(store._layers, function(lyr){return lyr.name();})); + return store; //Read-store + }; + + var _StoreLayer = declare("dojox.grid.enhanced.plugins._StoreLayer", null, { + // summary: + // The most abstract class of store layers, provides basic utilities and some interfaces. + // tags: + // abstract +/*===== + // _store: [protected] Read-store + // The wrapped store. + _store: null, + + // _originFetch: [protected] function + // The original fetch function of the store. + _originFetch: null, + + // __enabled: [private] Boolean + // To control whether this layer is valid. + __enabled: true, +=====*/ + tags: ["normal"], + + layerFuncName: "_fetch", + + constructor: function(){ + this._store = null; + this._originFetch = null; + this.__enabled = true; + }, + initialize: function(store){ + // summary: + // + }, + uninitialize: function(store){ + // summary: + // + }, + invalidate: function(){ + + }, + _wrap: function(store, funcName, layerFuncName, nextLayer){ + // summary: + // Do the actual wrapping (or 'hacking' if you like) to the store. + // tags: + // internal + // store: Read-store + // The store to be wrapped. + this._store = store; + this._funcName = funcName; + var fetchFunc = lang.hitch(this, function(){ + return (this.enabled() ? this[layerFuncName || this.layerFuncName] : this.originFetch).apply(this, arguments); + }); + if(nextLayer){ + this._originFetch = nextLayer._originFetch; + nextLayer._originFetch = fetchFunc; + }else{ + this._originFetch = store[funcName] || function(){}; + store[funcName] = fetchFunc; + } + this.initialize(store); + }, + _unwrap: function(nextLayer){ + // summary: + // Do the actual unwrapping to the store. + // tags: + // internal + // store: Read-store + // The store to be unwrapped. + this.uninitialize(this._store); + if(nextLayer){ + nextLayer._originFetch = this._originFetch; + }else{ + this._store[this._funcName] = this._originFetch; + } + this._originFetch = null; + this._store = null; + }, + enabled: function(/* bool? */toEnable){ + // summary: + // The get/set function of the enabled status of this layer + // tags: + // public + // toEnable: Boolean? + // If given, is a setter, otherwise, it's getter. + if(typeof toEnable != "undefined"){ + this.__enabled = !!toEnable; + } + return this.__enabled; //Boolean + }, + name: function(){ + // summary: + // Get the name of this store layer. + // The default name retrieved from class name, which should have a pattern of "{name}Layer". + // If this pattern does not exist, the whole class name will be this layer's name. + // It's better to override this method if your class name is too complicated. + // tags: + // public extension + // returns: + // The name of this layer. + if(!this.__name){ + var m = this.declaredClass.match(/(?:\.(?:_*)([^\.]+)Layer$)|(?:\.([^\.]+)$)/i); + this.__name = m ? (m[1] || m[2]).toLowerCase() : this.declaredClass; + } + return this.__name; + }, + originFetch: function(){ + return (lang.hitch(this._store, this._originFetch)).apply(this, arguments); + } + }); + var _ServerSideLayer = declare("dojox.grid.enhanced.plugins._ServerSideLayer", _StoreLayer, { + // summary: + // The most abstract class for all server side store layers. + // tags: + // abstract +/*===== + // _url: [protected] string + // The url of the server + _url: "", + // __cmds [private] object + // The command object to be sent to server. + __cmds: {}, +=====*/ + constructor: function(args){ + args = args || {}; + this._url = args.url || ""; + this._isStateful = !!args.isStateful; + this._onUserCommandLoad = args.onCommandLoad || function(){}; + this.__cmds = {cmdlayer:this.name(), enable:true}; + + //Only for stateful server, sending commands before fetch makes sense. + this.useCommands(this._isStateful); + }, + enabled: function(/* bool? */toEnable){ + // summary: + // Overrided from _StoreLayer.enabled + var res = this.inherited(arguments); + this.__cmds.enable = this.__enabled; + return res; + }, + useCommands: function(/* bool? */toUse){ + // summary: + // If you only want to modify the user request, instead of sending a separate command + // to server before fetch, just call: + // this.useCommand(false); + // tags: + // public + // toUse: Boolean? + // If provided, it's a setter, otherwise, it's a getter + if(typeof toUse != "undefined"){ + this.__cmds.cmdlayer = (toUse && this._isStateful) ? this.name() : null; + } + return !!(this.__cmds.cmdlayer); //Boolean + }, + _fetch: function(/* keywordArgs */userRequest){ + // summary: + // Implementation of _StoreLayer._fetch + if(this.__cmds.cmdlayer){ + //We're gonna send command to server before fetch. + xhr.post({ + url: this._url || this._store.url, + content: this.__cmds, + load: lang.hitch(this, function(responce){ + this.onCommandLoad(responce, userRequest); + this.originFetch(userRequest); + }), + error: lang.hitch(this, this.onCommandError) + }); + }else{ + //The user only wants to modify the request object. + this.onCommandLoad("", userRequest); + this.originFetch(userRequest); + } + return userRequest; //dojo.data.api.Request + }, + command: function(/* string */cmdName,/* (string|number|bool|...)? */cmdContent){ + // summary: + // get/set a command (a name-value pair) + // tags: + // public + // cmdName: string + // The name of the command + // cmdContent: anything + // The content of the command + // returns: + // The content of the command if cmdContent is undefined + var cmds = this.__cmds; + if(cmdContent === null){ + delete cmds[cmdName]; + }else if(typeof cmdContent !== "undefined"){ + cmds[cmdName] = cmdContent; + } + return cmds[cmdName]; //anything + }, + onCommandLoad: function(/* string */response, /* keywordArgs */userRequest){ + // summary: + // When the server gives back *response* for the commands, you can do something here. + // tags: + // callback extension + // response: string + // server response + // userRequest: [in|out] dojo.data.api.Request + // The request object for *fetch*. You can modify this object according to the *response* + // so as to change the behavior of *fetch* + this._onUserCommandLoad(this.__cmds, userRequest, response); + }, + onCommandError: function(error){ + // summary: + // handle errors when sending commands. + // tags: + // callback extension + // error: Error + console.log(error); + throw error; + } + }); + + return { + _StoreLayer: _StoreLayer, + _ServerSideLayer: _ServerSideLayer, + wrap: ns.wrap + }; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/exporter/CSVWriter.js b/js/dojo/dojox/grid/enhanced/plugins/exporter/CSVWriter.js new file mode 100644 index 0000000..44006e2 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/exporter/CSVWriter.js @@ -0,0 +1,88 @@ +//>>built +define("dojox/grid/enhanced/plugins/exporter/CSVWriter", [ + "dojo/_base/declare", + "dojo/_base/array", + "./_ExportWriter", + "../Exporter" +], function(declare, array, _ExportWriter, Exporter){ + +Exporter.registerWriter("csv", "dojox.grid.enhanced.plugins.exporter.CSVWriter"); + +return declare("dojox.grid.enhanced.plugins.exporter.CSVWriter", _ExportWriter, { + // summary: + // Export grid to CSV format. + _separator: ',', + + _newline: "\r\n", + + constructor: function(/* object? */writerArgs){ + // summary: + // CSV default separator is ','. + // But we can also use our own. + // writerArgs: object? + // {separator:'...'} + if(writerArgs){ + this._separator = writerArgs.separator ? writerArgs.separator : this._separator; + this._newline = writerArgs.newline ? writerArgs.newline : this._newline; + } + this._headers = []; + this._dataRows = []; + }, + + _formatCSVCell: function(/* string */cellValue){ + // summary: + // Format cell value to follow CSV standard. + // See: http://en.wikipedia.org/wiki/Comma-separated_values + // tags: + // private + // cellValue: string + // The value in a cell. + // returns: + // The formatted content of a cell + if(cellValue === null || cellValue === undefined){ + return ''; + } + var result = String(cellValue).replace(/"/g, '""'); + if(result.indexOf(this._separator) >= 0 || result.search(/[" \t\r\n]/) >= 0){ + result = '"' + result + '"'; + } + return result; //String + }, + + beforeContentRow: function(/* object */arg_obj){ + // summary: + // Overrided from _ExportWriter + var row = [], + func = this._formatCSVCell; + array.forEach(arg_obj.grid.layout.cells, function(cell){ + //We are not interested in indirect selectors and row indexes. + if(!cell.hidden && array.indexOf(arg_obj.spCols,cell.index) < 0){ + //We only need data here, not html + row.push(func(this._getExportDataForCell(arg_obj.rowIndex, arg_obj.row, cell, arg_obj.grid))); + } + }, this); + this._dataRows.push(row); + //We do not need to go into the row. + return false; //Boolean + }, + + handleCell: function(/* object */arg_obj){ + // summary: + // Overrided from _ExportWriter + var cell = arg_obj.cell; + if(arg_obj.isHeader && !cell.hidden && array.indexOf(arg_obj.spCols,cell.index) < 0){ + this._headers.push(cell.name || cell.field); + } + }, + + toString: function(){ + // summary: + // Overrided from _ExportWriter + var result = this._headers.join(this._separator); + for(var i = this._dataRows.length - 1; i >= 0; --i){ + this._dataRows[i] = this._dataRows[i].join(this._separator); + } + return result + this._newline + this._dataRows.join(this._newline); //String + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/exporter/TableWriter.js b/js/dojo/dojox/grid/enhanced/plugins/exporter/TableWriter.js new file mode 100644 index 0000000..0ea2c74 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/exporter/TableWriter.js @@ -0,0 +1,160 @@ +//>>built +define("dojox/grid/enhanced/plugins/exporter/TableWriter", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/dom-geometry", + "./_ExportWriter", + "../Exporter" +], function(declare, array, domGeometry, _ExportWriter, Exporter){ + +Exporter.registerWriter("table", "dojox.grid.enhanced.plugins.exporter.TableWriter"); + +return declare("dojox.grid.enhanced.plugins.exporter.TableWriter", _ExportWriter, { + // summary: + // Export grid to HTML table format. Primarily used by Printer plugin. + constructor: function(/* object? */writerArgs){ + // summary: + // The generated table only defines the col/rowspan, height and width of + // all the cells in the style attribute, no other attributes + // (like border, cellspacing, etc.) are used. + // Users can define these attributes in the writerArgs object, like: + // {table:"border='border'",thead:"cellspacing='3'"} + this._viewTables = []; + this._tableAttrs = writerArgs || {}; + }, + + _getTableAttrs: function(/* string */tagName){ + // summary: + // Get html attribute string for the given kind of tag. + // tags: + // private + // tagName: string + // An html tag name + // returns: + // The well formatted attributes for the given html table.tag + var attrs = this._tableAttrs[tagName] || ''; + //To ensure the attribute list starts with a space + if(attrs && attrs[0] != ' '){ + attrs = ' ' + attrs; + } + return attrs; //String + }, + + _getRowClass: function(/* object */arg_obj){ + // summary: + // Get CSS class string for a row + // tags: + // private + return arg_obj.isHeader ? " grid_header" : [//String + " grid_row grid_row_", + arg_obj.rowIdx + 1, + arg_obj.rowIdx % 2 ? " grid_even_row" : " grid_odd_row" + ].join(''); + }, + + _getColumnClass: function(/* object */arg_obj){ + // summary: + // Get CSS class string for a column + // tags: + // private + var col_idx = arg_obj.cell.index + arg_obj.colOffset + 1; + return [" grid_column grid_column_", col_idx,//String + col_idx % 2 ? " grid_odd_column" : " grid_even_column"].join(''); + }, + + beforeView: function(/* object */arg_obj){ + // summary: + // Overrided from _ExportWriter + var viewIdx = arg_obj.viewIdx, + table = this._viewTables[viewIdx], + height, width = domGeometry.getMarginBox(arg_obj.view.contentNode).w; + if(!table){ + var left = 0; + for(var i = 0; i < viewIdx; ++i){ + left += this._viewTables[i]._width; + } + table = this._viewTables[viewIdx] = ['<div class="grid_view" style="position: absolute; top: 0; ', + domGeometry.isBodyLtr() ? 'left' : 'right', ':', left, + 'px;">']; + } + table._width = width; + if(arg_obj.isHeader){ + height = domGeometry.getContentBox(arg_obj.view.headerContentNode).h; + }else{ + var rowNode = arg_obj.grid.getRowNode(arg_obj.rowIdx); + if(rowNode){ + height = domGeometry.getContentBox(rowNode).h; + }else{ + //This row has not been loaded from store, so we should estimate it's height. + height = arg_obj.grid.scroller.averageRowHeight; + } + } + table.push('<table class="', this._getRowClass(arg_obj), + '" style="table-layout:fixed; height:', height, 'px; width:', width, 'px;" ', + 'border="0" cellspacing="0" cellpadding="0" ', + this._getTableAttrs("table"), + '><tbody ', this._getTableAttrs('tbody'), '>'); + return true; //Boolean + }, + + afterView: function(/* object */arg_obj){ + // summary: + // Overrided from _ExportWriter + this._viewTables[arg_obj.viewIdx].push('</tbody></table>'); + }, + + beforeSubrow: function(/* object */arg_obj){ + // summary: + // Overrided from _ExportWriter + this._viewTables[arg_obj.viewIdx].push('<tr', this._getTableAttrs('tr'), '>'); + return true; //Boolean + }, + + afterSubrow: function(/* object */arg_obj){ + // summary: + // Overrided from _ExportWriter + this._viewTables[arg_obj.viewIdx].push('</tr>'); + }, + + handleCell: function(/* object */arg_obj){ + // summary: + // Overrided from _ExportWriter + var cell = arg_obj.cell; + if(cell.hidden || array.indexOf(arg_obj.spCols, cell.index) >= 0){ + //We are not interested in indirect selectors and row indexes. + return; + } + var cellTagName = arg_obj.isHeader ? 'th' : 'td', + attrs = [cell.colSpan ? ' colspan="' + cell.colSpan + '"' : '', + cell.rowSpan ? ' rowspan="' + cell.rowSpan + '"' : '', + ' style="width: ', domGeometry.getContentBox(cell.getHeaderNode()).w, 'px;"', + this._getTableAttrs(cellTagName), + ' class="', this._getColumnClass(arg_obj), '"'].join(''), + table = this._viewTables[arg_obj.viewIdx]; + table.push('<', cellTagName, attrs, '>'); + if(arg_obj.isHeader){ + table.push(cell.name || cell.field); + } else{ + table.push(this._getExportDataForCell(arg_obj.rowIdx, arg_obj.row, cell, arg_obj.grid)); + } + table.push('</', cellTagName, '>'); + }, + + afterContent: function(){ + // summary: + // Overrided from _ExportWriter + array.forEach(this._viewTables, function(table){ + table.push('</div>'); + }); + }, + + toString: function(){ + // summary: + // Overrided from _ExportWriter + var viewsHTML = array.map(this._viewTables, function(table){ //String + return table.join(''); + }).join(''); + return ['<div style="position: relative;">', viewsHTML, '</div>'].join(''); + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/exporter/_ExportWriter.js b/js/dojo/dojox/grid/enhanced/plugins/exporter/_ExportWriter.js new file mode 100644 index 0000000..14bd39f --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/exporter/_ExportWriter.js @@ -0,0 +1,269 @@ +//>>built +define("dojox/grid/enhanced/plugins/exporter/_ExportWriter", [ + "dojo/_base/declare" +], function(declare){ +//require Exporter here, so the implementations only need to require this file, +//and the users only need to require the implementation file. + +return declare("dojox.grid.enhanced.plugins.exporter._ExportWriter", null, { + // summary: + // This is an abstract class for all kinds of writers used in the Exporter plugin. + // It utilizes the strategy pattern to break the export work into several stages, + // and provide interfaces for all of them. + // Implementations might choose some of the functions in this class to override, + // thus providing their own functionalities. + // The Exporter will go through the grid line by line. So in every line, all the Views + // will be reached, and the header line is only handled once. + // An *argObj* object is passed to most functions of this class. + // It carries context arguments that make sense when they are called. + +/*===== + argObj: { + // grid: EnhancedGrid + // The grid object we are now handling. + grid: null, + + // isHeader: bool + // Indicating which context we're handling, header or content. + isHeader: true, + + // view: _View + // Reference to the current _View object. + view: null, + + // viewIdx: int + // The index of the current _View object in the views array. + // If the grid does not have any rowselector view, it conforms to the index + // in the _ViewManager.views. + viewIdx: -1, + + // subrow: _View.structure.cells[i] + // Reference to the current subrow. + // A subrow describe the innter structure of a row in a view, it's an array of cells + subrow: null, + + // subrowIdx: int + // The index of the current subrow in the subrow array: _View.structure.cells. + subrowIdx: -1, + + // cell: dojox.grid.__CellDef + // Reference to the current cell. + cell: null, + + //cellIdx: int + // The index of the current cell in the current subrow. + // It's different from cell.index, which is the index in the whole line. + cellIdx: -1, + + //row: item + // The current row of data (logically), a.k.a.: current item. + row: null, + + //rowIdx: int + // The index of the current row (item). + rowIdx: -1, + + // spCols: Array<int> + // An array of special column indexes(flat,not regarding structure). + // Special columns are typically attached to grid as a kind of UI facility + // by the grid widget, instead of some real data. + // For example, indirect selectors and row indexers. + // Users can choose to export it or not. + spCols: [], + + // colOffset: int + // If the grid has a _RowSelector view or something else, this view will NOT be + // passed to the user in argObj. So the column index (cell.index) will appear shifted + // (start from 1 instead of 0). This colOffset is provided to remove this shift. + // usage: + // var correctColIndex = argObj.cell.index + argObj.colOffset; + colOffset: 0 + }, +=====*/ + + constructor: function(/* object? */writerArgs){ + // summary: + // Writer initializations goes here. + // writerArgs: object? + // Any implementation of this class might accept a writerArgs object (optional), + // which contains some writer-specific arguments given by the user. + }, + _getExportDataForCell: function(rowIndex, rowItem, cell, grid){ + var data = (cell.get || grid.get).call(cell, rowIndex, rowItem); + if(this.formatter){ + return this.formatter(data, cell, rowIndex, rowItem); + }else{ + return data; + } + }, + beforeHeader: function(/* EnhancedGrid */grid){ + // summary: + // We are going to start the travel in the grid. + // Is there anything we should do now? + // tags: + // protected extension + // return: + // true: go on hanling the header row and then call afterHeader. + // false: skip the header row, won't call afterHeader. + return true; //Boolean + }, + afterHeader: function(){ + // summary: + // The header line has been handled. + // tags: + // protected extension + // returns: + // undefined + }, + beforeContent: function(/* Array */items){ + // summary: + // We are ready to go through all the contents(items). + // tags: + // protected extension + // items: + // All the items fetched from the store + // return: + // true: go on handling the contents and then call afterContent. + // false: skip all the contents, won't call afterContent. + return true; //Boolean + }, + afterContent: function(){ + // summary: + // We have finished the entire grid travel. + // Do some clean up work if you need to. + // tags: + // protected extension + // returns: + // undefined + }, + beforeContentRow: function(/* object */argObj){ + // summary: + // Before handling a line of data (not header). + // tags: + // protected extension + // argObj: + // An object with at least the following context properties available: + // { + // grid,isHeader, + // row,rowIdx, + // spCols + // } + // return: + // true: go on handling the current data row and then call afterContentRow. + // false: skip the current data row, won't call afterContentRow. + return true; //Boolean + }, + afterContentRow: function(/* object */argObj){ + // summary: + // After handling a line of data (not header). + // tags: + // protected extension + // argObj: + // An object with at least the following context properties available: + // { + // grid,isHeader, + // row,rowIdx, + // spCols + // } + // returns: + // undefined + }, + beforeView: function(/* object */argObj){ + // summary: + // Before handling a view. + // tags: + // protected extension + // argObj: + // An object with at least the following context properties available: + // { + // grid,isHeader, + // view,viewIdx, + // spCols(if isHeader==false) + // } + // return: + // true: go on handling the current view and then call afterView. + // false: skip the current view, won't call afterView. + return true; //Boolean + }, + afterView: function(/* object */argObj){ + // summary: + // After handling a view. + // tags: + // protected extension + // argObj: + // An object with at least the following context properties available: + // { + // grid,isHeader, + // view,viewIdx, + // spCols(if isHeader==false) + // } + // tags: + // protected extension + // returns: + // undefined + }, + beforeSubrow: function(/* object */argObj){ + // summary: + // Before handling a subrow in a line (defined in the grid structure). + // tags: + // protected extension + // argObj: + // An object with at least the following context properties available: + // { + // grid,isHeader, + // row,rowIdx, + // view,viewIdx, + // subrow,subrowIdx, + // spCols(if isHeader==false) + // } + // return: + // true: go on handling the current subrow and then call afterSubrow. + // false: skip the current subrow, won't call afterSubrow. + return true; //Boolean + }, + afterSubrow: function(/* object */argObj){ + // summary: + // Before handling a subrow in a line (defined in the grid structure). + // tags: + // protected extension + // argObj: + // An object with at least the following context properties available: + // { + // grid,isHeader, + // row,rowIdx, + // view,viewIdx, + // subrow,subrowIdx, + // spCols(if isHeader==false) + // } + // returns: + // undefined + }, + handleCell: function(/* object */argObj){ + // summary: + // Handle a header cell or data cell. + // tags: + // protected extension + // argObj: + // An object with at least the following context properties available: + // { + // grid,isHeader, + // row,rowIdx, + // view,viewIdx, + // subrow,subrowIdx, + // cell,cellIdx, + // spCols(if isHeader==false) + // } + // returns: + // undefined + }, + toString: function(){ + // summary: + // Export to a string. + // tags: + // protected extension + // returns: + // The exported result string. + return ''; //String + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/ClearFilterConfirm.js b/js/dojo/dojox/grid/enhanced/plugins/filter/ClearFilterConfirm.js new file mode 100644 index 0000000..2a63dcb --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/ClearFilterConfirm.js @@ -0,0 +1,46 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/ClearFilterConfirm", [ + "dojo/_base/declare", + "dojo/cache", + "dijit/_Widget", + "dijit/_TemplatedMixin", + "dijit/_WidgetsInTemplateMixin" +], function(declare, cache, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin){ + +return declare("dojox.grid.enhanced.plugins.filter.ClearFilterConfirm", + [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], { + // summary: + // The UI for user to confirm the operation of clearing filter. + templateString: cache("dojox.grid", "enhanced/templates/ClearFilterConfirmPane.html"), + + widgetsInTemplate: true, + + plugin: null, + + postMixInProperties: function(){ + var nls = this.plugin.nls; + this._clearBtnLabel = nls["clearButton"]; + this._cancelBtnLabel = nls["cancelButton"]; + this._clearFilterMsg = nls["clearFilterMsg"]; + }, + + postCreate: function(){ + this.inherited(arguments); + this.cancelBtn.domNode.setAttribute("aria-label", this.plugin.nls["waiCancelButton"]); + this.clearBtn.domNode.setAttribute("aria-label", this.plugin.nls["waiClearButton"]); + }, + + uninitialize: function(){ + this.plugin = null; + }, + + _onCancel: function(){ + this.plugin.clearFilterDialog.hide(); + }, + + _onClear: function(){ + this.plugin.clearFilterDialog.hide(); + this.plugin.filterDefDialog.clearFilter(this.plugin.filterDefDialog._clearWithoutRefresh); + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/FilterBar.js b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterBar.js new file mode 100644 index 0000000..422c56a --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterBar.js @@ -0,0 +1,386 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/FilterBar", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/connect", + "dojo/_base/lang", + "dojo/_base/sniff", + "dojo/_base/event", + "dojo/_base/html", + "dojo/_base/window", + "dojo/cache", + "dojo/query", + "dijit/_Widget", + "dijit/_TemplatedMixin", + "dijit/_WidgetsInTemplateMixin", + "dojo/fx", + "dojo/_base/fx", + "dojo/string", + "dijit/focus" +], function(declare, array, connect, lang, has, event, html, win, cache, query, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, fx, baseFx, string, dijitFocus){ + +var _focusClass = "dojoxGridFBarHover", + _filteredClass = "dojoxGridFBarFiltered", + _stopEvent = function(evt){ + try{ + if(evt && evt.preventDefault){ + event.stop(evt); + } + }catch(e){} + }; + +return declare("dojox.grid.enhanced.plugins.filter.FilterBar", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{ + // summary: + // The filter bar UI. + templateString: cache("dojox.grid","enhanced/templates/FilterBar.html"), + + widgetsInTemplate: true, + + _timeout_statusTooltip: 300, + + _handle_statusTooltip: null, + + _curColIdx: -1, + + plugin: null, + + postMixInProperties: function(){ + var plugin = this.plugin; + var nls = plugin.nls; + this._filterBarDefBtnLabel = nls["filterBarDefButton"]; + this._filterBarClearBtnLabel = nls["filterBarClearButton"]; + this._closeFilterBarBtnLabel = nls["closeFilterBarBtn"]; + var itemsName = plugin.args.itemsName || nls["defaultItemsName"]; + this._noFilterMsg = string.substitute(nls["filterBarMsgNoFilterTemplate"], ["", itemsName]); + + var t = this.plugin.args.statusTipTimeout; + if(typeof t == 'number'){ + this._timeout_statusTooltip = t; + } + + var g = plugin.grid; + g.showFilterBar = lang.hitch(this, "showFilterBar"); + g.toggleFilterBar = lang.hitch(this, "toggleFilterBar"); + g.isFilterBarShown = lang.hitch(this, "isFilterBarShown"); + }, + postCreate: function(){ + this.inherited(arguments); + if(!this.plugin.args.closeFilterbarButton){ + html.style(this.closeFilterBarButton.domNode, "display", "none"); + } + var _this = this, + g = this.plugin.grid, + old_func = this.oldGetHeaderHeight = lang.hitch(g,g._getHeaderHeight); + + this.placeAt(g.viewsHeaderNode, "after"); + this.connect(this.plugin.filterDefDialog, "showDialog", "_onShowFilterDefDialog"); + this.connect(this.plugin.filterDefDialog, "closeDialog", "_onCloseFilterDefDialog"); + this.connect(g.layer("filter"), "onFiltered", this._onFiltered); + + this.defineFilterButton.domNode.title = this.plugin.nls["filterBarDefButton"]; + if(html.hasClass(win.body(), "dijit_a11y")){ + this.defineFilterButton.set("label", this.plugin.nls["a11yFilterBarDefButton"]); + } + this.connect(this.defineFilterButton.domNode, "click", _stopEvent); + this.connect(this.clearFilterButton.domNode, "click", _stopEvent); + this.connect(this.closeFilterBarButton.domNode, "click", _stopEvent); + + this.toggleClearFilterBtn(true); + this._initAriaInfo(); + + //Hack the header height to include filter bar height; + g._getHeaderHeight = function(){ + return old_func() + html.marginBox(_this.domNode).h; + }; + //Define an area to make focusManager handle all the navigation stuff + g.focus.addArea({ + name: "filterbar", + onFocus: lang.hitch(this, this._onFocusFilterBar, false), + onBlur: lang.hitch(this, this._onBlurFilterBar) + }); + g.focus.placeArea("filterbar","after","header"); + }, + uninitialize: function(){ + var g = this.plugin.grid; + g._getHeaderHeight = this.oldGetHeaderHeight; + g.focus.removeArea("filterbar"); + this.plugin = null; + }, + isFilterBarShown: function(){ + return html.style(this.domNode, "display") != "none"; + }, + showFilterBar: function(toShow, useAnim, animArgs){ + var g = this.plugin.grid; + if(useAnim){ + if(Boolean(toShow) == this.isFilterBarShown()){ return; } + animArgs = animArgs || {}; + var anims = [], defaultDuration = 500; + anims.push(fx[toShow ? "wipeIn" : "wipeOut"](lang.mixin({ + "node": this.domNode, + "duration": defaultDuration + }, animArgs))); + var curHeight = g.views.views[0].domNode.offsetHeight; + var prop = { + "duration": defaultDuration, + "properties": { + "height": { + "end": lang.hitch(this, function(){ + var barHeight = this.domNode.scrollHeight; + if(has("ff")){ + barHeight -= 2; + } + return toShow ? (curHeight - barHeight) : (curHeight + barHeight); + }) + } + } + }; + array.forEach(g.views.views, function(view){ + anims.push(baseFx.animateProperty(lang.mixin({ + "node": view.domNode + }, prop, animArgs)), baseFx.animateProperty(lang.mixin({ + "node": view.scrollboxNode + }, prop, animArgs))); + }); + anims.push(baseFx.animateProperty(lang.mixin({ + "node": g.viewsNode + }, prop, animArgs))); + fx.combine(anims).play(); + }else{ + html.style(this.domNode, "display", toShow ? "" : "none"); + g.update(); + } + }, + toggleFilterBar: function(useAnim, animArgs){ + this.showFilterBar(!this.isFilterBarShown(), useAnim, animArgs); + }, + getColumnIdx: function(/* int */coordX){ + var headers = query("[role='columnheader']", this.plugin.grid.viewsHeaderNode); + var idx = -1; + for(var i = headers.length - 1; i >= 0; --i){ + var coord = html.position(headers[i]); + if(coordX >= coord.x && coordX < coord.x + coord.w){ + idx = i; + break; + } + } + if(idx >= 0 && this.plugin.grid.layout.cells[idx].filterable !== false){ + return idx; //Integer + }else{ + return -1; //Integer + } + }, + toggleClearFilterBtn: function(toHide){ + html.style(this.clearFilterButton.domNode, "display", toHide ? "none" : ""); + }, + _closeFilterBar: function(e){ + _stopEvent(e); + var rulesCnt = this.plugin.filterDefDialog.getCriteria(); + if(rulesCnt){ + var handle = connect.connect(this.plugin.filterDefDialog, "clearFilter", this, function(){ + this.showFilterBar(false, true); + connect.disconnect(handle); + }); + this._clearFilterDefDialog(e); + }else{ + this.showFilterBar(false, true); + } + }, + + _showFilterDefDialog: function(e){ + _stopEvent(e); + this.plugin.filterDefDialog.showDialog(this._curColIdx); + this.plugin.grid.focus.focusArea("filterbar"); + }, + _clearFilterDefDialog: function(e){ + _stopEvent(e); + this.plugin.filterDefDialog.onClearFilter(); + this.plugin.grid.focus.focusArea("filterbar"); + }, + _onEnterButton: function(e){ + //If mouse is hovering the btn, which means the user is about to click, + //we should not show status tip on the btn! + this._onBlurFilterBar(); + _stopEvent(e); + }, + _onMoveButton: function(e){ + this._onBlurFilterBar(); + }, + _onLeaveButton: function(e){ + this._leavingBtn = true; + }, + _onShowFilterDefDialog: function(/* Integer */colIdx){ + if(typeof colIdx == "number"){ + this._curColIdx = colIdx; + } + this._defPaneIsShown = true; + }, + _onCloseFilterDefDialog: function(){ + this._defPaneIsShown = false; + //Do not remember what column are we on, so clicking the btn will show 'any column' + this._curColIdx = -1; + dijitFocus.focus(this.defineFilterButton.domNode); + }, + _onClickFilterBar: function(/* event */e){ + _stopEvent(e); + this._clearStatusTipTimeout(); + this.plugin.grid.focus.focusArea("filterbar"); + this.plugin.filterDefDialog.showDialog(this.getColumnIdx(e.clientX)); + }, + _onMouseEnter: function(/* event */e){ + this._onFocusFilterBar(true, null); + this._updateTipPosition(e); + this._setStatusTipTimeout(); + }, + _onMouseMove: function(/* event */e){ + if(this._leavingBtn){ + this._onFocusFilterBar(true, null); + this._leavingBtn = false; + } + if(this._isFocused){ + this._setStatusTipTimeout(); + this._highlightHeader(this.getColumnIdx(e.clientX)); + if(this._handle_statusTooltip){ + this._updateTipPosition(e); + } + } + }, + _onMouseLeave: function(e){ + this._onBlurFilterBar(); + }, + _updateTipPosition: function(evt){ + this._tippos = { + x: evt.pageX, + y: evt.pageY + }; + }, + _onFocusFilterBar: function(highlightOnly, evt, step){ + if(!this.isFilterBarShown()){ + return false; + } + this._isFocused = true; + html.addClass(this.domNode,_focusClass); + if(!highlightOnly){ + var hasFilter = html.style(this.clearFilterButton.domNode, "display") !== "none"; + var hasCloseButton = html.style(this.closeFilterBarButton.domNode, "display") !== "none"; + if(typeof this._focusPos == "undefined"){ + if(step > 0){ + this._focusPos = 0; + }else{ + if(hasCloseButton){ + this._focusPos = 1; + }else{ + this._focusPos = 0; + } + if(hasFilter){ + ++this._focusPos; + } + } + } + if(this._focusPos === 0){ + dijitFocus.focus(this.defineFilterButton.focusNode); + }else if(this._focusPos === 1 && hasFilter){ + dijitFocus.focus(this.clearFilterButton.focusNode); + }else{ + dijitFocus.focus(this.closeFilterBarButton.focusNode); + } + } + _stopEvent(evt); + return true; + }, + _onBlurFilterBar: function(evt, step){ + if(this._isFocused){ + this._isFocused = false; + html.removeClass(this.domNode,_focusClass); + this._clearStatusTipTimeout(); + this._clearHeaderHighlight(); + } + var toBlur = true; + if(step){ + var buttonCount = 3; + if(html.style(this.closeFilterBarButton.domNode, "display") === "none"){ + --buttonCount; + } + if(html.style(this.clearFilterButton.domNode, "display") === "none"){ + --buttonCount; + } + if(buttonCount == 1){ + delete this._focusPos; + }else{ + var current = this._focusPos; + for(var next = current + step; next < 0; next += buttonCount){} + next %= buttonCount; + if((step > 0 && next < current) || (step < 0 && next > current)){ + delete this._focusPos; + }else{ + this._focusPos = next; + toBlur = false; + } + } + } + return toBlur; + }, + _onFiltered: function(/* int */filteredSize,/* int */originSize){ + var p = this.plugin, + itemsName = p.args.itemsName || p.nls["defaultItemsName"], + msg = "", g = p.grid, + filterLayer = g.layer("filter"); + if(filterLayer.filterDef()){ + msg = string.substitute(p.nls["filterBarMsgHasFilterTemplate"], [filteredSize, originSize, itemsName]); + html.addClass(this.domNode, _filteredClass); + }else{ + msg = string.substitute(p.nls["filterBarMsgNoFilterTemplate"], [originSize, itemsName]); + html.removeClass(this.domNode, _filteredClass); + } + this.statusBarNode.innerHTML = msg; + this._focusPos = 0; + }, + _initAriaInfo: function(){ + this.defineFilterButton.domNode.setAttribute("aria-label", this.plugin.nls["waiFilterBarDefButton"]); + this.clearFilterButton.domNode.setAttribute("aria-label", this.plugin.nls["waiFilterBarClearButton"]); + }, + _isInColumn: function(/* int */mousePos_x, /* domNode */headerNode, /* int */colIndex){ + var coord = html.position(headerNode); + return mousePos_x >= coord.x && mousePos_x < coord.x + coord.w; + }, + _setStatusTipTimeout: function(){ + this._clearStatusTipTimeout(); + if(!this._defPaneIsShown){ + this._handle_statusTooltip = setTimeout(lang.hitch(this,this._showStatusTooltip),this._timeout_statusTooltip); + } + }, + _clearStatusTipTimeout: function(){ + clearTimeout(this._handle_statusTooltip); + this._handle_statusTooltip = null; + }, + _showStatusTooltip: function(){ + this._handle_statusTooltip = null; + this.plugin.filterStatusTip.showDialog(this._tippos.x, this._tippos.y, this.getColumnIdx(this._tippos.x)); + }, + _highlightHeader: function(/* int */colIdx){ + if(colIdx != this._previousHeaderIdx){ + var g = this.plugin.grid, + cell = g.getCell(this._previousHeaderIdx); + if(cell){ + html.removeClass(cell.getHeaderNode(), "dojoxGridCellOver"); + } + cell = g.getCell(colIdx); + if(cell){ + html.addClass(cell.getHeaderNode(), "dojoxGridCellOver"); + } + this._previousHeaderIdx = colIdx; + } + }, + _clearHeaderHighlight: function(){ + if(typeof this._previousHeaderIdx != "undefined"){ + var g = this.plugin.grid, + cell = g.getCell(this._previousHeaderIdx); + if(cell){ + g.onHeaderCellMouseOut({ + cellNode: cell.getHeaderNode() + }); + } + delete this._previousHeaderIdx; + } + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/FilterBuilder.js b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterBuilder.js new file mode 100644 index 0000000..63297c0 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterBuilder.js @@ -0,0 +1,81 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/FilterBuilder", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "./_FilterExpr" +], function(declare, array, lang, exprs){ + +var bdr = function(opCls){ + return lang.partial(function(cls,operands){ + return new exprs[cls](operands); + },opCls); + }, + bdr_not = function(opCls){ + return lang.partial(function(cls,operands){ + return new exprs.LogicNOT(new exprs[cls](operands)); + },opCls); + }; +return declare("dojox.grid.enhanced.plugins.filter.FilterBuilder", null, { + // summary: + // Create filter expression from a JSON object. + buildExpression: function(def){ + if("op" in def){ + return this.supportedOps[def.op.toLowerCase()](array.map(def.data, this.buildExpression, this)); + }else{ + var args = lang.mixin(this.defaultArgs[def.datatype], def.args || {}); + return new this.supportedTypes[def.datatype](def.data, def.isColumn, args); + } + }, + supportedOps: { + // summary: + // The builders of all supported operations + "equalto": bdr("EqualTo"), + "lessthan": bdr("LessThan"), + "lessthanorequalto": bdr("LessThanOrEqualTo"), + "largerthan": bdr("LargerThan"), + "largerthanorequalto": bdr("LargerThanOrEqualTo"), + "contains": bdr("Contains"), + "startswith": bdr("StartsWith"), + "endswith": bdr("EndsWith"), + "notequalto": bdr_not("EqualTo"), + "notcontains": bdr_not("Contains"), + "notstartswith": bdr_not("StartsWith"), + "notendswith": bdr_not("EndsWith"), + "isempty": bdr("IsEmpty"), + "range": function(operands){ + return new exprs.LogicALL( + new exprs.LargerThanOrEqualTo(operands.slice(0,2)), + new exprs.LessThanOrEqualTo(operands[0], operands[2]) + ); + }, + "logicany": bdr("LogicANY"), + "logicall": bdr("LogicALL") + }, + supportedTypes: { + "number": exprs.NumberExpr, + "string": exprs.StringExpr, + "boolean": exprs.BooleanExpr, + "date": exprs.DateExpr, + "time": exprs.TimeExpr + }, + defaultArgs: { + "boolean": { + "falseValue": "false", + "convert": function(dataValue, args){ + var falseValue = args.falseValue; + var trueValue = args.trueValue; + if(lang.isString(dataValue)){ + if(trueValue && dataValue.toLowerCase() == trueValue){ + return true; + } + if(falseValue && dataValue.toLowerCase() == falseValue){ + return false; + } + } + return !!dataValue; + } + } + } +}); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/FilterDefDialog.js b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterDefDialog.js new file mode 100644 index 0000000..7e49153 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterDefDialog.js @@ -0,0 +1,1252 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/FilterDefDialog", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/connect", + "dojo/_base/lang", + "dojo/_base/event", + "dojo/_base/html", + "dojo/_base/sniff", + "dojo/cache", + "dojo/keys", + "dojo/string", + "dojo/window", + "dojo/date/locale", + "./FilterBuilder", + "../Dialog", + "dijit/form/ComboBox", + "dijit/form/TextBox", + "dijit/form/NumberTextBox", + "dijit/form/DateTextBox", + "dijit/form/TimeTextBox", + "dijit/form/Button", + "dijit/layout/AccordionContainer", + "dijit/layout/ContentPane", + "dijit/_Widget", + "dijit/_TemplatedMixin", + "dijit/_WidgetsInTemplateMixin", + "dijit/focus", + "dojox/html/metrics", + "dijit/a11y", + "dijit/Tooltip", + "dijit/form/Select", + "dijit/form/RadioButton", + "dojox/html/ellipsis", + "../../../cells/dijit" +], function(declare, array, connect, lang, event, html, has, cache, keys, string, win, dateLocale, + FilterBuilder, Dialog, ComboBox, TextBox, NumberTextBox, DateTextBox, TimeTextBox, Button, + AccordionContainer, ContentPane, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, dijitFocus, metrics, dijitA11y){ + +var _tabIdxes = { + // summary: + // Define tabindexes for elements in the filter definition dialog + relSelect: 60, + accordionTitle: 70, + removeCBoxBtn: -1, + colSelect: 90, + condSelect: 95, + valueBox: 10, + addCBoxBtn: 20, + filterBtn: 30, + clearBtn: 40, + cancelBtn: 50 + }; +var FilterAccordionContainer = declare("dojox.grid.enhanced.plugins.filter.AccordionContainer", AccordionContainer, { + nls: null, + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + var pane = arguments[0] = child._pane = new ContentPane({ + content: child + }); + this.inherited(arguments); + this._modifyChild(pane); + }, + removeChild: function(child){ + var pane = child, isRemoveByUser = false; + if(child._pane){ + isRemoveByUser = true; + pane = arguments[0] = child._pane; + } + this.inherited(arguments); + if(isRemoveByUser){ + this._hackHeight(false, this._titleHeight); + var children = this.getChildren(); + if(children.length === 1){ + html.style(children[0]._removeCBoxBtn.domNode, "display", "none"); + } + } + pane.destroyRecursive(); + }, + selectChild: function(child){ + if(child._pane){ + arguments[0] = child._pane; + } + this.inherited(arguments); + }, + resize: function(){ + this.inherited(arguments); + array.forEach(this.getChildren(), this._setupTitleDom); + }, + startup: function(){ + if(this._started){ + return; + } + this.inherited(arguments); + if(parseInt(has("ie"), 10) == 7){ + //IE7 will fire a lot of "onresize" event during initialization. + array.some(this._connects, function(cnnt){ + if(cnnt[0][1] == "onresize"){ + this.disconnect(cnnt); + return true; + } + }, this); + } + array.forEach(this.getChildren(), function(child){ + this._modifyChild(child, true); + }, this); + }, + _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){ + // summary: + // Overrides base class method, make left/right button do other things. + if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){ + return; + } + var k = keys, c = e.charOrCode, ltr = html._isBodyLtr(), toNext = null; + if((fromTitle && c == k.UP_ARROW) || (e.ctrlKey && c == k.PAGE_UP)){ + toNext = false; + }else if((fromTitle && c == k.DOWN_ARROW) || (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){ + toNext = true; + }else if(c == (ltr ? k.LEFT_ARROW : k.RIGHT_ARROW)){ + toNext = this._focusOnRemoveBtn ? null : false; + this._focusOnRemoveBtn = !this._focusOnRemoveBtn; + }else if(c == (ltr ? k.RIGHT_ARROW : k.LEFT_ARROW)){ + toNext = this._focusOnRemoveBtn ? true : null; + this._focusOnRemoveBtn = !this._focusOnRemoveBtn; + }else{ + return; + } + if(toNext !== null){ + this._adjacent(toNext)._buttonWidget._onTitleClick(); + } + event.stop(e); + win.scrollIntoView(this.selectedChildWidget._buttonWidget.domNode.parentNode); + if(has("ie")){ + //IE will not show focus indicator if tabIndex is -1 + this.selectedChildWidget._removeCBoxBtn.focusNode.setAttribute("tabIndex", this._focusOnRemoveBtn ? _tabIdxes.accordionTitle : -1); + } + dijitFocus.focus(this.selectedChildWidget[this._focusOnRemoveBtn ? "_removeCBoxBtn" : "_buttonWidget"].focusNode); + }, + _modifyChild: function(child, isFirst){ + if(!child || !this._started){ + return; + } + html.style(child.domNode, "overflow", "hidden"); + child._buttonWidget.connect(child._buttonWidget, "_setSelectedAttr", function(){ + this.focusNode.setAttribute("tabIndex", this.selected ? _tabIdxes.accordionTitle : "-1"); + }); + var _this = this; + child._buttonWidget.connect(child._buttonWidget.domNode, "onclick", function(){ + _this._focusOnRemoveBtn = false; + }); + (child._removeCBoxBtn = new Button({ + label: this.nls.removeRuleButton, + showLabel: false, + iconClass: "dojoxGridFCBoxRemoveCBoxBtnIcon", + tabIndex: _tabIdxes.removeCBoxBtn, + onClick: lang.hitch(child.content, "onRemove"), + onKeyPress: function(e){ + _this._onKeyPress(e, child._buttonWidget.contentWidget); + } + })).placeAt(child._buttonWidget.domNode); + var i, children = this.getChildren(); + if(children.length === 1){ + child._buttonWidget.set("selected", true); + html.style(child._removeCBoxBtn.domNode, "display", "none"); + }else{ + for(i = 0; i < children.length; ++i){ + if(children[i]._removeCBoxBtn){ + html.style(children[i]._removeCBoxBtn.domNode, "display", ""); + } + } + } + this._setupTitleDom(child); + if(!this._titleHeight){ + for(i = 0; i < children.length; ++i){ + if(children[i] != this.selectedChildWidget){ + this._titleHeight = html.marginBox(children[i]._buttonWidget.domNode.parentNode).h; + break; + } + } + } + if(!isFirst){ + this._hackHeight(true, this._titleHeight); + } + }, + _hackHeight: function(/* bool */toGrow,/* int */heightDif){ + var children = this.getChildren(), + dn = this.domNode, h = html.style(dn, "height"); + if(!toGrow){ + dn.style.height = (h - heightDif) + 'px'; + }else if(children.length > 1){ + dn.style.height = (h + heightDif) + 'px'; + }else{ + //Only one rule, no need to do anything. + return; + } + this.resize(); + }, + _setupTitleDom: function(child){ + var w = html.contentBox(child._buttonWidget.titleNode).w; + if(has("ie") < 8){ w -= 8; } + html.style(child._buttonWidget.titleTextNode, "width", w + "px"); + } +}); +var FilterDefPane = declare("dojox.grid.enhanced.plugins.filter.FilterDefPane",[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{ + templateString: cache("dojox.grid","enhanced/templates/FilterDefPane.html"), + widgetsInTemplate: true, + dlg: null, + postMixInProperties: function(){ + this.plugin = this.dlg.plugin; + var nls = this.plugin.nls; + this._addRuleBtnLabel = nls.addRuleButton; + this._cancelBtnLabel = nls.cancelButton; + this._clearBtnLabel = nls.clearButton; + this._filterBtnLabel = nls.filterButton; + this._relAll = nls.relationAll; + this._relAny = nls.relationAny; + this._relMsgFront = nls.relationMsgFront; + this._relMsgTail = nls.relationMsgTail; + }, + postCreate: function(){ + this.inherited(arguments); + this.connect(this.domNode, "onkeypress", "_onKey"); + (this.cboxContainer = new FilterAccordionContainer({ + nls: this.plugin.nls + })).placeAt(this.criteriaPane); + + this._relSelect.set("tabIndex", _tabIdxes.relSelect); + this._addCBoxBtn.set("tabIndex", _tabIdxes.addCBoxBtn); + this._cancelBtn.set("tabIndex", _tabIdxes.cancelBtn); + this._clearFilterBtn.set("tabIndex", _tabIdxes.clearBtn); + this._filterBtn.set("tabIndex", _tabIdxes.filterBtn); + + var nls = this.plugin.nls; + this._relSelect.domNode.setAttribute("aria-label", nls.waiRelAll); + this._addCBoxBtn.domNode.setAttribute("aria-label", nls.waiAddRuleButton); + this._cancelBtn.domNode.setAttribute("aria-label", nls.waiCancelButton); + this._clearFilterBtn.domNode.setAttribute("aria-label", nls.waiClearButton); + this._filterBtn.domNode.setAttribute("aria-label", nls.waiFilterButton); + + this._relSelect.set("value", this.dlg._relOpCls === "logicall" ? "0" : "1"); + }, + uninitialize: function(){ + this.cboxContainer.destroyRecursive(); + this.plugin = null; + this.dlg = null; + }, + _onRelSelectChange: function(val){ + this.dlg._relOpCls = val == "0" ? "logicall" : "logicany"; + this._relSelect.domNode.setAttribute("aria-label", this.plugin.nls[val == "0" ? "waiRelAll" : "waiRelAny"]); + }, + _onAddCBox: function(){ + this.dlg.addCriteriaBoxes(1); + }, + _onCancel: function(){ + this.dlg.onCancel(); + }, + _onClearFilter: function(){ + this.dlg.onClearFilter(); + }, + _onFilter: function(){ + this.dlg.onFilter(); + }, + _onKey: function(e){ + if(e.keyCode == keys.ENTER){ + this.dlg.onFilter(); + } + } +}); +var CriteriaBox = declare("dojox.grid.enhanced.plugins.filter.CriteriaBox",[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{ + templateString: cache("dojox.grid","enhanced/templates/CriteriaBox.html"), + widgetsInTemplate: true, + dlg: null, + postMixInProperties: function(){ + this.plugin = this.dlg.plugin; + this._curValueBox = null; + + var nls = this.plugin.nls; + this._colSelectLabel = nls.columnSelectLabel; + this._condSelectLabel = nls.conditionSelectLabel; + this._valueBoxLabel = nls.valueBoxLabel; + this._anyColumnOption = nls.anyColumnOption; + }, + postCreate: function(){ + var dlg = this.dlg, g = this.plugin.grid; + //Select Column + this._colSelect.set("tabIndex", _tabIdxes.colSelect); + this._colOptions = this._getColumnOptions(); + this._colSelect.addOption([ + {label: this.plugin.nls.anyColumnOption, value: "anycolumn", selected: dlg.curColIdx < 0}, + {value: ""} + ].concat(this._colOptions)); + //Select Condition + this._condSelect.set("tabIndex", _tabIdxes.condSelect); + this._condSelect.addOption(this._getUsableConditions(dlg.getColumnType(dlg.curColIdx))); + this._showSelectOrLabel(this._condSelect, this._condSelectAlt); + + this.connect(g.layout, "moveColumn", "onMoveColumn"); + }, + _getColumnOptions: function(){ + var colIdx = this.dlg.curColIdx >= 0 ? String(this.dlg.curColIdx) : "anycolumn"; + return array.map(array.filter(this.plugin.grid.layout.cells, function(cell){ + return !(cell.filterable === false || cell.hidden); + }), function(cell){ + return { + label: cell.name || cell.field, + value: String(cell.index), + selected: colIdx == String(cell.index) + }; + }); + }, + onMoveColumn: function(){ + var tmp = this._onChangeColumn; + this._onChangeColumn = function(){}; + var option = this._colSelect.get("selectedOptions"); + this._colSelect.removeOption(this._colOptions); + this._colOptions = this._getColumnOptions(); + this._colSelect.addOption(this._colOptions); + var i = 0; + for(; i < this._colOptions.length; ++i){ + if(this._colOptions[i].label == option.label){ + break; + } + } + if(i < this._colOptions.length){ + this._colSelect.set("value", this._colOptions[i].value); + } + var _this = this; + setTimeout(function(){ + _this._onChangeColumn = tmp; + }, 0); + }, + onRemove: function(){ + this.dlg.removeCriteriaBoxes(this); + }, + uninitialize: function(){ + if(this._curValueBox){ + this._curValueBox.destroyRecursive(); + this._curValueBox = null; + } + this.plugin = null; + this.dlg = null; + }, + _showSelectOrLabel: function(sel, alt){ + var options = sel.getOptions(); + if(options.length == 1){ + alt.innerHTML = options[0].label; + html.style(sel.domNode, "display", "none"); + html.style(alt, "display", ""); + }else{ + html.style(sel.domNode, "display", ""); + html.style(alt, "display", "none"); + } + }, + _onChangeColumn: function(val){ + this._checkValidCriteria(); + var type = this.dlg.getColumnType(val); + this._setConditionsByType(type); + this._setValueBoxByType(type); + this._updateValueBox(); + }, + _onChangeCondition: function(val){ + this._checkValidCriteria(); + var f = (val == "range"); + if(f ^ this._isRange){ + this._isRange = f; + this._setValueBoxByType(this.dlg.getColumnType(this._colSelect.get("value"))); + } + this._updateValueBox(); + }, + _updateValueBox: function(cond){ + this._curValueBox.set("disabled", this._condSelect.get("value") == "isempty"); + }, + _checkValidCriteria: function(){ + // summary: + // Check whether the given criteria box is completed. If it is, mark it. + setTimeout(lang.hitch(this, function(){ + this.updateRuleTitle(); + this.dlg._updatePane(); + }),0); + }, + _createValueBox: function(/* widget constructor */cls,/* object */arg){ + // summary: + // Create a value input box with given class and arguments + var func = lang.hitch(arg.cbox, "_checkValidCriteria"); + return new cls(lang.mixin(arg,{ + tabIndex: _tabIdxes.valueBox, + onKeyPress: func, + onChange: func, + "class": "dojoxGridFCBoxValueBox" + })); + }, + _createRangeBox: function(/* widget constructor */cls,/* object */arg){ + // summary: + // Create a DIV containing 2 input widgets, which represents a range, with the given class and arguments + var func = lang.hitch(arg.cbox, "_checkValidCriteria"); + lang.mixin(arg,{ + tabIndex: _tabIdxes.valueBox, + onKeyPress: func, + onChange: func + }); + var div = html.create("div", {"class": "dojoxGridFCBoxValueBox"}), + start = new cls(arg), + txt = html.create("span", {"class": "dojoxGridFCBoxRangeValueTxt", "innerHTML": this.plugin.nls.rangeTo}), + end = new cls(arg); + html.addClass(start.domNode, "dojoxGridFCBoxStartValue"); + html.addClass(end.domNode, "dojoxGridFCBoxEndValue"); + div.appendChild(start.domNode); + div.appendChild(txt); + div.appendChild(end.domNode); + div.domNode = div; + //Mock functions for set and get (in place of the old attr function) + div.set = function(dummy, args){ + if(lang.isObject(args)){ + start.set("value", args.start); + end.set("value", args.end); + } + }; + div.get = function(){ + var s = start.get("value"), + e = end.get("value"); + return s && e ? {start: s, end: e} : ""; + }; + return div; + }, + changeCurrentColumn: function(/* bool */selectCurCol){ + var colIdx = this.dlg.curColIdx; + //Re-populate the columns in case some of them are set to hidden. + this._colSelect.removeOption(this._colOptions); + this._colOptions = this._getColumnOptions(); + this._colSelect.addOption(this._colOptions); + this._colSelect.set('value', colIdx >= 0 ? String(colIdx) : "anycolumn"); + this.updateRuleTitle(true); + }, + curColumn: function(){ + return this._colSelect.getOptions(this._colSelect.get("value")).label; + }, + curCondition: function(){ + return this._condSelect.getOptions(this._condSelect.get("value")).label; + }, + curValue: function(){ + var cond = this._condSelect.get("value"); + if(cond == "isempty"){return "";} + return this._curValueBox ? this._curValueBox.get("value") : ""; + }, + save: function(){ + if(this.isEmpty()){ + return null; + } + var colIdx = this._colSelect.get("value"), + type = this.dlg.getColumnType(colIdx), + value = this.curValue(), + cond = this._condSelect.get("value"); + return { + "column": colIdx, + "condition": cond, + "value": value, + "formattedVal": this.formatValue(type, cond, value), + "type": type, + "colTxt": this.curColumn(), + "condTxt": this.curCondition() + }; + }, + load: function(obj){ + var tmp = [ + this._onChangeColumn, + this._onChangeCondition + ]; + this._onChangeColumn = this._onChangeCondition = function(){}; + if(obj.column){ + this._colSelect.set("value", obj.column); + } + if(obj.condition){ + this._condSelect.set("value", obj.condition); + } + if(obj.type){ + this._setValueBoxByType(obj.type); + }else{ + obj.type = this.dlg.getColumnType(this._colSelect.get("value")); + } + var value = obj.value || ""; + if(value || (obj.type != "date" && obj.type != "time")){ + this._curValueBox.set("value", value); + } + this._updateValueBox(); + setTimeout(lang.hitch(this, function(){ + this._onChangeColumn = tmp[0]; + this._onChangeCondition = tmp[1]; + }), 0); + }, + getExpr: function(){ + if(this.isEmpty()){ + return null; + } + var colval = this._colSelect.get("value"); + return this.dlg.getExprForCriteria({ + "type": this.dlg.getColumnType(colval), + "column": colval, + "condition": this._condSelect.get("value"), + "value": this.curValue() + }); + }, + isEmpty: function(){ + var cond = this._condSelect.get("value"); + if(cond == "isempty"){return false;} + var v = this.curValue(); + return v === "" || v === null || typeof v == "undefined" || (typeof v == "number" && isNaN(v)); + }, + updateRuleTitle: function(isEmpty){ + var node = this._pane._buttonWidget.titleTextNode; + var title = [ + "<div class='dojoxEllipsis'>" + ]; + if(isEmpty || this.isEmpty()){ + node.title = string.substitute(this.plugin.nls.ruleTitleTemplate, [this._ruleIndex || 1]); + title.push(node.title); + }else{ + var type = this.dlg.getColumnType(this._colSelect.get("value")); + var column = this.curColumn(); + var condition = this.curCondition(); + var value = this.formatValue(type, this._condSelect.get("value"), this.curValue()); + title.push( + column, + " <span class='dojoxGridRuleTitleCondition'>", + condition, + "</span> ", + value + ); + node.title = [column, " ", condition, " ", value].join(''); + } + node.innerHTML = title.join(''); + if(has("mozilla")){ + var tt = html.create("div", { + "style": "width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 9999;" + }, node); + tt.title = node.title; + } + }, + updateRuleIndex: function(index){ + if(this._ruleIndex != index){ + this._ruleIndex = index; + if(this.isEmpty()){ + this.updateRuleTitle(); + } + } + }, + setAriaInfo: function(idx){ + var dss = string.substitute, nls = this.plugin.nls; + this._colSelect.domNode.setAttribute("aria-label", dss(nls.waiColumnSelectTemplate, [idx])); + this._condSelect.domNode.setAttribute("aria-label", dss(nls.waiConditionSelectTemplate, [idx])); + this._pane._removeCBoxBtn.domNode.setAttribute("aria-label", dss(nls.waiRemoveRuleButtonTemplate, [idx])); + this._index = idx; + }, + _getUsableConditions: function(type){ + var conditions = lang.clone(this.dlg._dataTypeMap[type].conditions); + var typeDisabledConds = (this.plugin.args.disabledConditions || {})[type]; + var colIdx = parseInt(this._colSelect.get("value"), 10); + var colDisabledConds = isNaN(colIdx) ? + (this.plugin.args.disabledConditions || {})["anycolumn"] : + this.plugin.grid.layout.cells[colIdx].disabledConditions; + if(!lang.isArray(typeDisabledConds)){ + typeDisabledConds = []; + } + if(!lang.isArray(colDisabledConds)){ + colDisabledConds = []; + } + var arr = typeDisabledConds.concat(colDisabledConds); + if(arr.length){ + var disabledConds = {}; + array.forEach(arr, function(c){ + if(lang.isString(c)){ + disabledConds[c.toLowerCase()] = true; + } + }); + return array.filter(conditions, function(condOption){ + return !(condOption.value in disabledConds); + }); + } + return conditions; + }, + _setConditionsByType: function(/* string */type){ + var condSelect = this._condSelect; + condSelect.removeOption(condSelect.options); + condSelect.addOption(this._getUsableConditions(type)); + this._showSelectOrLabel(this._condSelect, this._condSelectAlt); + }, + _setValueBoxByType: function(/* string */type){ + if(this._curValueBox){ + this.valueNode.removeChild(this._curValueBox.domNode); + try{ + this._curValueBox.destroyRecursive(); + }catch(e){} + delete this._curValueBox; + } + //value box class + var vbcls = this.dlg._dataTypeMap[type].valueBoxCls[this._getValueBoxClsInfo(this._colSelect.get("value"), type)], + vboxArg = this._getValueBoxArgByType(type); + this._curValueBox = this[this._isRange ? "_createRangeBox" : "_createValueBox"](vbcls, vboxArg); + this.valueNode.appendChild(this._curValueBox.domNode); + + //Can not move to setAriaInfo, 'cause the value box is created after the defpane is loaded. + this._curValueBox.domNode.setAttribute("aria-label", string.substitute(this.plugin.nls.waiValueBoxTemplate,[this._index])); + //Now our cbox is completely ready + this.dlg.onRendered(this); + }, + //--------------------------UI Configuration-------------------------------------- + _getValueBoxArgByType: function(/* string */type){ + // summary: + // Get the arguments for the value box construction. + var g = this.plugin.grid, + cell = g.layout.cells[parseInt(this._colSelect.get("value"), 10)], + res = { + cbox: this + }; + if(type == "string"){ + if(cell && (cell.suggestion || cell.autoComplete)){ + html.mixin(res, { + store: g.store, + searchAttr: cell.field || cell.name, + fetchProperties: { + sort: [{"attribute": cell.field || cell.name}], + query: g.query, + queryOptions: g.queryOptions + } + }); + } + }else if(type == "boolean"){ + html.mixin(res, this.dlg.builder.defaultArgs["boolean"]); + } + if(cell && cell.dataTypeArgs){ + html.mixin(res, cell.dataTypeArgs); + } + return res; + }, + formatValue: function(type, cond, v){ + // summary: + // Format the value to be shown in tooltip. + if(cond == "isempty"){return "";} + if(type == "date" || type == "time"){ + var opt = {selector: type}, + fmt = dateLocale.format; + if(cond == "range"){ + return string.substitute(this.plugin.nls.rangeTemplate, [fmt(v.start, opt), fmt(v.end, opt)]); + } + return fmt(v, opt); + }else if(type == "boolean"){ + return v ? this._curValueBox._lblTrue : this._curValueBox._lblFalse; + } + return v; + }, + _getValueBoxClsInfo: function(/* int|string */colIndex, /* string */type){ + // summary: + // Decide which value box to use given data type and column index. + var cell = this.plugin.grid.layout.cells[parseInt(colIndex, 10)]; + //Now we only need to handle string. But maybe we need to handle more types here in the future. + if(type == "string"){ + return (cell && (cell.suggestion || cell.autoComplete)) ? "ac" : "dft"; + } + return "dft"; + } +}); + + +var UniqueComboBox = declare("dojox.grid.enhanced.plugins.filter.UniqueComboBox", ComboBox, { + _openResultList: function(results){ + var cache = {}, s = this.store, colName = this.searchAttr; + arguments[0] = array.filter(results, function(item){ + var key = s.getValue(item, colName), existed = cache[key]; + cache[key] = true; + return !existed; + }); + this.inherited(arguments); + }, + _onKey: function(evt){ + if(evt.charOrCode === keys.ENTER && this._opened){ + event.stop(evt); + } + this.inherited(arguments); + } +}); +var BooleanValueBox = declare("dojox.grid.enhanced.plugins.filter.BooleanValueBox", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], { + templateString: cache("dojox.grid","enhanced/templates/FilterBoolValueBox.html"), + widgetsInTemplate: true, + constructor: function(args){ + var nls = args.cbox.plugin.nls; + this._baseId = args.cbox.id; + this._lblTrue = args.trueLabel || nls.trueLabel || "true"; + this._lblFalse = args.falseLabel || nls.falseLabel || "false"; + this.args = args; + }, + postCreate: function(){ + this.onChange(); + }, + onChange: function(){}, + + get: function(prop){ + return this.rbTrue.get("checked"); + }, + set: function(prop, v){ + this.inherited(arguments); + if(prop == "value"){ + this.rbTrue.set("checked", !!v); + this.rbFalse.set("checked", !v); + } + } +}); +var FilterDefDialog = declare("dojox.grid.enhanced.plugins.filter.FilterDefDialog", null, { + // summary: + // Create the filter definition UI. + curColIdx: -1, + _relOpCls: "logicall", + _savedCriterias: null, + plugin: null, + constructor: function(args){ + var plugin = this.plugin = args.plugin; + this.builder = new FilterBuilder(); + this._setupData(); + this._cboxes = []; + this.defaultType = plugin.args.defaultType || "string"; + + (this.filterDefPane = new FilterDefPane({ + "dlg": this + })).startup(); + (this._defPane = new Dialog({ + "refNode": this.plugin.grid.domNode, + "title": plugin.nls.filterDefDialogTitle, + "class": "dojoxGridFDTitlePane", + "iconClass": "dojoxGridFDPaneIcon", + "content": this.filterDefPane + })).startup(); + + this._defPane.connect(plugin.grid.layer('filter'), "filterDef", lang.hitch(this, "_onSetFilter")); + plugin.grid.setFilter = lang.hitch(this, "setFilter"); + plugin.grid.getFilter = lang.hitch(this, "getFilter"); + plugin.grid.getFilterRelation = lang.hitch(this, function(){ + return this._relOpCls; + }); + + plugin.connect(plugin.grid.layout, "moveColumn", lang.hitch(this, "onMoveColumn")); + }, + onMoveColumn: function(sourceViewIndex, destViewIndex, cellIndex, targetIndex, before){ + if(this._savedCriterias && cellIndex != targetIndex){ + if(before){ --targetIndex; } + var min = cellIndex < targetIndex ? cellIndex : targetIndex; + var max = cellIndex < targetIndex ? targetIndex : cellIndex; + var dir = targetIndex > min ? 1 : -1; + array.forEach(this._savedCriterias, function(sc){ + var idx = parseInt(sc.column, 10); + if(!isNaN(idx) && idx >= min && idx <= max){ + sc.column = String(idx == cellIndex ? idx + (max - min) * dir : idx - dir); + } + }); + } + }, + destroy: function(){ + this._defPane.destroyRecursive(); + this._defPane = null; + this.filterDefPane = null; + this.builder = null; + this._dataTypeMap = null; + this._cboxes = null; + var g = this.plugin.grid; + g.setFilter = null; + g.getFilter = null; + g.getFilterRelation = null; + this.plugin = null; + }, + _setupData: function(){ + var nls = this.plugin.nls; + this._dataTypeMap = { + // summary: + // All supported data types + "number":{ + valueBoxCls: { + dft: NumberTextBox + }, + conditions:[ + {label: nls.conditionEqual, value: "equalto", selected: true}, + {label: nls.conditionNotEqual, value: "notequalto"}, + {label: nls.conditionLess, value: "lessthan"}, + {label: nls.conditionLessEqual, value: "lessthanorequalto"}, + {label: nls.conditionLarger, value: "largerthan"}, + {label: nls.conditionLargerEqual, value: "largerthanorequalto"}, + {label: nls.conditionIsEmpty, value: "isempty"} + ] + }, + "string":{ + valueBoxCls: { + dft: TextBox, + ac: UniqueComboBox //For autoComplete + }, + conditions:[ + {label: nls.conditionContains, value: "contains", selected: true}, + {label: nls.conditionIs, value: "equalto"}, + {label: nls.conditionStartsWith, value: "startswith"}, + {label: nls.conditionEndWith, value: "endswith"}, + {label: nls.conditionNotContain, value: "notcontains"}, + {label: nls.conditionIsNot, value: "notequalto"}, + {label: nls.conditionNotStartWith, value: "notstartswith"}, + {label: nls.conditionNotEndWith, value: "notendswith"}, + {label: nls.conditionIsEmpty, value: "isempty"} + ] + }, + "date":{ + valueBoxCls: { + dft: DateTextBox + }, + conditions:[ + {label: nls.conditionIs, value: "equalto", selected: true}, + {label: nls.conditionBefore, value: "lessthan"}, + {label: nls.conditionAfter, value: "largerthan"}, + {label: nls.conditionRange, value: "range"}, + {label: nls.conditionIsEmpty, value: "isempty"} + ] + }, + "time":{ + valueBoxCls: { + dft: TimeTextBox + }, + conditions:[ + {label: nls.conditionIs, value: "equalto", selected: true}, + {label: nls.conditionBefore, value: "lessthan"}, + {label: nls.conditionAfter, value: "largerthan"}, + {label: nls.conditionRange, value: "range"}, + {label: nls.conditionIsEmpty, value: "isempty"} + ] + }, + "boolean": { + valueBoxCls: { + dft: BooleanValueBox + }, + conditions: [ + {label: nls.conditionIs, value: "equalto", selected: true}, + {label: nls.conditionIsEmpty, value: "isempty"} + ] + } + }; + }, + setFilter: function(rules, ruleRelation){ + rules = rules || []; + if(!lang.isArray(rules)){ + rules = [rules]; + } + var func = function(){ + if(rules.length){ + this._savedCriterias = array.map(rules, function(rule){ + var type = rule.type || this.defaultType; + return { + "type": type, + "column": String(rule.column), + "condition": rule.condition, + "value": rule.value, + "colTxt": this.getColumnLabelByValue(String(rule.column)), + "condTxt": this.getConditionLabelByValue(type, rule.condition), + "formattedVal": rule.formattedVal || rule.value + }; + }, this); + this._criteriasChanged = true; + if(ruleRelation === "logicall" || ruleRelation === "logicany"){ + this._relOpCls = ruleRelation; + } + var exprs = array.map(rules, this.getExprForCriteria, this); + exprs = this.builder.buildExpression(exprs.length == 1 ? exprs[0] : { + "op": this._relOpCls, + "data": exprs + }); + this.plugin.grid.layer("filter").filterDef(exprs); + this.plugin.filterBar.toggleClearFilterBtn(false); + } + this._closeDlgAndUpdateGrid(); + }; + if(this._savedCriterias){ + this._clearWithoutRefresh = true; + var handle = connect.connect(this, "clearFilter", this, function(){ + connect.disconnect(handle); + this._clearWithoutRefresh = false; + func.apply(this); + }); + this.onClearFilter(); + }else{ + func.apply(this); + } + }, + getFilter: function(){ + return lang.clone(this._savedCriterias) || []; + }, + getColumnLabelByValue: function(v){ + var nls = this.plugin.nls; + if(v.toLowerCase() == "anycolumn"){ + return nls["anyColumnOption"]; + }else{ + var cell = this.plugin.grid.layout.cells[parseInt(v, 10)]; + return cell ? (cell.name || cell.field) : ""; + } + }, + getConditionLabelByValue: function(type, c){ + var conditions = this._dataTypeMap[type].conditions; + for(var i = conditions.length - 1; i >= 0; --i){ + var cond = conditions[i]; + if(cond.value == c.toLowerCase()){ + return cond.label; + } + } + return ""; + }, + addCriteriaBoxes: function(/* int */cnt){ + // summary: + // Add *cnt* criteria boxes to the filter definition pane. + // Check overflow if necessary. + if(typeof cnt != "number" || cnt <= 0){ + return; + } + var cbs = this._cboxes, + cc = this.filterDefPane.cboxContainer, + total = this.plugin.args.ruleCount, + len = cbs.length, cbox; + //If overflow, add to max rule count. + if(total > 0 && len + cnt > total){ + cnt = total - len; + } + for(; cnt > 0; --cnt){ + cbox = new CriteriaBox({ + dlg: this + }); + cbs.push(cbox); + cc.addChild(cbox); + } + //If there's no content box in it , AccordionContainer can not startup + cc.startup(); + this._updatePane(); + this._updateCBoxTitles(); + cc.selectChild(cbs[cbs.length-1]); + //Asign an impossibly large scrollTop to scroll the criteria pane to the bottom. + this.filterDefPane.criteriaPane.scrollTop = 1000000; + if(cbs.length === 4){ + if(has("ie") <= 6 && !this.__alreadyResizedForIE6){ + var size = html.position(cc.domNode); + size.w -= metrics.getScrollbar().w; + cc.resize(size); + this.__alreadyResizedForIE6 = true; + }else{ + cc.resize(); + } + } + }, + removeCriteriaBoxes: function(/* int|CriteriaBox|int[] */cnt,/* bool? */isIdx){ + // summary: + // Remove criteria boxes from the filter definition pane. + var cbs = this._cboxes, cc = this.filterDefPane.cboxContainer, + len = cbs.length, start = len - cnt, + end = len - 1, cbox, + curIdx = array.indexOf(cbs, cc.selectedChildWidget.content); + if(lang.isArray(cnt)){ + var i, idxes = cnt; + idxes.sort(); + cnt = idxes.length; + //find a rule that's not deleted. + //must find and focus the last one, or the hack will not work. + for(i = len - 1; i >= 0 && array.indexOf(idxes, i) >= 0; --i){} + if(i >= 0){ + //must select before remove + if(i != curIdx){ + cc.selectChild(cbs[i]); + } + //idxes is sorted from small to large, + //so travel reversely won't need change index after delete from array. + for(i = cnt-1; i >= 0; --i){ + if(idxes[i] >= 0 && idxes[i] < len){ + cc.removeChild(cbs[idxes[i]]); + cbs.splice(idxes[i],1); + } + } + } + start = cbs.length; + }else{ + if(isIdx === true){ + if(cnt >= 0 && cnt < len){ + start = end = cnt; + cnt = 1; + }else{ + return; + } + }else{ + if(cnt instanceof CriteriaBox){ + cbox = cnt; + cnt = 1; + start = end = array.indexOf(cbs, cbox); + }else if(typeof cnt != "number" || cnt <= 0){ + return; + }else if(cnt >= len){ + cnt = end; + start = 1; + } + } + if(end < start){ + return; + } + //must select before remove + if(curIdx >= start && curIdx <= end){ + cc.selectChild(cbs[start ? start-1 : end+1]); + } + for(; end >= start; --end){ + cc.removeChild(cbs[end]); + } + cbs.splice(start, cnt); + } + this._updatePane(); + this._updateCBoxTitles(); + if(cbs.length === 3){ + //In ie6, resize back to the normal width will cause the title button look strange. + cc.resize(); + } + }, + getCriteria: function(/* int */idx){ + // summary: + // Get the *idx*-th criteria. + if(typeof idx != "number"){ + return this._savedCriterias ? this._savedCriterias.length : 0; + } + if(this._savedCriterias && this._savedCriterias[idx]){ + return lang.mixin({ + relation: this._relOpCls == "logicall" ? this.plugin.nls.and : this.plugin.nls.or + },this._savedCriterias[idx]); + } + return null; + }, + getExprForCriteria: function(rule){ + if(rule.column == "anycolumn"){ + var cells = array.filter(this.plugin.grid.layout.cells, function(cell){ + return !(cell.filterable === false || cell.hidden); + }); + return { + "op": "logicany", + "data": array.map(cells, function(cell){ + return this.getExprForColumn(rule.value, cell.index, rule.type, rule.condition); + }, this) + }; + }else{ + return this.getExprForColumn(rule.value, rule.column, rule.type, rule.condition); + } + }, + getExprForColumn: function(value, colIdx, type, condition){ + colIdx = parseInt(colIdx, 10); + var cell = this.plugin.grid.layout.cells[colIdx], + colName = cell.field || cell.name, + obj = { + "datatype": type || this.getColumnType(colIdx), + "args": cell.dataTypeArgs, + "isColumn": true + }, + operands = [lang.mixin({"data": this.plugin.args.isServerSide ? colName : cell}, obj)]; + obj.isColumn = false; + if(condition == "range"){ + operands.push(lang.mixin({"data": value.start}, obj), + lang.mixin({"data": value.end}, obj)); + }else if(condition != "isempty"){ + operands.push(lang.mixin({"data": value}, obj)); + } + return { + "op": condition, + "data": operands + }; + }, + getColumnType: function(/* int */colIndex){ + var cell = this.plugin.grid.layout.cells[parseInt(colIndex, 10)]; + if(!cell || !cell.datatype){ + return this.defaultType; + } + var type = String(cell.datatype).toLowerCase(); + return this._dataTypeMap[type] ? type : this.defaultType; + }, + ////////////////////////////////////////////////////////////////////////////////////////////////////////// + clearFilter: function(noRefresh){ + // summary: + // Clear filter definition. + if(!this._savedCriterias){ + return; + } + this._savedCriterias = null; + this.plugin.grid.layer("filter").filterDef(null); + try{ + this.plugin.filterBar.toggleClearFilterBtn(true); + this.filterDefPane._clearFilterBtn.set("disabled", true); + this.removeCriteriaBoxes(this._cboxes.length-1); + this._cboxes[0].load({}); + }catch(e){ + //Any error means the filter is defined outside this plugin. + } + if(noRefresh){ + this.closeDialog(); + }else{ + this._closeDlgAndUpdateGrid(); + } + }, + showDialog: function(/* int */colIndex){ + // summary: + // Show the filter defintion dialog. + this._defPane.show(); + this.plugin.filterStatusTip.closeDialog(); + this._prepareDialog(colIndex); + }, + closeDialog: function(){ + // summary: + // Close the filter definition dialog. + if(this._defPane.open){ + this._defPane.hide(); + } + }, + onFilter: function(e){ + // summary: + // Triggered when the "Filter" button is clicked. + if(this.canFilter()){ + this._defineFilter(); + this._closeDlgAndUpdateGrid(); + this.plugin.filterBar.toggleClearFilterBtn(false); + } + }, + onClearFilter: function(e){ + // summary: + // Triggered when the "Clear" button is clicked. + if(this._savedCriterias){ + if(this._savedCriterias.length >= this.plugin.ruleCountToConfirmClearFilter){ + this.plugin.clearFilterDialog.show(); + }else{ + this.clearFilter(this._clearWithoutRefresh); + } + } + }, + onCancel: function(e){ + // summary: + // Triggered when the "Cancel" buttton is clicked. + var sc = this._savedCriterias; + var cbs = this._cboxes; + if(sc){ + this.addCriteriaBoxes(sc.length - cbs.length); + this.removeCriteriaBoxes(cbs.length - sc.length); + array.forEach(sc, function(c, i){ + cbs[i].load(c); + }); + }else{ + this.removeCriteriaBoxes(cbs.length - 1); + cbs[0].load({}); + } + this.closeDialog(); + }, + onRendered: function(cbox){ + // summary: + // Triggered when the rendering of the filter definition dialog is completely finished. + // cbox: + // Current visible criteria box + if(!has("ff")){ + var elems = dijitA11y._getTabNavigable(html.byId(cbox.domNode)); + dijitFocus.focus(elems.lowest || elems.first); + }else{ + var dp = this._defPane; + dp._getFocusItems(dp.domNode); + dijitFocus.focus(dp._firstFocusItem); + } + }, + _onSetFilter: function(filterDef){ + // summary: + // If someone clear the filter def in the store directly, we must clear it in the UI. + // If someone defines a filter, don't know how to handle it! + if(filterDef === null && this._savedCriterias){ + this.clearFilter(); + } + }, + _prepareDialog: function(/* int */colIndex){ + var sc = this._savedCriterias, + cbs = this._cboxes, i, cbox; + this.curColIdx = colIndex; + if(!sc){ + if(cbs.length === 0){ + this.addCriteriaBoxes(1); + }else{ + //Re-populate columns anyway, because we don't know when the column is set to hidden. + for(i = 0; (cbox = cbs[i]); ++i){ + cbox.changeCurrentColumn(); + } + } + }else if(this._criteriasChanged){ + this.filterDefPane._relSelect.set("value", this._relOpCls === "logicall" ? "0" : "1"); + this._criteriasChanged = false; + var needNewCBox = sc.length > cbs.length ? sc.length - cbs.length : 0; + this.addCriteriaBoxes(needNewCBox); + this.removeCriteriaBoxes(cbs.length - sc.length); + this.filterDefPane._clearFilterBtn.set("disabled", false); + for(i = 0; i < cbs.length - needNewCBox; ++i){ + cbs[i].load(sc[i]); + } + if(needNewCBox > 0){ + var handled = [], handle = connect.connect(this, "onRendered", function(cbox){ + var i = array.indexOf(cbs, cbox); + if(!handled[i]){ + handled[i] = true; + if(--needNewCBox === 0){ + connect.disconnect(handle); + } + cbox.load(sc[i]); + } + }); + } + } + //Since we're allowed to remove cboxes when the definition pane is not shown, + //we have to resize the container to have a correct _verticalSpace. + this.filterDefPane.cboxContainer.resize(); + }, + _defineFilter: function(){ + var cbs = this._cboxes, + filterCboxes = function(method){ + return array.filter(array.map(cbs, function(cbox){ + return cbox[method](); + }), function(result){ + return !!result; + }); + }, + exprs = filterCboxes("getExpr"); + this._savedCriterias = filterCboxes("save"); + exprs = exprs.length == 1 ? exprs[0] : { + "op": this._relOpCls, + "data": exprs + }; + exprs = this.builder.buildExpression(exprs); + + this.plugin.grid.layer("filter").filterDef(exprs); + this.filterDefPane._clearFilterBtn.set("disabled", false); + }, + _updateCBoxTitles: function(){ + for(var cbs = this._cboxes, i = cbs.length; i > 0; --i){ + cbs[i - 1].updateRuleIndex(i); + cbs[i - 1].setAriaInfo(i); + } + }, + _updatePane: function(){ + var cbs = this._cboxes, + defPane = this.filterDefPane; + defPane._addCBoxBtn.set("disabled", cbs.length == this.plugin.args.ruleCount); + defPane._filterBtn.set("disabled", !this.canFilter()); + }, + canFilter: function(){ + return array.filter(this._cboxes, function(cbox){ + return !cbox.isEmpty(); + }).length > 0; + }, + _closeDlgAndUpdateGrid: function(){ + this.closeDialog(); + var g = this.plugin.grid; + g.showMessage(g.loadingMessage); + setTimeout(lang.hitch(g, g._refresh), this._defPane.duration + 10); + } +}); + +return FilterDefDialog; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/FilterLayer.js b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterLayer.js new file mode 100644 index 0000000..e8f7ac4 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterLayer.js @@ -0,0 +1,411 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/FilterLayer", [ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/_base/window", + "dojo/_base/json", + "../_StoreLayer" +], function(declare, lang, win, json, layers){ + + var cmdSetFilter = "filter", + cmdClearFilter = "clear", + hitchIfCan = function(scope, func){ + return func ? lang.hitch(scope || win.global, func) : function(){}; + }, + shallowClone = function(obj){ + var res = {}; + if(obj && lang.isObject(obj)){ + for(var name in obj){ + res[name] = obj[name]; + } + } + return res; + }; + var _FilterLayerMixin = declare("dojox.grid.enhanced.plugins.filter._FilterLayerMixin", null, { +/*===== + // _filter: _ConditionExpr + // The filter definition + _filter: null, + + filterDef: function(filter){ + // summary: + // Get/set/clear the filter definition + // tags: + // public + // filter: (_ConditionExpr|null)? + // null: clear filter definition + // undefined: it's getter + // returns: + // A filter definition if it's getter. + }, +=====*/ + tags: ["sizeChange"], + name: function(){ + // summary: + // override from _StoreLayer.name + return "filter"; //string + }, + onFilterDefined: function(filter){}, + + onFiltered: function(filteredSize, totalSize){ + // summary: + // Called when store data is filtered. This event is before *onComplete*, after *onBegin*. + // tags: + // callback extension + // filteredSize: Integer + // The number of remaining fetched items after filtering. + // totalSize: Integer + // The number of original fetched items. + } + }); + var ServerSideFilterLayer = declare("dojox.grid.enhanced.plugins.filter.ServerSideFilterLayer", [layers._ServerSideLayer, _FilterLayerMixin], { + constructor: function(args){ + this._onUserCommandLoad = args.setupFilterQuery || this._onUserCommandLoad; + this.filterDef(null); + }, + filterDef: function(/* (_ConditionExpr|null)? */filter){ + // summary: + // See _FilterLayerMixin.filterDef + if(filter){ + this._filter = filter; + var obj = filter.toObject(); + //Stateless implementation will need to parse the filter object. + this.command(cmdSetFilter, this._isStateful ? json.toJson(obj) : obj); + this.command(cmdClearFilter, null); + this.useCommands(true); + this.onFilterDefined(filter); + }else if(filter === null){ + this._filter = null; + this.command(cmdSetFilter, null); + this.command(cmdClearFilter, true); + this.useCommands(true); + this.onFilterDefined(null); + } + return this._filter; //_ConditionExpr + }, + onCommandLoad: function(/* (in)string */responce, /* (in|out)keywordArgs */ userRequest){ + // summary: + // override from _ServerSideLayer.onCommandLoad + this.inherited(arguments); + var oldOnBegin = userRequest.onBegin; + if(this._isStateful){ + var filteredSize; + if(responce){ + this.command(cmdSetFilter, null); + this.command(cmdClearFilter, null); + this.useCommands(false); + var sizes = responce.split(','); + if(sizes.length >= 2){ + filteredSize = this._filteredSize = parseInt(sizes[0], 10); + this.onFiltered(filteredSize, parseInt(sizes[1], 10)); + }else{ + //Error here. + return; + } + }else{ + filteredSize = this._filteredSize; + } + if(this.enabled()){ + userRequest.onBegin = function(size, req){ + hitchIfCan(userRequest.scope, oldOnBegin)(filteredSize, req); + }; + } + }else{ + var _this = this; + userRequest.onBegin = function(size, req){ + if(!_this._filter){ + _this._storeSize = size; + } + _this.onFiltered(size, _this._storeSize || size); + req.onBegin = oldOnBegin; + hitchIfCan(userRequest.scope, oldOnBegin)(size, req); + }; + } + } + }); + var ClientSideFilterLayer = declare("dojox.grid.enhanced.plugins.filter.ClientSideFilterLayer", [layers._StoreLayer, _FilterLayerMixin], { + // summary: + // Add a client side filter layer on top of the data store, + // so any filter expression can be applied to the store. +/*===== + //_items: Array, + // Cached items (may contain holes) + _items: [], + + //_result: Array, + // Current fetch result + _result: [], + + //_resultStartIdx: Integer, + // The index in cache of the first result item + _resultStartIdx: 0, + + //_indexMap: Array, + // A map from the row index of this._items to the row index of the original store. + _indexMap: null, + + //_getter: function(datarow, colArg, rowIndex, store); + // A user defined way to get data from store + _getter: null, + + // _nextUnfetchedIdx: Integer + // The index of the next item in the store that is never fetched. + _nextUnfetchedIdx: 0, +=====*/ + // _storeSize: Integer + // The actual size of the original store + _storeSize: -1, + + // _fetchAll + // If the store is small or store size must be correct when onBegin is called, + // we should fetch and filter all the items on the first query. + _fetchAll: true, + + constructor: function(args){ + this.filterDef(null); + args = lang.isObject(args) ? args : {}; + this.fetchAllOnFirstFilter(args.fetchAll); + this._getter = lang.isFunction(args.getter) ? args.getter : this._defaultGetter; + }, + _defaultGetter: function(datarow, colName, rowIndex, store){ + return store.getValue(datarow, colName); + }, + filterDef: function(/* (_ConditionExpr|null)? */filter){ + // summary: + // See _FilterLayerMixin.filterDef + if(filter !== undefined){ + this._filter = filter; + this.invalidate(); + this.onFilterDefined(filter); + } + return this._filter; //_ConditionExpr + }, + setGetter: function(/* function */getter){ + // summary: + // Set the user defined way to retrieve data from store. + // tags: + // public + // getter: function(datarow, colArg, rowIndex, store); + if(lang.isFunction(getter)){ + this._getter = getter; + } + }, + fetchAllOnFirstFilter: function(/* bool? */toFetchAll){ + // summary: + // The get/set function for fetchAll. + // tags: + // public + // toFetchAll: boolean? + // If provided, it's a set function, otherwise it's a get function. + // returns: + // Whether fetch all on first filter if this is a getter + if(toFetchAll !== undefined){ + this._fetchAll = !!toFetchAll; + } + return this._fetchAll; //Boolean + }, + invalidate: function(){ + // summary: + // Clear all the status information of this layer + // tags: + // private + this._items = []; + this._nextUnfetchedIdx = 0; + this._result = []; + this._indexMap = []; + this._resultStartIdx = 0; + }, + //----------------Private Functions----------------------------- + _fetch: function(userRequest,filterRequest){ + // summary: + // Implement _StoreLayer._fetch + // tags: + // private callback + // filterRequest: dojo.data.api.Request + // The actual request used in store.fetch. + // This function is called recursively to fill the result store items + // until the user specified item count is reached. Only in recursive calls, + // this parameter is valid. + if(!this._filter){ + //If we don't have any filter, use the original request and fetch. + var old_onbegin = userRequest.onBegin, _this = this; + userRequest.onBegin = function(size, r){ + hitchIfCan(userRequest.scope, old_onbegin)(size, r); + _this.onFiltered(size, size); + }; + this.originFetch(userRequest); + return userRequest; + } + try{ + //If the fetch is at the beginning, user's start position is used; + //If we are in a recursion, our own request is used. + var start = filterRequest ? filterRequest._nextResultItemIdx : userRequest.start; + start = start || 0; + if(!filterRequest){ + //Initially, we have no results. + this._result = []; + this._resultStartIdx = start; + var sortStr; + if(lang.isArray(userRequest.sort) && userRequest.sort.length > 0 && + //Sort info will stay here in every re-fetch, so remember it! + (sortStr = json.toJson(userRequest.sort)) != this._lastSortInfo){ + //If we should sort data, all the old caches are no longer valid. + this.invalidate(); + this._lastSortInfo = sortStr; + } + } + //this._result contains the current fetch result (of every recursion). + var end = typeof userRequest.count == "number" ? + start + userRequest.count - this._result.length : this._items.length; + //Try to retrieve all the items from our cache. + //Only need items after userRequest.start, test it in case start is smaller. + if(this._result.length){ + this._result = this._result.concat(this._items.slice(start, end)); + }else{ + this._result = this._items.slice(userRequest.start, typeof userRequest.count == "number" ? + userRequest.start + userRequest.count : this._items.length); + } + if(this._result.length >= userRequest.count || this._hasReachedStoreEnd()){ + //We already have had enough items, or we have to stop fetching because there's nothing more to fetch. + this._completeQuery(userRequest); + }else{ + //User's request hasn't been finished yet. Fetch more. + if(!filterRequest){ + //Initially, we've got to create a new request object. + filterRequest = shallowClone(userRequest); + //Use our own onBegin function to remember the total size of the original store. + filterRequest.onBegin = lang.hitch(this, this._onFetchBegin); + filterRequest.onComplete = lang.hitch(this, function(items, req){ + //We've fetched some more, so march ahead! + this._nextUnfetchedIdx += items.length; + //Actual filtering work goes here. Survived items are added to our cache. + //req is our own request object. + this._doFilter(items, req.start, userRequest); + //Recursively call this function. Let's do this again! + this._fetch(userRequest, req); + }); + } + //Fetch starts from the next unfetched item. + filterRequest.start = this._nextUnfetchedIdx; + //If store is small, we should only fetch once. + if(this._fetchAll){ + delete filterRequest.count; + } + //Remember we've (maybe) already added something to our result array, so next time we should not start over again. + filterRequest._nextResultItemIdx = end < this._items.length ? end : this._items.length; + //Actual fetch work goes here. + this.originFetch(filterRequest); + } + }catch(e){ + if(userRequest.onError){ + hitchIfCan(userRequest.scope, userRequest.onError)(e, userRequest); + }else{ + throw e; + } + } + return userRequest; + }, + _hasReachedStoreEnd: function(){ + // summary: + // Check whether all the items in the original store have been fetched. + // tags: + // private + return this._storeSize >= 0 && this._nextUnfetchedIdx >= this._storeSize; //Boolean + }, + _applyFilter: function(/* data item */datarow,/* Integer */rowIndex){ + // summary: + // Apply the filter to a row of data + // tags: + // private + // returns: + // whether this row survived the filter. + var g = this._getter, s = this._store; + try{ + return !!(this._filter.applyRow(datarow, function(item, arg){ + return g(item, arg, rowIndex, s); + }).getValue()); + }catch(e){ + console.warn("FilterLayer._applyFilter() error: ", e); + return false; + } + }, + _doFilter: function(/* Array */items,/* Integer */startIdx,/* object */userRequest){ + // summary: + // Use the filter expression to filter items. Survived items are stored in this._items. + // The given items start from "startIdx" in the original store. + // tags: + // private + for(var i = 0, cnt = 0; i < items.length; ++i){ + if(this._applyFilter(items[i], startIdx + i)){ + hitchIfCan(userRequest.scope, userRequest.onItem)(items[i], userRequest); + cnt += this._addCachedItems(items[i], this._items.length); + this._indexMap.push(startIdx + i); + } + } + }, + _onFetchBegin: function(/* Integer */size,/* request object */req){ + // summary: + // This function is used to replace the user's onFetchBegin in store.fetch + // tags: + // private + this._storeSize = size; + }, + _completeQuery: function(/* request object */userRequest){ + // summary: + // Logically, the user's query is completed here, i.e., all the filtered results are ready. + // (or their index mappings are ready) + // tags: + // private + var size = this._items.length; + if(this._nextUnfetchedIdx < this._storeSize){ + //FIXME: There's still some items in the original store that are not fetched & filtered. + //So we have to estimate a little bigger size to allow scrolling to these unfetched items. + //However, this behavior is ONLY correct in Grid! Any better way to do this? + size++; + } + hitchIfCan(userRequest.scope, userRequest.onBegin)(size,userRequest); + this.onFiltered(this._items.length, this._storeSize); + hitchIfCan(userRequest.scope, userRequest.onComplete)(this._result, userRequest); + }, + _addCachedItems: function(/* Array */items,/* Integer */filterStartIdx){ + // summary: + // Add data items to the cache. The insert point is at *filterStartIdx* + // tags: + // private + // items: Array + // Data items to add. + // filterStartIdx: Integer + // The start point to insert in the cache. + if(!lang.isArray(items)){ + items = [items]; + } + for(var k = 0; k < items.length; ++k){ + this._items[filterStartIdx + k] = items[k]; + } + return items.length; + }, + onRowMappingChange: function(mapping){ + //This function runs in FilterLayer scope! + if(this._filter){ + var m = lang.clone(mapping), + alreadyUpdated = {}; + for(var r in m){ + r = parseInt(r, 10); + mapping[this._indexMap[r]] = this._indexMap[m[r]]; + if(!alreadyUpdated[this._indexMap[r]]){ + alreadyUpdated[this._indexMap[r]] = true; + } + if(!alreadyUpdated[r]){ + alreadyUpdated[r] = true; + delete mapping[r]; + } + } + } + } + }); + + return lang.mixin({ + ServerSideFilterLayer: ServerSideFilterLayer, + ClientSideFilterLayer: ClientSideFilterLayer + }, layers); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/FilterStatusTip.js b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterStatusTip.js new file mode 100644 index 0000000..36f4430 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/FilterStatusTip.js @@ -0,0 +1,148 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/FilterStatusTip", [ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/query", + "dojo/cache", + "dojo/string", + "dojo/date/locale", + "dijit/_Widget", + "dijit/_TemplatedMixin", + "dijit/_WidgetsInTemplateMixin", + "dijit/TooltipDialog", + "dijit/form/Button", + "dijit/_base/popup", + "dojo/i18n!../../nls/Filter" +], function(declare, array, lang, query, cache, string, dateLocale, _Widget, + _TemplatedMixin, _WidgetsInTemplateMixin, TooltipDialog, Button, popup){ + + var gridCssCls = "", headerCssCls = "", cellCssCls = "", rowCssCls = "", + oddRowCssCls = "dojoxGridFStatusTipOddRow", + handleHolderCssCls = "dojoxGridFStatusTipHandle", + conditionCssCls = "dojoxGridFStatusTipCondition", + _removeRuleIconCls = "dojoxGridFStatusTipDelRuleBtnIcon", + _statusFooter = "</tbody></table>"; + + var FilterStatusPane = declare("dojox.grid.enhanced.plugins.filter.FilterStatusPane", [_Widget, _TemplatedMixin], { + templateString: cache("dojox.grid", "enhanced/templates/FilterStatusPane.html") + }); + + return declare("dojox.grid.enhanced.plugins.filter.FilterStatusTip", null, { + // summary: + // Create the status tip UI. + constructor: function(args){ + var plugin = this.plugin = args.plugin; + this._statusHeader = ["<table border='0' cellspacing='0' class='", + gridCssCls, "'><thead><tr class='", + headerCssCls, "'><th class='", + cellCssCls, "'><div>", plugin.nls["statusTipHeaderColumn"], "</div></th><th class='", + cellCssCls, " lastColumn'><div>", plugin.nls["statusTipHeaderCondition"], "</div></th></tr></thead><tbody>" + ].join(''); + this._removedCriterias = []; + this._rules = []; + this.statusPane = new FilterStatusPane(); + this._dlg = new TooltipDialog({ + "class": "dojoxGridFStatusTipDialog", + content: this.statusPane, + autofocus: false + }); + this._dlg.connect(this._dlg.domNode, 'onmouseleave', lang.hitch(this, this.closeDialog)); + this._dlg.connect(this._dlg.domNode, 'click', lang.hitch(this, this._modifyFilter)); + }, + destroy: function(){ + this._dlg.destroyRecursive(); + }, + //-----------------Public Functions------------------------ + showDialog: function(/* int */pos_x,/* int */pos_y, columnIdx){ + this._pos = {x:pos_x,y:pos_y}; + popup.close(this._dlg); + this._removedCriterias = []; + this._rules = []; + this._updateStatus(columnIdx); + popup.open({ + popup: this._dlg, + parent: this.plugin.filterBar, + onCancel: function(){}, + x:pos_x - 12, + y:pos_y - 3 + }); + }, + closeDialog: function(){ + popup.close(this._dlg); + if(this._removedCriterias.length){ + this.plugin.filterDefDialog.removeCriteriaBoxes(this._removedCriterias); + this._removedCriterias = []; + this.plugin.filterDefDialog.onFilter(); + } + }, + //-----------------Private Functions--------------------------- + _updateStatus: function(columnIdx){ + var res, p = this.plugin, + nls = p.nls, + sp = this.statusPane, + fdg = p.filterDefDialog; + if(fdg.getCriteria() === 0){ + sp.statusTitle.innerHTML = nls["statusTipTitleNoFilter"]; + sp.statusRel.innerHTML = ""; + var cell = p.grid.layout.cells[columnIdx]; + var colName = cell ? "'" + (cell.name || cell.field) + "'" : nls["anycolumn"]; + res = string.substitute(nls["statusTipMsg"], [colName]); + }else{ + sp.statusTitle.innerHTML = nls["statusTipTitleHasFilter"]; + sp.statusRel.innerHTML = fdg._relOpCls == "logicall" ? nls["statusTipRelAll"] : nls["statusTipRelAny"]; + this._rules = []; + var i = 0, c = fdg.getCriteria(i++); + while(c){ + c.index = i - 1; + this._rules.push(c); + c = fdg.getCriteria(i++); + } + res = this._createStatusDetail(); + } + sp.statusDetailNode.innerHTML = res; + this._addButtonForRules(); + }, + _createStatusDetail: function(){ + return this._statusHeader + array.map(this._rules, function(rule, i){ + return this._getCriteriaStr(rule, i); + }, this).join('') + _statusFooter; + }, + _addButtonForRules: function(){ + if(this._rules.length > 1){ + query("." + handleHolderCssCls, this.statusPane.statusDetailNode).forEach(lang.hitch(this, function(nd, idx){ + (new Button({ + label: this.plugin.nls["removeRuleButton"], + showLabel: false, + iconClass: _removeRuleIconCls, + onClick: lang.hitch(this, function(e){ + e.stopPropagation(); + this._removedCriterias.push(this._rules[idx].index); + this._rules.splice(idx,1); + this.statusPane.statusDetailNode.innerHTML = this._createStatusDetail(); + this._addButtonForRules(); + }) + })).placeAt(nd, "last"); + })); + } + }, + _getCriteriaStr: function(/* object */c, /* int */rowIdx){ + var res = ["<tr class='", rowCssCls, + " ", (rowIdx % 2 ? oddRowCssCls : ""), + "'><td class='", cellCssCls, "'>", c.colTxt, + "</td><td class='", cellCssCls, + "'><div class='", handleHolderCssCls, "'><span class='", conditionCssCls, + "'>", c.condTxt, " </span>", + c.formattedVal, "</div></td></tr>"]; + return res.join(''); + }, + _modifyFilter: function(){ + this.closeDialog(); + var p = this.plugin; + p.filterDefDialog.showDialog(p.filterBar.getColumnIdx(this._pos.x)); + } + }); + + + return FilterStatusTip; +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/_ConditionExpr.js b/js/dojo/dojox/grid/enhanced/plugins/filter/_ConditionExpr.js new file mode 100644 index 0000000..87f3461 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/_ConditionExpr.js @@ -0,0 +1,243 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/_ConditionExpr", [ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/_base/array" +], function(declare, lang, array){ + +var _ConditionExpr = declare("dojox.grid.enhanced.plugins.filter._ConditionExpr", null, { + // summary: + // The most abstract class for all condition expressions. + // A condition expression can be applied on a data row (e.g. an item in a store) + // and generate a result condition expression. + // tags: + // abstract + + _name: "expr", + + applyRow: function(/* data item */datarow,/* function(row,colArg) */getter){ + // summary: + // *Unimplemented Interface* + // Apply this condition expression on the given datarow, return a result expression. + // taqs: + // public extension + // datarow: object + // A data item of a store. + // getter: function(datarow, colArg) + // A user defined function that extract cell data from *datarow*. + // *colArg* is an argument that provides a kind of column information. + // It is defined by user in the constructor of a _DataExpr object. + // returns: + // MUST return a _ConditionExpr object + throw new Error("_ConditionExpr.applyRow: unimplemented interface"); + }, + + toObject: function(){ + // summary: + // Convert this data expression to a simple object. Mainly used for serialization. + // tags: + // public extension + // returns: + // An object for serialization. + return {}; //Object + }, + + getName: function(){ + // summary: + // Get the name of this kind of expression. + // tags: + // public extension + // returns: + // the name of this kind of expression + return this._name; //String + } +}); + +var _DataExpr = declare("dojox.grid.enhanced.plugins.filter._DataExpr", _ConditionExpr, { + // summary: + // The most abstract class for all data expressions. + // A _DataExpr is a condition expression for a single data value. + // If the data value to be represent is a pure value (literal value, like string/number/Date/...) + // this _DataExpr is nothing more than a simple wrapper. + // If the data value to be represent is in a store, then _DataExpr is responsible to extract it + // from the store when this condition is applied to a data row. + // private fields: + // _value: anything + // _colArg: anything + _name: "data", + + constructor: function(/* anything */dataValue,/* bool */isColumn, /* object */convertArgs){ + // summary: + // If a _DataExpr is constructed with only one argument, this argument is regarded as a pure value. + // If the second argument is exactly a boolean true (no implict type transformation, + // so as to allow derived classes accept more arguments while retain *isColumn* to be optional), + // then this _DataExpr represents a column, and it's applyRow method is not a no-op. + // dataValue: anything + // If *isColumn* is a boolean true, then it should be a kind of column information, like field name + // or column index. Otherwise, it is regarded as a pure value, and the getValue method will simply + // return it. + // isColumn: boolean? + // Optional. To specify whether this _DataExpr represents a column or a pure value. + this._convertArgs = convertArgs || {}; + if(lang.isFunction(this._convertArgs.convert)){ + this._convertData = lang.hitch(this._convertArgs.scope, this._convertArgs.convert); + } + if(isColumn){ + this._colArg = dataValue; + }else{ + this._value = this._convertData(dataValue, this._convertArgs); + } + }, + + getValue: function(){ + // summary: + // If this is a pure value wrapper, simply return the value. + // Otherwise (it's a column), return is undefined. + // tags: + // public + // returns: + // the value of this data expression. + return this._value; //String + }, + + applyRow: function(/* data item */datarow,/* function(row,colIdx) */getter){ + // summary: + // Implement _ConditionExpr.applyRow. + // If this is a pure value, simply return self. + // Otherwise, extract the cell data from datarow using the given getter function, + // and then convert this cell data to a _DataExpr and return the expression. + return typeof this._colArg == "undefined" ? this : //_ConditionExpr + new (lang.getObject(this.declaredClass))( + this._convertData(getter(datarow, this._colArg), this._convertArgs) + ); + }, + + _convertData: function(/* anything */dataValue){ + // summary: + // + // tags: + // protected extension + // dataValue: anything + // This argument should come from a store. + // returns: + return dataValue; + }, + + toObject: function(){ + // summary: + // Overrided from _ConditionExpr.toObject + return { //String + op: this.getName(), + data: this._colArg === undefined ? this._value : this._colArg, + isCol: this._colArg !== undefined + }; + } +}); + +var _OperatorExpr = declare("dojox.grid.enhanced.plugins.filter._OperatorExpr", _ConditionExpr, { + // summary: + // The most abstract class for all operator expressions. + // An operator expression is a _ConditionExpr that represents an operation. + _name: "operator", + + constructor: function(/* Array | operand1,operand2,... */){ + // summary: + // The arguments are operands (or an array of operands, if the first argument + // is an Array) of this operator, ordering from left to right. + // Every operand should be a _ConditionExpr. + if(lang.isArray(arguments[0])){ + this._operands = arguments[0]; + }else{ + this._operands = []; + for(var i = 0; i < arguments.length; ++i){ + this._operands.push(arguments[i]); + } + } + }, + toObject: function(){ + // summary: + // Overrided from _ConditionExpr.toObject + return { //Object + op: this.getName(), + data: array.map(this._operands,function(operand){ + return operand.toObject(); + }) + }; + } +}); + +var _UniOpExpr = declare("dojox.grid.enhanced.plugins.filter._UniOpExpr", _OperatorExpr, { + // summary: + // The most abstract class for all uni-operator expressions. + // A uni-operator expression is an _OperatorExpr that only allow one operand. + _name: "uniOperator", + + applyRow: function(/* data item */datarow,/* function(row,colArg) */getter){ + // summary: + // Implement _ConditionExpr.applyRow. + // Apply the restriction of "only one operand" and confirm the operand is a valid _ConditionExpr. + // Then do the calculation of this operator. + if(!(this._operands[0] instanceof _ConditionExpr)){ + throw new Error("_UniOpExpr: operand is not expression."); + } + return this._calculate(this._operands[0],datarow,getter); //_ConditionExpr + }, + + _calculate: function(/* _ConditionExpr */operand,/* data item*/datarow,/* function(row,colArg) */getter){ + // summary: + // *Unimplemented Interface* + // Do the actrual work of applyRow here. + // tags: + // protected extension + // operand: _ConditionExpr + // datarow: object + // getter: function(row,colArg) + // returns: + // MUST return a _ConditionExpr object. + throw new Error("_UniOpExpr._calculate: unimplemented interface"); + } +}); + +var _BiOpExpr = declare("dojox.grid.enhanced.plugins.filter._BiOpExpr", _OperatorExpr, { + // summary: + // The most abstract class for all bi-operator expressions. + // A bi-operator expression is an _OperatorExpr that allow and only allow two operands. + _name: "biOperator", + + applyRow: function(/* data item */datarow,/* function(row,colArg) */getter){ + // summary: + // Implement _ConditionExpr.applyRow. + // Apply the restriction of "two operands" and confirm operands are valid _ConditionExpr's. + if(!(this._operands[0] instanceof _ConditionExpr)){ + throw new Error("_BiOpExpr: left operand is not expression."); + }else if(!(this._operands[1] instanceof _ConditionExpr)){ + throw new Error("_BiOpExpr: right operand is not expression."); + } + return this._calculate(this._operands[0],this._operands[1],datarow,getter); + }, + + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand,/* data item*/datarow,/* function(row,colArg) */getter){ + // summary: + // *Unimplemented Interface* + // Do the actrual work of applyRow here. + // tags: + // protected extension + // left_operand: _ConditionExpr + // right_operand: _ConditionExpr + // datarow: object + // getter: function(row,colArg) + // returns: + // MUST return a _ConditionExpr object. + throw new Error("_BiOpExpr._calculate: unimplemented interface"); + } +}); + +return { + _ConditionExpr: _ConditionExpr, + _DataExpr: _DataExpr, + _OperatorExpr: _OperatorExpr, + _UniOpExpr: _UniOpExpr, + _BiOpExpr: _BiOpExpr +}; + +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/_DataExprs.js b/js/dojo/dojox/grid/enhanced/plugins/filter/_DataExprs.js new file mode 100644 index 0000000..b19c2c4 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/_DataExprs.js @@ -0,0 +1,85 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/_DataExprs", [ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/date/locale", + "./_ConditionExpr" +], function(declare, lang, dateLocale, exprs){ + + var BooleanExpr = declare("dojox.grid.enhanced.plugins.filter.BooleanExpr", exprs._DataExpr, { + // summary: + // A condition expression wrapper for boolean values + _name: "bool", + _convertData: function(/* anything */dataValue){ + // summary: + // override from _DataExpr + return !!dataValue; //Boolean + } + }); + var StringExpr = declare("dojox.grid.enhanced.plugins.filter.StringExpr", exprs._DataExpr, { + // summary: + // A condition expression wrapper for string values + _name: "string", + _convertData: function(/* anything */dataValue){ + // summary: + // override from _DataExpr + return String(dataValue); //String + } + }); + var NumberExpr = declare("dojox.grid.enhanced.plugins.filter.NumberExpr", exprs._DataExpr, { + // summary: + // A condition expression wrapper for number values + _name: "number", + _convertDataToExpr: function(/* anything */dataValue){ + // summary: + // override from _DataExpr + return parseFloat(dataValue); //Number + } + }); + var DateExpr = declare("dojox.grid.enhanced.plugins.filter.DateExpr", exprs._DataExpr, { + // summary: + // A condition expression wrapper for date values + _name: "date", + _convertData: function(/* anything */dataValue){ + // summary: + // override from _DataExpr + if(dataValue instanceof Date){ + return dataValue; + }else if(typeof dataValue == "number"){ + return new Date(dataValue); + }else{ + var res = dateLocale.parse(String(dataValue), lang.mixin({selector: this._name}, this._convertArgs)); + if(!res){ + throw new Error("Datetime parse failed: " + dataValue); + } + return res; + } + }, + toObject: function(){ + // summary: + // Overrided from _DataExpr.toObject + if(this._value instanceof Date){ + var tmp = this._value; + this._value = this._value.valueOf(); + var res = this.inherited(arguments); + this._value = tmp; + return res; + }else{ + return this.inherited(arguments); + } + } + }); + var TimeExpr = declare("dojox.grid.enhanced.plugins.filter.TimeExpr", DateExpr, { + // summary: + // A condition expression wrapper for time values + _name: "time" + }); + + return lang.mixin({ + BooleanExpr: BooleanExpr, + StringExpr: StringExpr, + NumberExpr: NumberExpr, + DateExpr: DateExpr, + TimeExpr: TimeExpr + }, exprs); +}); diff --git a/js/dojo/dojox/grid/enhanced/plugins/filter/_FilterExpr.js b/js/dojo/dojox/grid/enhanced/plugins/filter/_FilterExpr.js new file mode 100644 index 0000000..f1c2461 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/plugins/filter/_FilterExpr.js @@ -0,0 +1,248 @@ +//>>built +define("dojox/grid/enhanced/plugins/filter/_FilterExpr", [ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/date", + "./_DataExprs" +], function(declare, lang, date, exprs){ +//This is the main file that should be 'required' if filter expression facility is necessary. + + /* Logic Operations */ + var LogicAND = declare("dojox.grid.enhanced.plugins.filter.LogicAND", exprs._BiOpExpr, { + // summary: + // A logic AND condition expression. + _name: "and", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var res = left_operand.applyRow(datarow, getter).getValue() && + right_operand.applyRow(datarow, getter).getValue(); + return new exprs.BooleanExpr(res); //_ConditionExpr + } + }); + var LogicOR = declare("dojox.grid.enhanced.plugins.filter.LogicOR", exprs._BiOpExpr, { + // summary: + // A logic OR condition expression. + _name: "or", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var res = left_operand.applyRow(datarow, getter).getValue() || + right_operand.applyRow(datarow, getter).getValue(); + return new exprs.BooleanExpr(res); //_ConditionExpr + } + }); + var LogicXOR = declare("dojox.grid.enhanced.plugins.filter.LogicXOR", exprs._BiOpExpr, { + // summary: + // A logic XOR condition expression. + _name: "xor", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var left_res = left_operand.applyRow(datarow, getter).getValue(); + var right_res = right_operand.applyRow(datarow, getter).getValue(); + return new exprs.BooleanExpr((!!left_res) != (!!right_res)); //_ConditionExpr + } + }); + var LogicNOT = declare("dojox.grid.enhanced.plugins.filter.LogicNOT", exprs._UniOpExpr, { + // summary: + // A logic NOT condition expression. + _name: "not", + _calculate: function(/* _ConditionExpr */operand,/* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _UniOpExpr + return new exprs.BooleanExpr(!operand.applyRow(datarow, getter).getValue()); //_ConditionExpr + } + }); + var LogicALL = declare("dojox.grid.enhanced.plugins.filter.LogicALL", exprs._OperatorExpr, { + // summary: + // A logic ALL condition expression, equals a sequence of logic ANDs + _name: "all", + applyRow: function(/* data item */datarow,/* function(row,colIdx) */ getter){ + // summary: + // Override from _ConditionExpr + for(var i = 0, res = true; res && (this._operands[i] instanceof exprs._ConditionExpr); ++i){ + res = this._operands[i].applyRow(datarow,getter).getValue(); + } + return new exprs.BooleanExpr(res); //_ConditionExpr + } + }); + var LogicANY = declare("dojox.grid.enhanced.plugins.filter.LogicANY", exprs._OperatorExpr, { + // summary: + // A logic ANY condition expression, equals a sequence of logic ORs + _name: "any", + applyRow: function(/* data item */datarow,/* function(row,colIdx) */ getter){ + for(var i = 0,res = false; !res && (this._operands[i] instanceof exprs._ConditionExpr); ++i){ + res = this._operands[i].applyRow(datarow,getter).getValue(); + } + return new exprs.BooleanExpr(res); //_ConditionExpr + } + }); + + /* Comparison Operations */ + function compareFunc(left,right,row,getter){ + left = left.applyRow(row, getter); + right = right.applyRow(row, getter); + var left_res = left.getValue(); + var right_res = right.getValue(); + if(left instanceof exprs.TimeExpr){ + return date.compare(left_res,right_res,"time"); + }else if(left instanceof exprs.DateExpr){ + return date.compare(left_res,right_res,"date"); + }else{ + if(left instanceof exprs.StringExpr){ + left_res = left_res.toLowerCase(); + right_res = String(right_res).toLowerCase(); + } + return left_res == right_res ? 0 : (left_res < right_res ? -1 : 1); + } + } + var EqualTo = declare("dojox.grid.enhanced.plugins.filter.EqualTo", exprs._BiOpExpr, { + // summary: + // An "equal to" condition expression. + _name: "equal", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var res = compareFunc(left_operand,right_operand,datarow,getter); + return new exprs.BooleanExpr(res === 0); //_ConditionExpr + } + }); + var LessThan = declare("dojox.grid.enhanced.plugins.filter.LessThan", exprs._BiOpExpr, { + // summary: + // A "less than" condition expression. + _name: "less", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var res = compareFunc(left_operand,right_operand,datarow,getter); + return new exprs.BooleanExpr(res < 0); //_ConditionExpr + } + }); + var LessThanOrEqualTo = declare("dojox.grid.enhanced.plugins.filter.LessThanOrEqualTo", exprs._BiOpExpr, { + // summary: + // A "less than or equal to" condition expression. + _name: "lessEqual", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var res = compareFunc(left_operand,right_operand,datarow,getter); + return new exprs.BooleanExpr(res <= 0); //_ConditionExpr + } + }); + var LargerThan = declare("dojox.grid.enhanced.plugins.filter.LargerThan", exprs._BiOpExpr, { + // summary: + // A "larger than" condition expression. + _name: "larger", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var res = compareFunc(left_operand,right_operand,datarow,getter); + return new exprs.BooleanExpr(res > 0); //_ConditionExpr + } + }); + var LargerThanOrEqualTo = declare("dojox.grid.enhanced.plugins.filter.LargerThanOrEqualTo", exprs._BiOpExpr, { + // summary: + // A "larger than or equal to" condition expression. + _name: "largerEqual", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var res = compareFunc(left_operand,right_operand,datarow,getter); + return new exprs.BooleanExpr(res >= 0); //_ConditionExpr + } + }); + + /* String Operations */ + var Contains = declare("dojox.grid.enhanced.plugins.filter.Contains", exprs._BiOpExpr, { + // summary: + // A "contains" condition expression. + _name: "contains", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var left_res = String(left_operand.applyRow(datarow, getter).getValue()).toLowerCase(); + var right_res = String(right_operand.applyRow(datarow, getter).getValue()).toLowerCase(); + return new exprs.BooleanExpr(left_res.indexOf(right_res) >= 0); //_ConditionExpr + } + }); + var StartsWith = declare("dojox.grid.enhanced.plugins.filter.StartsWith", exprs._BiOpExpr, { + // summary: + // A "starts with" condition expression. + _name: "startsWith", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var left_res = String(left_operand.applyRow(datarow, getter).getValue()).toLowerCase(); + var right_res = String(right_operand.applyRow(datarow, getter).getValue()).toLowerCase(); + return new exprs.BooleanExpr(left_res.substring(0, right_res.length) == right_res); //_ConditionExpr + } + }); + var EndsWith = declare("dojox.grid.enhanced.plugins.filter.EndsWith", exprs._BiOpExpr, { + // summary: + // An "ends with" condition expression. + _name: "endsWith", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var left_res = String(left_operand.applyRow(datarow, getter).getValue()).toLowerCase(); + var right_res = String(right_operand.applyRow(datarow, getter).getValue()).toLowerCase(); + return new exprs.BooleanExpr(left_res.substring(left_res.length - right_res.length) == right_res); //_ConditionExpr + } + }); + var Matches = declare("dojox.grid.enhanced.plugins.filter.Matches", exprs._BiOpExpr, { + // summary: + // A "regular expression match" condition expression. + // The second operand's value will be regarded as an regular expression string. + _name: "matches", + _calculate: function(/* _ConditionExpr */left_operand,/* _ConditionExpr */right_operand, + /* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var left_res = String(left_operand.applyRow(datarow, getter).getValue()); + var right_res = new RegExp(right_operand.applyRow(datarow, getter).getValue()); + return new exprs.BooleanExpr(left_res.search(right_res) >= 0); //_ConditionExpr + } + }); + var IsEmpty = declare("dojox.grid.enhanced.plugins.filter.IsEmpty", exprs._UniOpExpr, { + // summary: + // Check empty + _name: "isEmpty", + _calculate: function(/* _ConditionExpr */operand,/* data item*/datarow,/* function(row,colIdx) */getter){ + // summary: + // Override from _BiOpExpr + var res = operand.applyRow(datarow, getter).getValue(); + return new exprs.BooleanExpr(res === "" || res == null); + } + }); + + return lang.mixin({ + LogicAND: LogicAND, + LogicOR: LogicOR, + LogicXOR: LogicXOR, + LogicNOT: LogicNOT, + LogicALL: LogicALL, + LogicANY: LogicANY, + EqualTo: EqualTo, + LessThan: LessThan, + LessThanOrEqualTo: LessThanOrEqualTo, + LargerThan: LargerThan, + LargerThanOrEqualTo: LargerThanOrEqualTo, + Contains: Contains, + StartsWith: StartsWith, + EndsWith: EndsWith, + Matches: Matches, + IsEmpty: IsEmpty + }, exprs); +}); |
