diff options
-rw-r--r-- | examples/example-composite-editor-item-details.html | 233 | ||||
-rw-r--r-- | examples/examples.css | 2 | ||||
-rw-r--r-- | examples/slick.compositeeditor.js | 213 | ||||
-rw-r--r-- | slick.grid.js | 18 |
4 files changed, 458 insertions, 8 deletions
diff --git a/examples/example-composite-editor-item-details.html b/examples/example-composite-editor-item-details.html new file mode 100644 index 0000000..978067f --- /dev/null +++ b/examples/example-composite-editor-item-details.html @@ -0,0 +1,233 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>SlickGrid example: CompositeEditor</title> + <link rel="stylesheet" href="../slick.grid.css" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="../css/smoothness/jquery-ui-1.8.5.custom.css" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="examples.css" type="text/css" media="screen" charset="utf-8" /> + <style> + .cell-title { + font-weight: bold; + } + + .cell-effort-driven { + text-align: center; + } + + .item-details-form { + display:inline-block; + border:1px solid black; + margin:8px; + padding:10px; + background: #efefef; + -moz-box-shadow: 0px 0px 15px black; + -webkit-box-shadow: 0px 0px 15px black; + box-shadow: 0px 0px 15px black; + + position:absolute; + top: 10px; + left: 150px; + } + + .item-details-form-buttons { + float: right; + } + + .item-details-row { + + } + + .item-details-label { + margin-left:10px; + margin-top:20px; + display:block; + font-weight:bold; + } + + .item-details-editor-container { + width:200px; + height:20px; + border:1px solid silver; + background:white; + display:block; + margin:10px; + margin-top:4px; + padding:0; + padding-left:4px; + padding-right:4px; + } + </style> + </head> + <body> + <div style="width:600px;float:left;"> + <div id="myGrid" style="width:100%;height:500px;"></div> + </div> + + <div class="options-panel" style="width:320px;margin-left:650px;"> + <h2>Demonstrates:</h2> + <ul> + <li>using a CompositeEditor to implement detached item edit form</li> + </ul> + + <h2>Options:</h2> + <button onclick="openDetails()">Open Item Edit for active row</button> + + + </div> + + + <script id="itemDetailsTemplate" type="text/x-jquery-tmpl"> + <div class='item-details-form'> + {{each columns}} + <div class='item-details-label'> + ${name} + </div> + <div class='item-details-editor-container' data-editorid='${id}'></div> + {{/each}} + + <hr/> + <div class='item-details-form-buttons'> + <button data-action='save'>Save</button> + <button data-action='cancel'>Cancel</button> + </div> + </div> + </script> + + + + <script src="../lib/firebugx.js"></script> + + <script src="../lib/jquery-1.4.3.min.js"></script> + <script src="../lib/jquery-ui-1.8.5.custom.min.js"></script> + <script src="../lib/jquery.event.drag-2.0.min.js"></script> + <script src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script> + + <script src="../slick.core.js"></script> + <script src="../plugins/slick.cellrangeselector.js"></script> + <script src="../plugins/slick.cellselectionmodel.js"></script> + <script src="../slick.editors.js"></script> + <script src="../slick.grid.js"></script> + <script src="slick.compositeeditor.js"></script> + + <script> + function requiredFieldValidator(value) { + if (value == null || value == undefined || !value.length) + return {valid:false, msg:"This is a required field"}; + else + return {valid:true, msg:null}; + } + + var grid; + var data = []; + var columns = [ + {id:"title", name:"Title", field:"title", width:120, cssClass:"cell-title", editor:TextCellEditor, validator:requiredFieldValidator}, + {id:"desc", name:"Description", field:"description", width:100, editor:TextCellEditor}, + {id:"duration", name:"Duration", field:"duration", editor:TextCellEditor}, + {id:"%", name:"% Complete", field:"percentComplete", width:80, resizable:false, formatter:GraphicalPercentCompleteCellFormatter, editor:PercentCompleteCellEditor}, + {id:"start", name:"Start", field:"start", minWidth:60, editor:DateCellEditor}, + {id:"finish", name:"Finish", field:"finish", minWidth:60, editor:DateCellEditor}, + {id:"effort-driven", name:"Effort Driven", width:80, minWidth:20, maxWidth:80, cssClass:"cell-effort-driven", field:"effortDriven", formatter:BoolCellFormatter, editor:YesNoCheckboxCellEditor} + ]; + var options = { + editable: true, + enableAddRow: true, + enableCellNavigation: true, + asyncEditorLoading: false, + autoEdit: false + }; + + + + function openDetails() { + if (grid.getEditorLock().isActive() && !grid.getEditorLock().commitCurrentEdit()) { + return; + } + + var $modal = $("<div class='item-details-form'></div>"); + + $modal = $("#itemDetailsTemplate") + .tmpl({ + context: grid.getDataItem(grid.getActiveCell().row), + columns: columns + }) + .appendTo("body"); + + $modal.keydown(function(e) { + if (e.which == $.ui.keyCode.ENTER) { + grid.getEditController().commitCurrentEdit(); + e.stopPropagation(); + e.preventDefault(); + } + else if (e.which == $.ui.keyCode.ESCAPE) { + grid.getEditController().cancelCurrentEdit(); + e.stopPropagation(); + e.preventDefault(); + } + }); + + $modal.find("[data-action=save]").click(function() { + grid.getEditController().commitCurrentEdit(); + }); + + $modal.find("[data-action=cancel]").click(function() { + grid.getEditController().cancelCurrentEdit(); + }); + + + var containers = $.map(columns, function(c) { return $modal.find("[data-editorid=" + c.id + "]"); }); + + var compositeEditor = new Slick.CompositeEditor( + columns, + containers, + { + destroy: function () { $modal.remove(); } + } + ); + + grid.editActiveCell(compositeEditor); + } + + $(function() + { + for (var i=0; i<500; i++) { + var d = (data[i] = {}); + + d["title"] = "Task " + i; + d["description"] = "This is a sample task description.\n It can be multiline"; + d["duration"] = "5 days"; + d["percentComplete"] = Math.round(Math.random() * 100); + d["start"] = "01/01/2009"; + d["finish"] = "01/05/2009"; + d["effortDriven"] = (i % 5 == 0); + } + + grid = new Slick.Grid("#myGrid", data, columns, options); + + grid.onAddNewRow.subscribe(function(e, args) { + var item = args.item; + var column = args.column; + grid.invalidateRow(data.length); + data.push(item); + grid.updateRowCount(); + grid.render(); + }); + + + grid.onValidationError.subscribe(function(e, args) { + // handle validation errors originating from the CompositeEditor + if (args.editor && (args.editor instanceof Slick.CompositeEditor)) { + var err; + var idx = args.validationResults.errors.length; + while (idx--) { + err = args.validationResults.errors[idx]; + $(err.container).stop(true,true).effect("highlight", {color:"red"}); + } + } + }); + + grid.setActiveCell(0, 0); + }) + </script> + </body> +</html> diff --git a/examples/examples.css b/examples/examples.css index 9f7abdc..2f413eb 100644 --- a/examples/examples.css +++ b/examples/examples.css @@ -137,7 +137,7 @@ input.editor-text { } -.slick-cell .ui-datepicker-trigger { +.ui-datepicker-trigger { margin-top: 2px; padding: 0; vertical-align: top; diff --git a/examples/slick.compositeeditor.js b/examples/slick.compositeeditor.js new file mode 100644 index 0000000..9dbbc41 --- /dev/null +++ b/examples/slick.compositeeditor.js @@ -0,0 +1,213 @@ +;(function($) { + + $.extend(true, window, { + Slick: { + CompositeEditor: CompositeEditor + } + }); + + + /*** + * A composite SlickGrid editor factory. + * Generates an editor that is composed of multiple editors for given columns. + * Individual editors are provided given containers instead of the original cell. + * Validation will be performed on all editors individually and the results will be aggregated into one + * validation result. + * + * + * The returned editor will have its prototype set to CompositeEditor, so you can use the "instanceof" check. + * + * NOTE: This doesn't work for detached editors since they will be created and positioned relative to the + * active cell and not the provided container. + * + * @namespace Slick + * @class CompositeEditor + * @constructor + * @param columns {Array} Column definitions from which editors will be pulled. + * @param containers {Array} Container HTMLElements in which editors will be placed. + * @param options {Object} Options hash: + * validationFailedMsg - A generic failed validation message set on the aggregated validation resuls. + * hide - A function to be called when the grid asks the editor to hide itself. + * show - A function to be called when the grid asks the editor to show itself. + * position - A function to be called when the grid asks the editor to reposition itself. + * destroy - A function to be called when the editor is destroyed. + */ + function CompositeEditor(columns, containers, options) { + var defaultOptions = { + validationFailedMsg: "Some of the fields have failed validation", + show: null, + hide: null, + position: null, + destroy: null + }; + + var noop = function () {}; + + var firstInvalidEditor; + + options = $.extend({}, defaultOptions, options); + + + function getContainerBox(i) { + var c = containers[i]; + var offset = $(c).offset(); + var w = $(c).width(); + var h = $(c).height(); + + return { + top: offset.top, + left: offset.left, + bottom: offset.top + h, + right: offset.left + w, + width: w, + height: h, + visible: true + }; + } + + + function editor(args) { + var editors = []; + + + function init() { + var newArgs = {}; + var idx = columns.length; + while (idx--) { + if (columns[idx].editor) { + newArgs = $.extend({}, args); + newArgs.container = containers[idx]; + newArgs.column = columns[idx]; + newArgs.position = getContainerBox(idx); + newArgs.commitChanges = noop; + newArgs.cancelChanges = noop; + + editors[idx] = new (columns[idx].editor)(newArgs); + } + } + } + + + this.destroy = function () { + var idx = editors.length; + while (idx--) { + editors[idx].destroy(); + } + + options.destroy && options.destroy(); + }; + + + this.focus = function () { + // if validation has failed, set the focus to the first invalid editor + (firstInvalidEditor || editors[0]).focus(); + }; + + + this.isValueChanged = function () { + var idx = editors.length; + while (idx--) { + if (editors[idx].isValueChanged()) { + return true; + } + } + return false; + }; + + + this.serializeValue = function () { + var serializedValue = []; + var idx = editors.length; + while (idx--) { + serializedValue[idx] = editors[idx].serializeValue(); + } + return serializedValue; + }; + + + this.applyValue = function (item, state) { + var idx = editors.length; + while (idx--) { + editors[idx].applyValue(item, state[idx]); + } + }; + + + this.loadValue = function (item) { + var idx = editors.length; + while (idx--) { + editors[idx].loadValue(item); + } + }; + + + this.validate = function () { + var validationResults; + var errors = []; + + firstInvalidEditor = null; + + var idx = editors.length; + while (idx--) { + validationResults = editors[idx].validate(); + if (!validationResults.valid) { + firstInvalidEditor = editors[idx]; + errors.push({ + index: idx, + editor: editors[idx], + container: containers[idx], + msg: validationResults.msg + }); + } + } + + if (errors.length) { + return { + valid: false, + msg: options.validationFailedMsg, + errors: errors + }; + } + else { + return { + valid: true, + msg: "" + }; + } + }; + + + this.hide = function () { + var idx = editors.length; + while (idx--) { + editors[idx].hide && editors[idx].hide(); + } + + options.hide && options.hide(); + }; + + + this.show = function () { + var idx = editors.length; + while (idx--) { + editors[idx].show && editors[idx].show(); + } + + options.show && options.show(); + }; + + + this.position = function (box) { + options.position && options.position(box); + }; + + + init(); + } + + // so we can do "editor instanceof Slick.CompositeEditor + editor.prototype = this; + + return editor; + } +})(jQuery);
\ No newline at end of file diff --git a/slick.grid.js b/slick.grid.js index 6b7c815..1afeaee 100644 --- a/slick.grid.js +++ b/slick.grid.js @@ -1843,7 +1843,7 @@ if (typeof Slick === "undefined") { getEditorLock().deactivate(editController); } - function makeActiveCellEditable() { + function makeActiveCellEditable(editor) { if (!activeCellNode) { return; } if (!options.editable) { throw "Grid : makeActiveCellEditable : should never get called when options.editable is false"; @@ -1856,7 +1856,10 @@ if (typeof Slick === "undefined") { return; } - if (trigger(self.onBeforeEditCell, {row:activeRow, cell:activeCell,item:getDataItem(activeRow)}) === false) { + var columnDef = columns[activeCell]; + var item = getDataItem(activeRow); + + if (trigger(self.onBeforeEditCell, {row:activeRow, cell:activeCell, item:item, column:columnDef}) === false) { setFocus(); return; } @@ -1864,12 +1867,12 @@ if (typeof Slick === "undefined") { getEditorLock().activate(editController); $(activeCellNode).addClass("editable"); - activeCellNode.innerHTML = ""; - - var columnDef = columns[activeCell]; - var item = getDataItem(activeRow); + // don't clear the cell if a custom editor is passed through + if (!editor) { + activeCellNode.innerHTML = ""; + } - currentEditor = new (getEditor(columnDef))({ + currentEditor = new (editor || getEditor(columnDef))({ grid: self, gridPosition: absBox($container[0]), position: absBox(activeCellNode), @@ -2192,6 +2195,7 @@ if (typeof Slick === "undefined") { $(activeCellNode).stop(true,true).effect("highlight", {color:"red"}, 300); trigger(self.onValidationError, { + editor: currentEditor, cellNode: activeCellNode, validationResults: validationResults, row: activeRow, |