diff --git a/frappe/public/js/lib/slickgrid/images/arrow_redo.png b/frappe/public/js/lib/slickgrid/images/arrow_redo.png index fdc394c7c5..4f7f55d6f2 100644 Binary files a/frappe/public/js/lib/slickgrid/images/arrow_redo.png and b/frappe/public/js/lib/slickgrid/images/arrow_redo.png differ diff --git a/frappe/public/js/lib/slickgrid/images/arrow_right_peppermint.png b/frappe/public/js/lib/slickgrid/images/arrow_right_peppermint.png index 1804fa9f90..8722567866 100644 Binary files a/frappe/public/js/lib/slickgrid/images/arrow_right_peppermint.png and b/frappe/public/js/lib/slickgrid/images/arrow_right_peppermint.png differ diff --git a/frappe/public/js/lib/slickgrid/images/arrow_right_spearmint.png b/frappe/public/js/lib/slickgrid/images/arrow_right_spearmint.png index 298515ab6c..277ddde384 100644 Binary files a/frappe/public/js/lib/slickgrid/images/arrow_right_spearmint.png and b/frappe/public/js/lib/slickgrid/images/arrow_right_spearmint.png differ diff --git a/frappe/public/js/lib/slickgrid/images/arrow_undo.png b/frappe/public/js/lib/slickgrid/images/arrow_undo.png index 6972c5e594..bc9924ac07 100644 Binary files a/frappe/public/js/lib/slickgrid/images/arrow_undo.png and b/frappe/public/js/lib/slickgrid/images/arrow_undo.png differ diff --git a/frappe/public/js/lib/slickgrid/images/bullet_blue.png b/frappe/public/js/lib/slickgrid/images/bullet_blue.png index a7651ec8a0..79d978c36a 100644 Binary files a/frappe/public/js/lib/slickgrid/images/bullet_blue.png and b/frappe/public/js/lib/slickgrid/images/bullet_blue.png differ diff --git a/frappe/public/js/lib/slickgrid/images/bullet_star.png b/frappe/public/js/lib/slickgrid/images/bullet_star.png index 3829023b8e..142ea482a5 100644 Binary files a/frappe/public/js/lib/slickgrid/images/bullet_star.png and b/frappe/public/js/lib/slickgrid/images/bullet_star.png differ diff --git a/frappe/public/js/lib/slickgrid/images/bullet_toggle_minus.png b/frappe/public/js/lib/slickgrid/images/bullet_toggle_minus.png index b47ce55f68..f5aa0450d4 100644 Binary files a/frappe/public/js/lib/slickgrid/images/bullet_toggle_minus.png and b/frappe/public/js/lib/slickgrid/images/bullet_toggle_minus.png differ diff --git a/frappe/public/js/lib/slickgrid/images/bullet_toggle_plus.png b/frappe/public/js/lib/slickgrid/images/bullet_toggle_plus.png index 9ab4a89664..a965053423 100644 Binary files a/frappe/public/js/lib/slickgrid/images/bullet_toggle_plus.png and b/frappe/public/js/lib/slickgrid/images/bullet_toggle_plus.png differ diff --git a/frappe/public/js/lib/slickgrid/images/drag-handle.png b/frappe/public/js/lib/slickgrid/images/drag-handle.png index 86727b194f..ad7531cf04 100644 Binary files a/frappe/public/js/lib/slickgrid/images/drag-handle.png and b/frappe/public/js/lib/slickgrid/images/drag-handle.png differ diff --git a/frappe/public/js/lib/slickgrid/images/help.png b/frappe/public/js/lib/slickgrid/images/help.png index 0eff0a5cb5..85eca0950f 100644 Binary files a/frappe/public/js/lib/slickgrid/images/help.png and b/frappe/public/js/lib/slickgrid/images/help.png differ diff --git a/frappe/public/js/lib/slickgrid/images/sort-asc.png b/frappe/public/js/lib/slickgrid/images/sort-asc.png index e6b6264fcc..8604ff4e07 100644 Binary files a/frappe/public/js/lib/slickgrid/images/sort-asc.png and b/frappe/public/js/lib/slickgrid/images/sort-asc.png differ diff --git a/frappe/public/js/lib/slickgrid/images/sort-desc.png b/frappe/public/js/lib/slickgrid/images/sort-desc.png index 74ad1540e3..a2a6adf936 100644 Binary files a/frappe/public/js/lib/slickgrid/images/sort-desc.png and b/frappe/public/js/lib/slickgrid/images/sort-desc.png differ diff --git a/frappe/public/js/lib/slickgrid/images/stripes.png b/frappe/public/js/lib/slickgrid/images/stripes.png index 0c293a927c..c3c4b28a80 100644 Binary files a/frappe/public/js/lib/slickgrid/images/stripes.png and b/frappe/public/js/lib/slickgrid/images/stripes.png differ diff --git a/frappe/public/js/lib/slickgrid/images/tag_red.png b/frappe/public/js/lib/slickgrid/images/tag_red.png index 6ebb37d25f..d290fcd791 100644 Binary files a/frappe/public/js/lib/slickgrid/images/tag_red.png and b/frappe/public/js/lib/slickgrid/images/tag_red.png differ diff --git a/frappe/public/js/lib/slickgrid/images/tick.png b/frappe/public/js/lib/slickgrid/images/tick.png index a9925a06ab..3899d71dfa 100644 Binary files a/frappe/public/js/lib/slickgrid/images/tick.png and b/frappe/public/js/lib/slickgrid/images/tick.png differ diff --git a/frappe/public/js/lib/slickgrid/plugins/slick.autotooltips.js b/frappe/public/js/lib/slickgrid/plugins/slick.autotooltips.js index a9cea49f94..955684f2aa 100644 --- a/frappe/public/js/lib/slickgrid/plugins/slick.autotooltips.js +++ b/frappe/public/js/lib/slickgrid/plugins/slick.autotooltips.js @@ -1,45 +1,80 @@ (function ($) { - // register namespace + // Register namespace $.extend(true, window, { "Slick": { "AutoTooltips": AutoTooltips } }); - + /** + * AutoTooltips plugin to show/hide tooltips when columns are too narrow to fit content. + * @constructor + * @param {boolean} [options.enableForCells=true] - Enable tooltip for grid cells + * @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells + * @param {number} [options.maxToolTipLength=null] - The maximum length for a tooltip + */ function AutoTooltips(options) { var _grid; var _self = this; var _defaults = { + enableForCells: true, + enableForHeaderCells: false, maxToolTipLength: null }; - + + /** + * Initialize plugin. + */ function init(grid) { options = $.extend(true, {}, _defaults, options); _grid = grid; - _grid.onMouseEnter.subscribe(handleMouseEnter); + if (options.enableForCells) _grid.onMouseEnter.subscribe(handleMouseEnter); + if (options.enableForHeaderCells) _grid.onHeaderMouseEnter.subscribe(handleHeaderMouseEnter); } - + + /** + * Destroy plugin. + */ function destroy() { - _grid.onMouseEnter.unsubscribe(handleMouseEnter); + if (options.enableForCells) _grid.onMouseEnter.unsubscribe(handleMouseEnter); + if (options.enableForHeaderCells) _grid.onHeaderMouseEnter.unsubscribe(handleHeaderMouseEnter); } - - function handleMouseEnter(e, args) { + + /** + * Handle mouse entering grid cell to add/remove tooltip. + * @param {jQuery.Event} e - The event + */ + function handleMouseEnter(e) { var cell = _grid.getCellFromEvent(e); if (cell) { - var node = _grid.getCellNode(cell.row, cell.cell); - if ($(node).innerWidth() < node.scrollWidth) { - var text = $.trim($(node).text()); + var $node = $(_grid.getCellNode(cell.row, cell.cell)); + var text; + if ($node.innerWidth() < $node[0].scrollWidth) { + text = $.trim($node.text()); if (options.maxToolTipLength && text.length > options.maxToolTipLength) { text = text.substr(0, options.maxToolTipLength - 3) + "..."; } - $(node).attr("title", text); } else { - $(node).attr("title", ""); + text = ""; } + $node.attr("title", text); } } - + + /** + * Handle mouse entering header cell to add/remove tooltip. + * @param {jQuery.Event} e - The event + * @param {object} args.column - The column definition + */ + function handleHeaderMouseEnter(e, args) { + var column = args.column, + $node = $(e.target).closest(".slick-header-column"); + if (!column.toolTip) { + $node.attr("title", ($node.innerWidth() < $node[0].scrollWidth) ? column.name : ""); + } + } + + // Public API $.extend(this, { "init": init, "destroy": destroy diff --git a/frappe/public/js/lib/slickgrid/plugins/slick.cellrangedecorator.js b/frappe/public/js/lib/slickgrid/plugins/slick.cellrangedecorator.js index a511a59d6a..0cbe71d48d 100644 --- a/frappe/public/js/lib/slickgrid/plugins/slick.cellrangedecorator.js +++ b/frappe/public/js/lib/slickgrid/plugins/slick.cellrangedecorator.js @@ -20,6 +20,7 @@ function CellRangeDecorator(grid, options) { var _elem; var _defaults = { + selectionCssClass: 'slick-range-decorator', selectionCss: { "zIndex": "9999", "border": "2px dashed red" @@ -32,6 +33,7 @@ function show(range) { if (!_elem) { _elem = $("
", {css: options.selectionCss}) + .addClass(options.selectionCssClass) .css("position", "absolute") .appendTo(grid.getCanvasNode()); } @@ -61,4 +63,4 @@ "hide": hide }); } -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/frappe/public/js/lib/slickgrid/plugins/slick.cellrangeselector.js b/frappe/public/js/lib/slickgrid/plugins/slick.cellrangeselector.js index 76af7feeb7..520b17f3c4 100644 --- a/frappe/public/js/lib/slickgrid/plugins/slick.cellrangeselector.js +++ b/frappe/public/js/lib/slickgrid/plugins/slick.cellrangeselector.js @@ -54,6 +54,8 @@ return; } + _grid.focus(); + var start = _grid.getCellFromPoint( dd.startX - $(_canvas).offset().left, dd.startY - $(_canvas).offset().top); diff --git a/frappe/public/js/lib/slickgrid/plugins/slick.cellselectionmodel.js b/frappe/public/js/lib/slickgrid/plugins/slick.cellselectionmodel.js index 2f1a9bc203..74bc3eb70e 100644 --- a/frappe/public/js/lib/slickgrid/plugins/slick.cellselectionmodel.js +++ b/frappe/public/js/lib/slickgrid/plugins/slick.cellselectionmodel.js @@ -82,6 +82,13 @@ } function handleKeyDown(e) { + /*** + * Кey codes + * 37 left + * 38 up + * 39 right + * 40 down + */ var ranges, last; var active = _grid.getActiveCell(); @@ -119,8 +126,10 @@ var new_last = new Slick.Range(active.row, active.cell, active.row + dirRow*dRow, active.cell + dirCell*dCell); if (removeInvalidRanges([new_last]).length) { ranges.push(new_last); - _grid.scrollRowIntoView(dirRow > 0 ? new_last.toRow : new_last.fromRow); - _grid.scrollCellIntoView(new_last.fromRow, dirCell > 0 ? new_last.toCell : new_last.fromCell); + var viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow; + var viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell; + _grid.scrollRowIntoView(viewRow); + _grid.scrollCellIntoView(viewRow, viewCell); } else ranges.push(last); @@ -142,4 +151,4 @@ "onSelectedRangesChanged": new Slick.Event() }); } -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/frappe/public/js/lib/slickgrid/plugins/slick.headerbuttons.css b/frappe/public/js/lib/slickgrid/plugins/slick.headerbuttons.css index 9bd05cdc8d..0ba79ea0df 100644 --- a/frappe/public/js/lib/slickgrid/plugins/slick.headerbuttons.css +++ b/frappe/public/js/lib/slickgrid/plugins/slick.headerbuttons.css @@ -1,4 +1,5 @@ -.slick-column-name { +.slick-column-name, +.slick-sort-indicator { /** * This makes all "float:right" elements after it that spill over to the next line * display way below the lower boundary of the column thus hiding them. diff --git a/frappe/public/js/lib/slickgrid/plugins/slick.headermenu.css b/frappe/public/js/lib/slickgrid/plugins/slick.headermenu.css index e20aeedac6..8b0b6a9f7b 100644 --- a/frappe/public/js/lib/slickgrid/plugins/slick.headermenu.css +++ b/frappe/public/js/lib/slickgrid/plugins/slick.headermenu.css @@ -7,6 +7,7 @@ width: 14px; background-repeat: no-repeat; background-position: left center; + background-image: url(../images/down.gif); cursor: pointer; display: none; diff --git a/frappe/public/js/lib/slickgrid/plugins/slick.headermenu.js b/frappe/public/js/lib/slickgrid/plugins/slick.headermenu.js index bc63a388ba..ec8244daa6 100644 --- a/frappe/public/js/lib/slickgrid/plugins/slick.headermenu.js +++ b/frappe/public/js/lib/slickgrid/plugins/slick.headermenu.js @@ -83,7 +83,7 @@ var _handler = new Slick.EventHandler(); var _defaults = { buttonCssClass: null, - buttonImage: "../images/down.gif" + buttonImage: null }; var $menu; var $activeHeaderColumn; @@ -182,7 +182,7 @@ if (!$menu) { $menu = $("
") - .appendTo(document.body); + .appendTo(_grid.getContainerNode()); } $menu.empty(); @@ -225,14 +225,17 @@ // Position the menu. $menu - .css("top", $(this).offset().top + $(this).height()) - .css("left", $(this).offset().left); + .offset({ top: $(this).offset().top + $(this).height(), left: $(this).offset().left }); // Mark the header as active to keep the highlighting. $activeHeaderColumn = $menuButton.closest(".slick-header-column"); $activeHeaderColumn .addClass("slick-header-column-active"); + + // Stop propagation so that it doesn't register as a header click event. + e.preventDefault(); + e.stopPropagation(); } @@ -269,4 +272,4 @@ "onCommand": new Slick.Event() }); } -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/frappe/public/js/lib/slickgrid/plugins/slick.rowselectionmodel.js b/frappe/public/js/lib/slickgrid/plugins/slick.rowselectionmodel.js index 28e7e43a6d..0de8dd3a40 100644 --- a/frappe/public/js/lib/slickgrid/plugins/slick.rowselectionmodel.js +++ b/frappe/public/js/lib/slickgrid/plugins/slick.rowselectionmodel.js @@ -134,34 +134,34 @@ return false; } + if (!_grid.getOptions().multiSelect || ( + !e.ctrlKey && !e.shiftKey && !e.metaKey)) { + return false; + } + var selection = rangesToRows(_ranges); var idx = $.inArray(cell.row, selection); - if (!e.ctrlKey && !e.shiftKey && !e.metaKey) { - return false; - } - else if (_grid.getOptions().multiSelect) { - if (idx === -1 && (e.ctrlKey || e.metaKey)) { - selection.push(cell.row); - _grid.setActiveCell(cell.row, cell.cell); - } else if (idx !== -1 && (e.ctrlKey || e.metaKey)) { - selection = $.grep(selection, function (o, i) { - return (o !== cell.row); - }); - _grid.setActiveCell(cell.row, cell.cell); - } else if (selection.length && e.shiftKey) { - var last = selection.pop(); - var from = Math.min(cell.row, last); - var to = Math.max(cell.row, last); - selection = []; - for (var i = from; i <= to; i++) { - if (i !== last) { - selection.push(i); - } + if (idx === -1 && (e.ctrlKey || e.metaKey)) { + selection.push(cell.row); + _grid.setActiveCell(cell.row, cell.cell); + } else if (idx !== -1 && (e.ctrlKey || e.metaKey)) { + selection = $.grep(selection, function (o, i) { + return (o !== cell.row); + }); + _grid.setActiveCell(cell.row, cell.cell); + } else if (selection.length && e.shiftKey) { + var last = selection.pop(); + var from = Math.min(cell.row, last); + var to = Math.max(cell.row, last); + selection = []; + for (var i = from; i <= to; i++) { + if (i !== last) { + selection.push(i); } - selection.push(last); - _grid.setActiveCell(cell.row, cell.cell); } + selection.push(last); + _grid.setActiveCell(cell.row, cell.cell); } _ranges = rowsToRanges(selection); diff --git a/frappe/public/js/lib/slickgrid/slick-default-theme.css b/frappe/public/js/lib/slickgrid/slick-default-theme.css index 006afe7c07..efc7415435 100644 --- a/frappe/public/js/lib/slickgrid/slick-default-theme.css +++ b/frappe/public/js/lib/slickgrid/slick-default-theme.css @@ -6,16 +6,17 @@ classes should alter those! */ .slick-header-columns { - background-color: #f2f2f2; + background: url('images/header-columns-bg.gif') repeat-x center bottom; border-bottom: 1px solid silver; } .slick-header-column { + background: url('images/header-columns-bg.gif') repeat-x center bottom; border-right: 1px solid silver; } .slick-header-column:hover, .slick-header-column-active { - background-color: #f5f5f5; + background: white url('images/header-columns-over-bg.gif') repeat-x center bottom; } .slick-headerrow { @@ -45,10 +46,8 @@ classes should alter those! } .slick-cell { - padding: 1px 4px 0px 5px; - border-top: 0px; - border-left: 0px; - border-right: 1px solid silver; + padding-left: 4px; + padding-right: 4px; } .slick-group { @@ -62,11 +61,11 @@ classes should alter those! } .slick-group-toggle.expanded { - background: url(../frappe/js/lib/slickgrid/images/collapse.gif) no-repeat center center; + background: url(images/collapse.gif) no-repeat center center; } .slick-group-toggle.collapsed { - background: url(../frappe/js/lib/slickgrid/images/expand.gif) no-repeat center center; + background: url(images/expand.gif) no-repeat center center; } .slick-group-totals { @@ -74,6 +73,10 @@ classes should alter those! background: white; } +.slick-cell.selected { + background-color: beige; +} + .slick-cell.active { border-color: gray; border-style: solid; @@ -83,18 +86,10 @@ classes should alter those! background: silver !important; } -.slick-row[row$="1"], .slick-row[row$="3"], .slick-row[row$="5"], .slick-row[row$="7"], .slick-row[row$="9"] { +.slick-row.odd { background: #fafafa; } -.slick-row.odd .slick-cell { - background-color: #f9f9f9; -} - -.slick-cell.selected { - background-color: beige !important; -} - .slick-row.ui-state-active { background: #F5F7D7; } @@ -106,195 +101,18 @@ classes should alter those! .slick-cell.invalid { border-color: red; + -moz-animation-duration: 0.2s; + -webkit-animation-duration: 0.2s; + -moz-animation-name: slickgrid-invalid-hilite; + -webkit-animation-name: slickgrid-invalid-hilite; } -.grid-header { - border: 1px solid gray; - border-bottom: 0; - border-top: 0; - background: url('../lib/js/lib/slickgrid/images/header-bg.gif') repeat-x center top; - color: black; - height: 24px; - line-height: 24px; -} - -.grid-header label { - display: inline-block; - font-weight: bold; - margin: auto auto auto 6px; -} - -.grid-header .ui-icon { - margin: 4px 4px auto 6px; - background-color: transparent; - border-color: transparent; -} - -.grid-header .ui-icon.ui-state-hover { - background-color: white; -} - -.grid-header #txtSearch { - margin: 0 4px 0 4px; - padding: 2px 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border: 1px solid silver; -} - -.options-panel { - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border: 1px solid silver; - background: #f0f0f0; - padding: 4px; - margin-bottom: 20px; - width: 320px; - position: absolute; - top: 0px; - left: 650px; -} - -/* Individual cell styles */ -.slick-cell.task-name { - font-weight: bold; - text-align: right; -} - -.slick-cell.task-percent { - text-align: right; -} - -.slick-cell.cell-move-handle { - font-weight: bold; - text-align: right; - border-right: solid gray; - - background: #efefef; - cursor: move; -} - -.cell-move-handle:hover { - background: #b6b9bd; -} - -.slick-row.selected .cell-move-handle { - background: #D5DC8D; -} - -.slick-row .cell-actions { - text-align: left; -} - -.slick-row.complete { - background-color: #DFD; - color: #555; +@-moz-keyframes slickgrid-invalid-hilite { + from { box-shadow: 0 0 6px red; } + to { box-shadow: none; } } -.percent-complete-bar { - display: inline-block; - height: 6px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; -} - -/* Slick.Editors.Text, Slick.Editors.Date */ -input.editor-text { - width: 100%; - height: 100%; - border: 0; - margin: 0; - background: transparent; - outline: 0; - padding: 0; - -} - -.ui-datepicker-trigger { - margin-top: 2px; - padding: 0; - vertical-align: top; -} - -/* Slick.Editors.PercentComplete */ -input.editor-percentcomplete { - width: 100%; - height: 100%; - border: 0; - margin: 0; - background: transparent; - outline: 0; - padding: 0; - - float: left; -} - -.editor-percentcomplete-picker { - position: relative; - display: inline-block; - width: 16px; - height: 100%; - background: url("../lib/js/lib/slickgrid/images/pencil.gif") no-repeat center center; - overflow: visible; - z-index: 1000; - float: right; -} - -.editor-percentcomplete-helper { - border: 0 solid gray; - position: absolute; - top: -2px; - left: -9px; - background: url("../lib/js/lib/slickgrid/images/editor-helper-bg.gif") no-repeat top left; - padding-left: 9px; - - width: 120px; - height: 140px; - display: none; - overflow: visible; -} - -.editor-percentcomplete-wrapper { - background: beige; - padding: 20px 8px; - - width: 100%; - height: 98px; - border: 1px solid gray; - border-left: 0; -} - -.editor-percentcomplete-buttons { - float: right; -} - -.editor-percentcomplete-buttons button { - width: 80px; -} - -.editor-percentcomplete-slider { - float: left; -} - -.editor-percentcomplete-picker:hover .editor-percentcomplete-helper { - display: block; -} - -.editor-percentcomplete-helper:hover { - display: block; -} - -/* Slick.Editors.YesNoSelect */ -select.editor-yesno { - width: 100%; - margin: 0; - vertical-align: middle; -} - -/* Slick.Editors.Checkbox */ -input.editor-checkbox { - margin: 0; - height: 100%; - padding: 0; - border: 0; -} +@-webkit-keyframes slickgrid-invalid-hilite { + from { box-shadow: 0 0 6px red; } + to { box-shadow: none; } +} \ No newline at end of file diff --git a/frappe/public/js/lib/slickgrid/slick.core.js b/frappe/public/js/lib/slickgrid/slick.core.js index 5c4c69562a..2f097b1db6 100644 --- a/frappe/public/js/lib/slickgrid/slick.core.js +++ b/frappe/public/js/lib/slickgrid/slick.core.js @@ -348,7 +348,8 @@ Group.prototype.equals = function (group) { return this.value === group.value && this.count === group.count && - this.collapsed === group.collapsed; + this.collapsed === group.collapsed && + this.title === group.title; }; /*** @@ -369,6 +370,14 @@ * @type {Group} */ this.group = null; + + /*** + * Whether the totals have been fully initialized / calculated. + * Will be set to false for lazy-calculated group totals. + * @param initialized + * @type {Boolean} + */ + this.initialized = false; } GroupTotals.prototype = new NonDataItem(); diff --git a/frappe/public/js/lib/slickgrid/slick.dataview.js b/frappe/public/js/lib/slickgrid/slick.dataview.js index a4dcdd41be..f1c1b5e34f 100644 --- a/frappe/public/js/lib/slickgrid/slick.dataview.js +++ b/frappe/public/js/lib/slickgrid/slick.dataview.js @@ -60,7 +60,8 @@ aggregateCollapsed: false, aggregateChildGroups: false, collapsed: false, - displayTotalsRow: true + displayTotalsRow: true, + lazyTotalsCalculation: false }; var groupingInfos = []; var groups = []; @@ -266,7 +267,7 @@ */ function setAggregators(groupAggregators, includeCollapsed) { if (!groupingInfos.length) { - throw new Error("At least must setGrouping must be specified before calling setAggregators()."); + throw new Error("At least one grouping must be specified before calling setAggregators()."); } groupingInfos[0].aggregators = groupAggregators; @@ -304,7 +305,7 @@ function mapIdsToRows(idArray) { var rows = []; ensureRowsByIdCache(); - for (var i = 0; i < idArray.length; i++) { + for (var i = 0, l = idArray.length; i < l; i++) { var row = rowsById[idArray[i]]; if (row != null) { rows[rows.length] = row; @@ -315,7 +316,7 @@ function mapRowsToIds(rowArray) { var ids = []; - for (var i = 0; i < rowArray.length; i++) { + for (var i = 0, l = rowArray.length; i < l; i++) { if (rowArray[i] < rows.length) { ids[ids.length] = rows[rowArray[i]][idProperty]; } @@ -363,7 +364,22 @@ } function getItem(i) { - return rows[i]; + var item = rows[i]; + + // if this is a group row, make sure totals are calculated and update the title + if (item && item.__group && item.totals && !item.totals.initialized) { + var gi = groupingInfos[item.level]; + if (!gi.displayTotalsRow) { + calculateTotals(item.totals); + item.title = gi.formatter ? gi.formatter(item) : item.value; + } + } + // if this is a totals row, make sure it's calculated + else if (item && item.__groupTotals && !item.initialized) { + calculateTotals(item); + } + + return item; } function getItemMetadata(i) { @@ -372,7 +388,7 @@ return null; } - // overrides for setGrouping rows + // overrides for grouping rows if (item.__group) { return options.groupItemMetadataProvider.getGroupRowMetadata(item); } @@ -421,7 +437,7 @@ * @param varArgs Either a Slick.Group's "groupingKey" property, or a * variable argument list of grouping values denoting a unique path to the row. For * example, calling collapseGroup('high', '10%') will collapse the '10%' subgroup of - * the 'high' setGrouping. + * the 'high' group. */ function collapseGroup(varArgs) { var args = Array.prototype.slice.call(arguments); @@ -437,7 +453,7 @@ * @param varArgs Either a Slick.Group's "groupingKey" property, or a * variable argument list of grouping values denoting a unique path to the row. For * example, calling expandGroup('high', '10%') will expand the '10%' subgroup of - * the 'high' setGrouping. + * the 'high' group. */ function expandGroup(varArgs) { var args = Array.prototype.slice.call(arguments); @@ -457,7 +473,7 @@ var group; var val; var groups = []; - var groupsByVal = []; + var groupsByVal = {}; var r; var level = parentGroup ? parentGroup.level + 1 : 0; var gi = groupingInfos[level]; @@ -503,27 +519,50 @@ return groups; } - // TODO: lazy totals calculation - function calculateGroupTotals(group) { - // TODO: try moving iterating over groups into compiled accumulator + function calculateTotals(totals) { + var group = totals.group; var gi = groupingInfos[group.level]; var isLeafLevel = (group.level == groupingInfos.length); - var totals = new Slick.GroupTotals(); var agg, idx = gi.aggregators.length; + + if (!isLeafLevel && gi.aggregateChildGroups) { + // make sure all the subgroups are calculated + var i = group.groups.length; + while (i--) { + if (!group.groups[i].initialized) { + calculateTotals(group.groups[i]); + } + } + } + while (idx--) { agg = gi.aggregators[idx]; agg.init(); - gi.compiledAccumulators[idx].call(agg, - (!isLeafLevel && gi.aggregateChildGroups) ? group.groups : group.rows); + if (!isLeafLevel && gi.aggregateChildGroups) { + gi.compiledAccumulators[idx].call(agg, group.groups); + } else { + gi.compiledAccumulators[idx].call(agg, group.rows); + } agg.storeResult(totals); } + totals.initialized = true; + } + + function addGroupTotals(group) { + var gi = groupingInfos[group.level]; + var totals = new Slick.GroupTotals(); totals.group = group; group.totals = totals; + if (!gi.lazyTotalsCalculation) { + calculateTotals(totals); + } } - function calculateTotals(groups, level) { + function addTotals(groups, level) { level = level || 0; var gi = groupingInfos[level]; + var groupCollapsed = gi.collapsed; + var toggledGroups = toggledGroupsByLevel[level]; var idx = groups.length, g; while (idx--) { g = groups[idx]; @@ -532,38 +571,20 @@ continue; } - // Do a depth-first aggregation so that parent setGrouping aggregators can access subgroup totals. + // Do a depth-first aggregation so that parent group aggregators can access subgroup totals. if (g.groups) { - calculateTotals(g.groups, level + 1); + addTotals(g.groups, level + 1); } if (gi.aggregators.length && ( gi.aggregateEmpty || g.rows.length || (g.groups && g.groups.length))) { - calculateGroupTotals(g); + addGroupTotals(g); } - } - } - function finalizeGroups(groups, level) { - level = level || 0; - var gi = groupingInfos[level]; - var groupCollapsed = gi.collapsed; - var toggledGroups = toggledGroupsByLevel[level]; - var idx = groups.length, g; - while (idx--) { - g = groups[idx]; g.collapsed = groupCollapsed ^ toggledGroups[g.groupingKey]; g.title = gi.formatter ? gi.formatter(g) : g.value; - - if (g.groups) { - finalizeGroups(g.groups, level + 1); - // Let the non-leaf setGrouping rows get garbage-collected. - // They may have been used by aggregates that go over all of the descendants, - // but at this point they are no longer needed. - g.rows = []; - } } - } + } function flattenGroupedRows(groups, level) { level = level || 0; @@ -613,10 +634,10 @@ var filterInfo = getFunctionInfo(filter); var filterBody = filterInfo.body - .replace(/return false[;}]/gi, "{ continue _coreloop; }") - .replace(/return true[;}]/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }") - .replace(/return ([^;}]+?);/gi, - "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }"); + .replace(/return false\s*([;}]|$)/gi, "{ continue _coreloop; }$1") + .replace(/return true\s*([;}]|$)/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }$1") + .replace(/return ([^;}]+?)\s*([;}]|$)/gi, + "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }$2"); // This preserves the function template code after JS compression, // so that replace() commands still work as expected. @@ -645,10 +666,10 @@ var filterInfo = getFunctionInfo(filter); var filterBody = filterInfo.body - .replace(/return false[;}]/gi, "{ continue _coreloop; }") - .replace(/return true[;}]/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }") - .replace(/return ([^;}]+?);/gi, - "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }"); + .replace(/return false\s*([;}]|$)/gi, "{ continue _coreloop; }$1") + .replace(/return true\s*([;}]|$)/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }$1") + .replace(/return ([^;}]+?)\s*([;}]|$)/gi, + "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }$2"); // This preserves the function template code after JS compression, // so that replace() commands still work as expected. @@ -793,8 +814,7 @@ if (groupingInfos.length) { groups = extractGroups(newRows); if (groups.length) { - calculateTotals(groups); - finalizeGroups(groups); + addTotals(groups); newRows = flattenGroupedRows(groups); } } @@ -838,17 +858,50 @@ } } - function syncGridSelection(grid, preserveHidden) { + /*** + * Wires the grid and the DataView together to keep row selection tied to item ids. + * This is useful since, without it, the grid only knows about rows, so if the items + * move around, the same rows stay selected instead of the selection moving along + * with the items. + * + * NOTE: This doesn't work with cell selection model. + * + * @param grid {Slick.Grid} The grid to sync selection with. + * @param preserveHidden {Boolean} Whether to keep selected items that go out of the + * view due to them getting filtered out. + * @param preserveHiddenOnSelectionChange {Boolean} Whether to keep selected items + * that are currently out of the view (see preserveHidden) as selected when selection + * changes. + * @return {Slick.Event} An event that notifies when an internal list of selected row ids + * changes. This is useful since, in combination with the above two options, it allows + * access to the full list selected row ids, and not just the ones visible to the grid. + * @method syncGridSelection + */ + function syncGridSelection(grid, preserveHidden, preserveHiddenOnSelectionChange) { var self = this; - var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());; var inHandler; + var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows()); + var onSelectedRowIdsChanged = new Slick.Event(); + + function setSelectedRowIds(rowIds) { + if (selectedRowIds.join(",") == rowIds.join(",")) { + return; + } + + selectedRowIds = rowIds; + + onSelectedRowIdsChanged.notify({ + "grid": grid, + "ids": selectedRowIds + }, new Slick.EventData(), self); + } function update() { if (selectedRowIds.length > 0) { inHandler = true; var selectedRows = self.mapIdsToRows(selectedRowIds); if (!preserveHidden) { - selectedRowIds = self.mapRowsToIds(selectedRows); + setSelectedRowIds(self.mapRowsToIds(selectedRows)); } grid.setSelectedRows(selectedRows); inHandler = false; @@ -857,12 +910,22 @@ grid.onSelectedRowsChanged.subscribe(function(e, args) { if (inHandler) { return; } - selectedRowIds = self.mapRowsToIds(grid.getSelectedRows()); + var newSelectedRowIds = self.mapRowsToIds(grid.getSelectedRows()); + if (!preserveHiddenOnSelectionChange || !grid.getOptions().multiSelect) { + setSelectedRowIds(newSelectedRowIds); + } else { + // keep the ones that are hidden + var existing = $.grep(selectedRowIds, function(id) { return self.getRowById(id) === undefined; }); + // add the newly selected ones + setSelectedRowIds(existing.concat(newSelectedRowIds)); + } }); this.onRowsChanged.subscribe(update); this.onRowCountChanged.subscribe(update); + + return onSelectedRowIdsChanged; } function syncGridCellCssStyles(grid, key) { @@ -910,7 +973,7 @@ this.onRowCountChanged.subscribe(update); } - return { + $.extend(this, { // methods "beginUpdate": beginUpdate, "endUpdate": endUpdate, @@ -956,7 +1019,7 @@ "onRowCountChanged": onRowCountChanged, "onRowsChanged": onRowsChanged, "onPagingInfoChanged": onPagingInfoChanged - }; + }); } function AvgAggregator(field) { diff --git a/frappe/public/js/lib/slickgrid/slick.editors.js b/frappe/public/js/lib/slickgrid/slick.editors.js index f3ef8e9d28..04b20d2fad 100644 --- a/frappe/public/js/lib/slickgrid/slick.editors.js +++ b/frappe/public/js/lib/slickgrid/slick.editors.js @@ -304,14 +304,14 @@ this.loadValue = function (item) { defaultValue = !!item[args.column.field]; if (defaultValue) { - $select.attr("checked", "checked"); + $select.prop('checked', true); } else { - $select.removeAttr("checked"); + $select.prop('checked', false); } }; this.serializeValue = function () { - return !!$select.attr("checked"); + return $select.prop('checked'); }; this.applyValue = function (item, state) { diff --git a/frappe/public/js/lib/slickgrid/slick.formatters.js b/frappe/public/js/lib/slickgrid/slick.formatters.js new file mode 100644 index 0000000000..a31aaf9319 --- /dev/null +++ b/frappe/public/js/lib/slickgrid/slick.formatters.js @@ -0,0 +1,59 @@ +/*** + * Contains basic SlickGrid formatters. + * + * NOTE: These are merely examples. You will most likely need to implement something more + * robust/extensible/localizable/etc. for your use! + * + * @module Formatters + * @namespace Slick + */ + +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "Formatters": { + "PercentComplete": PercentCompleteFormatter, + "PercentCompleteBar": PercentCompleteBarFormatter, + "YesNo": YesNoFormatter, + "Checkmark": CheckmarkFormatter + } + } + }); + + function PercentCompleteFormatter(row, cell, value, columnDef, dataContext) { + if (value == null || value === "") { + return "-"; + } else if (value < 50) { + return "" + value + "%"; + } else { + return "" + value + "%"; + } + } + + function PercentCompleteBarFormatter(row, cell, value, columnDef, dataContext) { + if (value == null || value === "") { + return ""; + } + + var color; + + if (value < 30) { + color = "red"; + } else if (value < 70) { + color = "silver"; + } else { + color = "green"; + } + + return ""; + } + + function YesNoFormatter(row, cell, value, columnDef, dataContext) { + return value ? "Yes" : "No"; + } + + function CheckmarkFormatter(row, cell, value, columnDef, dataContext) { + return value ? "" : ""; + } +})(jQuery); diff --git a/frappe/public/js/lib/slickgrid/slick.grid.css b/frappe/public/js/lib/slickgrid/slick.grid.css index 1aeb3affe3..6a416db1ef 100644 --- a/frappe/public/js/lib/slickgrid/slick.grid.css +++ b/frappe/public/js/lib/slickgrid/slick.grid.css @@ -48,6 +48,8 @@ classes should alter those! width: 8px; height: 5px; margin-left: 4px; + margin-top: 6px; + float: left; } .slick-sort-indicator-desc { diff --git a/frappe/public/js/lib/slickgrid/slick.grid.js b/frappe/public/js/lib/slickgrid/slick.grid.js index 0f10c57797..c12bae9bba 100644 --- a/frappe/public/js/lib/slickgrid/slick.grid.js +++ b/frappe/public/js/lib/slickgrid/slick.grid.js @@ -1,13 +1,13 @@ /** * @license - * (c) 2009-2012 Michael Leibman + * (c) 2009-2013 Michael Leibman * michael{dot}leibman{at}gmail{dot}com * http://github.com/mleibman/slickgrid * * Distributed under MIT license. * All rights reserved. * - * SlickGrid v2.1 + * SlickGrid v2.2 * * NOTES: * Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods. @@ -85,7 +85,8 @@ if (typeof Slick === "undefined") { fullWidthRows: false, multiColumnSort: false, defaultFormatter: defaultFormatter, - forceSyncScrolling: false + forceSyncScrolling: false, + addNewRowCssClass: "new-row" }; var columnDefaults = { @@ -133,7 +134,6 @@ if (typeof Slick === "undefined") { var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding cellWidthDiff = 0, cellHeightDiff = 0; var absoluteColumnMinWidth; - var numberOfRows = 0; var tabbingDirection = 1; var activePosX; @@ -177,6 +177,11 @@ if (typeof Slick === "undefined") { var counter_rows_rendered = 0; var counter_rows_removed = 0; + // These two variables work around a bug with inertial scrolling in Webkit/Blink on Mac. + // See http://crbug.com/312427. + var rowNodeFromLastMouseWheelEvent; // this node must not be deleted while inertial scrolling + var zombieRowNodeFromLastMouseWheelEvent; // node that was hidden instead of getting deleted + ////////////////////////////////////////////////////////////////////////////////////////////// // Initialization @@ -299,6 +304,7 @@ if (typeof Slick === "undefined") { $container .bind("resize.slickgrid", resizeCanvas); $viewport + //.bind("click", handleClick) .bind("scroll", handleScroll); $headerScroller .bind("contextmenu", handleHeaderContextMenu) @@ -320,6 +326,12 @@ if (typeof Slick === "undefined") { .bind("dragend", handleDragEnd) .delegate(".slick-cell", "mouseenter", handleMouseEnter) .delegate(".slick-cell", "mouseleave", handleMouseLeave); + + // Work around http://crbug.com/312427. + if (navigator.userAgent.toLowerCase().match(/webkit/) && + navigator.userAgent.toLowerCase().match(/macintosh/)) { + $canvas.bind("mousewheel", handleMouseWheel); + } } } @@ -547,9 +559,10 @@ if (typeof Slick === "undefined") { for (var i = 0; i < columns.length; i++) { var m = columns[i]; - var header = $("
") + var header = $("
") .html("" + m.name + "") .width(m.width - headerColumnWidthDiff) + .attr("id", "" + uid + m.id) .attr("title", m.toolTip || "") .data("column", m) .addClass(m.headerCssClass || "") @@ -666,8 +679,8 @@ if (typeof Slick === "undefined") { tolerance: "intersection", helper: "clone", placeholder: "slick-sortable-placeholder ui-state-default slick-header-column", - forcePlaceholderSize: true, start: function (e, ui) { + ui.placeholder.width(ui.helper.outerWidth() - headerColumnWidthDiff); $(ui.helper).addClass("slick-header-column-active"); }, beforeStop: function (e, ui) { @@ -878,23 +891,27 @@ if (typeof Slick === "undefined") { el = $("").appendTo($headers); headerColumnWidthDiff = headerColumnHeightDiff = 0; - $.each(h, function (n, val) { - headerColumnWidthDiff += parseFloat(el.css(val)) || 0; - }); - $.each(v, function (n, val) { - headerColumnHeightDiff += parseFloat(el.css(val)) || 0; - }); + if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") { + $.each(h, function (n, val) { + headerColumnWidthDiff += parseFloat(el.css(val)) || 0; + }); + $.each(v, function (n, val) { + headerColumnHeightDiff += parseFloat(el.css(val)) || 0; + }); + } el.remove(); var r = $("
").appendTo($canvas); el = $("").appendTo(r); cellWidthDiff = cellHeightDiff = 0; - $.each(h, function (n, val) { - cellWidthDiff += parseFloat(el.css(val)) || 0; - }); - $.each(v, function (n, val) { - cellHeightDiff += parseFloat(el.css(val)) || 0; - }); + if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") { + $.each(h, function (n, val) { + cellWidthDiff += parseFloat(el.css(val)) || 0; + }); + $.each(v, function (n, val) { + cellHeightDiff += parseFloat(el.css(val)) || 0; + }); + } r.remove(); absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff); @@ -975,8 +992,8 @@ if (typeof Slick === "undefined") { unregisterPlugin(plugins[i]); } - if (options.enableColumnReorder && $headers.sortable) { - $headers.sortable("destroy"); + if (options.enableColumnReorder) { + $headers.filter(":ui-sortable").sortable("destroy"); } unbindAncestorScrollEvents(); @@ -1044,7 +1061,7 @@ if (typeof Slick === "undefined") { shrinkLeeway -= shrinkSize; widths[i] -= shrinkSize; } - if (prevTotal == total) { // avoid infinite loop + if (prevTotal <= total) { // avoid infinite loop break; } prevTotal = total; @@ -1056,14 +1073,18 @@ if (typeof Slick === "undefined") { var growProportion = availWidth / total; for (i = 0; i < columns.length && total < availWidth; i++) { c = columns[i]; - if (!c.resizable || c.maxWidth <= c.width) { - continue; + var currentWidth = widths[i]; + var growSize; + + if (!c.resizable || c.maxWidth <= currentWidth) { + growSize = 0; + } else { + growSize = Math.min(Math.floor(growProportion * currentWidth) - currentWidth, (c.maxWidth - currentWidth) || 1000000) || 1; } - var growSize = Math.min(Math.floor(growProportion * c.width) - c.width, (c.maxWidth - c.width) || 1000000) || 1; total += growSize; widths[i] += growSize; } - if (prevTotal == total) { // avoid infinite loop + if (prevTotal >= total) { // avoid infinite loop break; } prevTotal = total; @@ -1257,6 +1278,10 @@ if (typeof Slick === "undefined") { } } + function getDataLengthIncludingAddNew() { + return getDataLength() + (options.enableAddRow ? 1 : 0); + } + function getDataItem(i) { if (data.getItem) { return data.getItem(i); @@ -1291,6 +1316,10 @@ if (typeof Slick === "undefined") { } } + function getContainerNode() { + return $container.get(0); + } + ////////////////////////////////////////////////////////////////////////////////////////////// // Rendering / Scrolling @@ -1330,7 +1359,7 @@ if (typeof Slick === "undefined") { if (value == null) { return ""; } else { - return value.toString().replace(/&/g,"&").replace(//g,">"); + return (value + "").replace(/&/g,"&").replace(//g,">"); } } @@ -1371,14 +1400,18 @@ if (typeof Slick === "undefined") { return item[columnDef.field]; } - function appendRowHtml(stringArray, row, range) { + function appendRowHtml(stringArray, row, range, dataLength) { var d = getDataItem(row); - var dataLoading = row < getDataLength() && !d; + var dataLoading = row < dataLength && !d; var rowCss = "slick-row" + (dataLoading ? " loading" : "") + (row === activeRow ? " active" : "") + (row % 2 == 1 ? " odd" : " even"); + if (!d) { + rowCss += " " + options.addNewRowCssClass; + } + var metadata = data.getItemMetadata && data.getItemMetadata(row); if (metadata && metadata.cssClasses) { @@ -1406,7 +1439,7 @@ if (typeof Slick === "undefined") { break; } - appendCellHtml(stringArray, row, i, colspan); + appendCellHtml(stringArray, row, i, colspan, d); } if (colspan > 1) { @@ -1417,9 +1450,8 @@ if (typeof Slick === "undefined") { stringArray.push("
"); } - function appendCellHtml(stringArray, row, cell, colspan) { + function appendCellHtml(stringArray, row, cell, colspan, item) { var m = columns[cell]; - var d = getDataItem(row); var cellCss = "slick-cell l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) + (m.cssClass ? " " + m.cssClass : ""); if (row === activeRow && cell === activeCell) { @@ -1436,9 +1468,9 @@ if (typeof Slick === "undefined") { stringArray.push("
"); // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet) - if (d) { - var value = getDataItemValueForColumn(d, m); - stringArray.push(getFormatter(row, m)(row, cell, value, m, d)); + if (item) { + var value = getDataItemValueForColumn(item, m); + stringArray.push(getFormatter(row, m)(row, cell, value, m, item)); } stringArray.push("
"); @@ -1476,7 +1508,14 @@ if (typeof Slick === "undefined") { if (!cacheEntry) { return; } - $canvas[0].removeChild(cacheEntry.rowNode); + + if (rowNodeFromLastMouseWheelEvent == cacheEntry.rowNode) { + cacheEntry.rowNode.style.display = 'none'; + zombieRowNodeFromLastMouseWheelEvent = rowNodeFromLastMouseWheelEvent; + } else { + $canvas[0].removeChild(cacheEntry.rowNode); + } + delete rowsCache[row]; delete postProcessedRows[row]; renderedRows--; @@ -1526,6 +1565,8 @@ if (typeof Slick === "undefined") { ensureCellNodesInRowsCache(row); + var d = getDataItem(row); + for (var columnIdx in cacheEntry.cellNodesByColumnIdx) { if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) { continue; @@ -1533,7 +1574,6 @@ if (typeof Slick === "undefined") { columnIdx = columnIdx | 0; var m = columns[columnIdx], - d = getDataItem(row), node = cacheEntry.cellNodesByColumnIdx[columnIdx]; if (row === activeRow && columnIdx === activeCell && currentEditor) { @@ -1560,7 +1600,7 @@ if (typeof Slick === "undefined") { function resizeCanvas() { if (!initialized) { return; } if (options.autoHeight) { - viewportH = options.rowHeight * (getDataLength() + (options.enableAddRow ? 1 : 0)); + viewportH = options.rowHeight * getDataLengthIncludingAddNew(); } else { viewportH = getViewportHeight(); } @@ -1577,22 +1617,27 @@ if (typeof Slick === "undefined") { updateRowCount(); handleScroll(); + // Since the width has changed, force the render() to reevaluate virtually rendered cells. + lastRenderedScrollLeft = -1; render(); } function updateRowCount() { if (!initialized) { return; } - numberOfRows = getDataLength() + - (options.enableAddRow ? 1 : 0) + + + var dataLengthIncludingAddNew = getDataLengthIncludingAddNew(); + var numberOfRows = dataLengthIncludingAddNew + (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0); var oldViewportHasVScroll = viewportHasVScroll; // with autoHeight, we do not need to accommodate the vertical scroll bar viewportHasVScroll = !options.autoHeight && (numberOfRows * options.rowHeight > viewportH); + makeActiveCellNormal(); + // remove the rows that are now outside of the data range // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows - var l = options.enableAddRow ? getDataLength() : getDataLength() - 1; + var l = dataLengthIncludingAddNew - 1; for (var i in rowsCache) { if (i >= l) { removeRowFromCache(i); @@ -1678,7 +1723,7 @@ if (typeof Slick === "undefined") { } range.top = Math.max(0, range.top); - range.bottom = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, range.bottom); + range.bottom = Math.min(getDataLengthIncludingAddNew() - 1, range.bottom); range.leftPx -= viewportW; range.rightPx += viewportW; @@ -1747,7 +1792,7 @@ if (typeof Slick === "undefined") { var totalCellsAdded = 0; var colspan; - for (var row = range.top; row <= range.bottom; row++) { + for (var row = range.top, btm = range.bottom; row <= btm; row++) { cacheEntry = rowsCache[row]; if (!cacheEntry) { continue; @@ -1764,6 +1809,8 @@ if (typeof Slick === "undefined") { var metadata = data.getItemMetadata && data.getItemMetadata(row); metadata = metadata && metadata.columns; + var d = getDataItem(row); + // TODO: shorten this loop (index? heuristics? binary search?) for (var i = 0, ii = columns.length; i < ii; i++) { // Cells to the right are outside the range. @@ -1787,7 +1834,7 @@ if (typeof Slick === "undefined") { } if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) { - appendCellHtml(stringArray, row, i, colspan); + appendCellHtml(stringArray, row, i, colspan, d); cellsAdded++; } @@ -1824,9 +1871,10 @@ if (typeof Slick === "undefined") { var parentNode = $canvas[0], stringArray = [], rows = [], - needToReselectCell = false; + needToReselectCell = false, + dataLength = getDataLength(); - for (var i = range.top; i <= range.bottom; i++) { + for (var i = range.top, ii = range.bottom; i <= ii; i++) { if (rowsCache[i]) { continue; } @@ -1851,7 +1899,7 @@ if (typeof Slick === "undefined") { "cellRenderQueue": [] }; - appendRowHtml(stringArray, i, range); + appendRowHtml(stringArray, i, range, dataLength); if (activeCellNode && activeRow === i) { needToReselectCell = true; } @@ -1910,7 +1958,7 @@ if (typeof Slick === "undefined") { renderRows(rendered); postProcessFromRow = visible.top; - postProcessToRow = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, visible.bottom); + postProcessToRow = Math.min(getDataLengthIncludingAddNew() - 1, visible.bottom); startPostProcessing(); lastRenderedScrollTop = scrollTop; @@ -1982,10 +2030,11 @@ if (typeof Slick === "undefined") { } function asyncPostProcessRows() { + var dataLength = getDataLength(); while (postProcessFromRow <= postProcessToRow) { var row = (vScrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--; var cacheEntry = rowsCache[row]; - if (!cacheEntry || row >= getDataLength()) { + if (!cacheEntry || row >= dataLength) { continue; } @@ -2106,6 +2155,17 @@ if (typeof Slick === "undefined") { ////////////////////////////////////////////////////////////////////////////////////////////// // Interactivity + function handleMouseWheel(e) { + var rowNode = $(e.target).closest(".slick-row")[0]; + if (rowNode != rowNodeFromLastMouseWheelEvent) { + if (zombieRowNodeFromLastMouseWheelEvent && zombieRowNodeFromLastMouseWheelEvent != rowNode) { + $canvas[0].removeChild(zombieRowNodeFromLastMouseWheelEvent); + zombieRowNodeFromLastMouseWheelEvent = null; + } + rowNodeFromLastMouseWheelEvent = rowNode; + } + } + function handleDragInit(e, dd) { var cell = getCellFromEvent(e); if (!cell || !cellExists(cell.row, cell.cell)) { @@ -2155,6 +2215,12 @@ if (typeof Slick === "undefined") { return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event) } cancelEditAndSetFocus(); + } else if (e.which == 34) { + navigatePageDown(); + handled = true; + } else if (e.which == 33) { + navigatePageUp(); + handled = true; } else if (e.which == 37) { handled = navigateLeft(); } else if (e.which == 39) { @@ -2205,7 +2271,8 @@ if (typeof Slick === "undefined") { if (!currentEditor) { // if this click resulted in some cell child node getting focus, // don't steal it back - keyboard events will still bubble up - if (e.target != document.activeElement) { + // IE9+ seems to default DIVs to tabIndex=0 instead of -1, so check for cell clicks directly. + if (e.target != document.activeElement || $(e.target).hasClass("slick-cell")) { setFocus(); } } @@ -2223,7 +2290,7 @@ if (typeof Slick === "undefined") { if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) { if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) { scrollRowIntoView(cell.row, false); - setActiveCellInternal(getCellNode(cell.row, cell.cell), (cell.row === getDataLength()) || options.autoEdit); + setActiveCellInternal(getCellNode(cell.row, cell.cell)); } } } @@ -2406,7 +2473,7 @@ if (typeof Slick === "undefined") { } } - function setActiveCellInternal(newCell, editMode) { + function setActiveCellInternal(newCell, opt_editMode) { if (activeCellNode !== null) { makeActiveCellNormal(); $(activeCellNode).removeClass("active"); @@ -2422,10 +2489,14 @@ if (typeof Slick === "undefined") { activeRow = getRowFromNode(activeCellNode.parentNode); activeCell = activePosX = getCellFromNode(activeCellNode); + if (opt_editMode == null) { + opt_editMode = (activeRow == getDataLength()) || options.autoEdit; + } + $(activeCellNode).addClass("active"); $(rowsCache[activeRow].rowNode).addClass("active"); - if (options.editable && editMode && isCellPotentiallyEditable(activeRow, activeCell)) { + if (options.editable && opt_editMode && isCellPotentiallyEditable(activeRow, activeCell)) { clearTimeout(h_editorLoader); if (options.asyncEditorLoading) { @@ -2447,7 +2518,10 @@ if (typeof Slick === "undefined") { function clearTextSelection() { if (document.selection && document.selection.empty) { - document.selection.empty(); + try { + //IE fails here if selected element is not in dom + document.selection.empty(); + } catch (e) { } } else if (window.getSelection) { var sel = window.getSelection(); if (sel && sel.removeAllRanges) { @@ -2457,13 +2531,14 @@ if (typeof Slick === "undefined") { } function isCellPotentiallyEditable(row, cell) { + var dataLength = getDataLength(); // is the data for this row loaded? - if (row < getDataLength() && !getDataItem(row)) { + if (row < dataLength && !getDataItem(row)) { return false; } // are we in the Add New row? can we create new from this cell? - if (columns[cell].cannotTriggerInsert && row >= getDataLength()) { + if (columns[cell].cannotTriggerInsert && row >= dataLength) { return false; } @@ -2489,7 +2564,7 @@ if (typeof Slick === "undefined") { if (d) { var column = columns[activeCell]; var formatter = getFormatter(activeRow, column); - activeCellNode.innerHTML = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, getDataItem(activeRow)); + activeCellNode.innerHTML = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d); invalidatePostProcessingResults(activeRow); } } @@ -2680,6 +2755,47 @@ if (typeof Slick === "undefined") { render(); } + function scrollPage(dir) { + var deltaRows = dir * numVisibleRows; + scrollTo((getRowFromPosition(scrollTop) + deltaRows) * options.rowHeight); + render(); + + if (options.enableCellNavigation && activeRow != null) { + var row = activeRow + deltaRows; + var dataLengthIncludingAddNew = getDataLengthIncludingAddNew(); + if (row >= dataLengthIncludingAddNew) { + row = dataLengthIncludingAddNew - 1; + } + if (row < 0) { + row = 0; + } + + var cell = 0, prevCell = null; + var prevActivePosX = activePosX; + while (cell <= activePosX) { + if (canCellBeActive(row, cell)) { + prevCell = cell; + } + cell += getColspan(row, cell); + } + + if (prevCell !== null) { + setActiveCellInternal(getCellNode(row, prevCell)); + activePosX = prevActivePosX; + } else { + resetActiveCell(); + } + } + } + + function navigatePageDown() { + scrollPage(1); + } + + function navigatePageUp() { + scrollPage(-1); + } + function getColspan(row, cell) { var metadata = data.getItemMetadata && data.getItemMetadata(row); if (!metadata || !metadata.columns) { @@ -2770,8 +2886,9 @@ if (typeof Slick === "undefined") { function gotoDown(row, cell, posX) { var prevCell; + var dataLengthIncludingAddNew = getDataLengthIncludingAddNew(); while (true) { - if (++row >= getDataLength() + (options.enableAddRow ? 1 : 0)) { + if (++row >= dataLengthIncludingAddNew) { return null; } @@ -2832,7 +2949,8 @@ if (typeof Slick === "undefined") { } var firstFocusableCell = null; - while (++row < getDataLength() + (options.enableAddRow ? 1 : 0)) { + var dataLengthIncludingAddNew = getDataLengthIncludingAddNew(); + while (++row < dataLengthIncludingAddNew) { firstFocusableCell = findFirstFocusableCell(row); if (firstFocusableCell !== null) { return { @@ -2847,7 +2965,7 @@ if (typeof Slick === "undefined") { function gotoPrev(row, cell, posX) { if (row == null && cell == null) { - row = getDataLength() + (options.enableAddRow ? 1 : 0) - 1; + row = getDataLengthIncludingAddNew() - 1; cell = posX = columns.length - 1; if (canCellBeActive(row, cell)) { return { @@ -2947,11 +3065,11 @@ if (typeof Slick === "undefined") { if (pos) { var isAddNewRow = (pos.row == getDataLength()); scrollCellIntoView(pos.row, pos.cell, !isAddNewRow); - setActiveCellInternal(getCellNode(pos.row, pos.cell), isAddNewRow || options.autoEdit); + setActiveCellInternal(getCellNode(pos.row, pos.cell)); activePosX = pos.posX; return true; } else { - setActiveCellInternal(getCellNode(activeRow, activeCell), (activeRow == getDataLength()) || options.autoEdit); + setActiveCellInternal(getCellNode(activeRow, activeCell)); return false; } } @@ -2979,7 +3097,7 @@ if (typeof Slick === "undefined") { } function canCellBeActive(row, cell) { - if (!options.enableCellNavigation || row >= getDataLength() + (options.enableAddRow ? 1 : 0) || + if (!options.enableCellNavigation || row >= getDataLengthIncludingAddNew() || row < 0 || cell >= columns.length || cell < 0) { return false; } @@ -3064,10 +3182,20 @@ if (typeof Slick === "undefined") { execute: function () { this.editor.applyValue(item, this.serializedValue); updateRow(this.row); + trigger(self.onCellChange, { + row: activeRow, + cell: activeCell, + item: item + }); }, undo: function () { this.editor.applyValue(item, this.prevSerializedValue); updateRow(this.row); + trigger(self.onCellChange, { + row: activeRow, + cell: activeCell, + item: item + }); } }; @@ -3079,11 +3207,6 @@ if (typeof Slick === "undefined") { makeActiveCellNormal(); } - trigger(self.onCellChange, { - row: activeRow, - cell: activeCell, - item: item - }); } else { var newItem = {}; currentEditor.applyValue(newItem, currentEditor.serializeValue()); @@ -3094,9 +3217,10 @@ if (typeof Slick === "undefined") { // check whether the lock has been re-acquired by event handlers return !getEditorLock().isActive(); } else { - // TODO: remove and put in onValidationError handlers in examples + // Re-add the CSS class to trigger transitions, if any. + $(activeCellNode).removeClass("invalid"); + $(activeCellNode).width(); // force layout $(activeCellNode).addClass("invalid"); - $(activeCellNode).stop(true, true).effect("highlight", {color: "red"}, 300); trigger(self.onValidationError, { editor: currentEditor, @@ -3232,6 +3356,7 @@ if (typeof Slick === "undefined") { "setSelectionModel": setSelectionModel, "getSelectedRows": getSelectedRows, "setSelectedRows": setSelectedRows, + "getContainerNode": getContainerNode, "render": render, "invalidate": invalidate, @@ -3269,6 +3394,8 @@ if (typeof Slick === "undefined") { "navigateDown": navigateDown, "navigateLeft": navigateLeft, "navigateRight": navigateRight, + "navigatePageUp": navigatePageUp, + "navigatePageDown": navigatePageDown, "gotoCell": gotoCell, "getTopPanel": getTopPanel, "setTopPanelVisibility": setTopPanelVisibility, diff --git a/frappe/public/js/lib/slickgrid/slick.groupitemmetadataprovider.js b/frappe/public/js/lib/slickgrid/slick.groupitemmetadataprovider.js index 49852a469a..18ee1c6012 100644 --- a/frappe/public/js/lib/slickgrid/slick.groupitemmetadataprovider.js +++ b/frappe/public/js/lib/slickgrid/slick.groupitemmetadataprovider.js @@ -33,7 +33,9 @@ toggleCssClass: "slick-group-toggle", toggleExpandedCssClass: "expanded", toggleCollapsedCssClass: "collapsed", - enableExpandCollapse: true + enableExpandCollapse: true, + groupFormatter: defaultGroupCellFormatter, + totalsFormatter: defaultTotalsCellFormatter }; options = $.extend(true, {}, _defaults, options); @@ -77,6 +79,12 @@ function handleGridClick(e, args) { var item = this.getDataItem(args.row); if (item && item instanceof Slick.Group && $(e.target).hasClass(options.toggleCssClass)) { + var range = _grid.getRenderedRange(); + this.getData().setRefreshHints({ + ignoreDiffsBefore: range.top, + ignoreDiffsAfter: range.bottom + }); + if (item.collapsed) { this.getData().expandGroup(item.groupingKey); } else { @@ -95,6 +103,12 @@ if (activeCell) { var item = this.getDataItem(activeCell.row); if (item && item instanceof Slick.Group) { + var range = _grid.getRenderedRange(); + this.getData().setRefreshHints({ + ignoreDiffsBefore: range.top, + ignoreDiffsAfter: range.bottom + }); + if (item.collapsed) { this.getData().expandGroup(item.groupingKey); } else { @@ -116,7 +130,7 @@ columns: { 0: { colspan: "*", - formatter: defaultGroupCellFormatter, + formatter: options.groupFormatter, editor: null } } @@ -128,7 +142,7 @@ selectable: false, focusable: options.totalsFocusable, cssClasses: options.totalsCssClass, - formatter: defaultTotalsCellFormatter, + formatter: options.totalsFormatter, editor: null }; } diff --git a/frappe/public/js/lib/slickgrid/slick.remotemodel.js b/frappe/public/js/lib/slickgrid/slick.remotemodel.js new file mode 100644 index 0000000000..6d21020e65 --- /dev/null +++ b/frappe/public/js/lib/slickgrid/slick.remotemodel.js @@ -0,0 +1,173 @@ +(function ($) { + /*** + * A sample AJAX data store implementation. + * Right now, it's hooked up to load Hackernews stories, but can + * easily be extended to support any JSONP-compatible backend that accepts paging parameters. + */ + function RemoteModel() { + // private + var PAGESIZE = 50; + var data = {length: 0}; + var searchstr = ""; + var sortcol = null; + var sortdir = 1; + var h_request = null; + var req = null; // ajax request + + // events + var onDataLoading = new Slick.Event(); + var onDataLoaded = new Slick.Event(); + + + function init() { + } + + + function isDataLoaded(from, to) { + for (var i = from; i <= to; i++) { + if (data[i] == undefined || data[i] == null) { + return false; + } + } + + return true; + } + + + function clear() { + for (var key in data) { + delete data[key]; + } + data.length = 0; + } + + + function ensureData(from, to) { + if (req) { + req.abort(); + for (var i = req.fromPage; i <= req.toPage; i++) + data[i * PAGESIZE] = undefined; + } + + if (from < 0) { + from = 0; + } + + if (data.length > 0) { + to = Math.min(to, data.length - 1); + } + + var fromPage = Math.floor(from / PAGESIZE); + var toPage = Math.floor(to / PAGESIZE); + + while (data[fromPage * PAGESIZE] !== undefined && fromPage < toPage) + fromPage++; + + while (data[toPage * PAGESIZE] !== undefined && fromPage < toPage) + toPage--; + + if (fromPage > toPage || ((fromPage == toPage) && data[fromPage * PAGESIZE] !== undefined)) { + // TODO: look-ahead + onDataLoaded.notify({from: from, to: to}); + return; + } + + var url = "http://api.thriftdb.com/api.hnsearch.com/items/_search?filter[fields][type][]=submission&q=" + searchstr + "&start=" + (fromPage * PAGESIZE) + "&limit=" + (((toPage - fromPage) * PAGESIZE) + PAGESIZE); + + if (sortcol != null) { + url += ("&sortby=" + sortcol + ((sortdir > 0) ? "+asc" : "+desc")); + } + + if (h_request != null) { + clearTimeout(h_request); + } + + h_request = setTimeout(function () { + for (var i = fromPage; i <= toPage; i++) + data[i * PAGESIZE] = null; // null indicates a 'requested but not available yet' + + onDataLoading.notify({from: from, to: to}); + + req = $.jsonp({ + url: url, + callbackParameter: "callback", + cache: true, + success: onSuccess, + error: function () { + onError(fromPage, toPage) + } + }); + req.fromPage = fromPage; + req.toPage = toPage; + }, 50); + } + + + function onError(fromPage, toPage) { + alert("error loading pages " + fromPage + " to " + toPage); + } + + function onSuccess(resp) { + var from = resp.request.start, to = from + resp.results.length; + data.length = Math.min(parseInt(resp.hits),1000); // limitation of the API + + for (var i = 0; i < resp.results.length; i++) { + var item = resp.results[i].item; + + // Old IE versions can't parse ISO dates, so change to universally-supported format. + item.create_ts = item.create_ts.replace(/^(\d+)-(\d+)-(\d+)T(\d+:\d+:\d+)Z$/, "$2/$3/$1 $4 UTC"); + item.create_ts = new Date(item.create_ts); + + data[from + i] = item; + data[from + i].index = from + i; + } + + req = null; + + onDataLoaded.notify({from: from, to: to}); + } + + + function reloadData(from, to) { + for (var i = from; i <= to; i++) + delete data[i]; + + ensureData(from, to); + } + + + function setSort(column, dir) { + sortcol = column; + sortdir = dir; + clear(); + } + + function setSearch(str) { + searchstr = str; + clear(); + } + + + init(); + + return { + // properties + "data": data, + + // methods + "clear": clear, + "isDataLoaded": isDataLoaded, + "ensureData": ensureData, + "reloadData": reloadData, + "setSort": setSort, + "setSearch": setSearch, + + // events + "onDataLoading": onDataLoading, + "onDataLoaded": onDataLoaded + }; + } + + // Slick.Data.RemoteModel + $.extend(true, window, { Slick: { Data: { RemoteModel: RemoteModel }}}); +})(jQuery);