diff options
-rw-r--r-- | examples/example3-editing.html | 37 | ||||
-rw-r--r-- | examples/example4-model.html | 3 | ||||
-rw-r--r-- | slick.editors.js | 1049 | ||||
-rw-r--r-- | slick.grid.js | 180 |
4 files changed, 715 insertions, 554 deletions
diff --git a/examples/example3-editing.html b/examples/example3-editing.html index e88aa71..3258ed5 100644 --- a/examples/example3-editing.html +++ b/examples/example3-editing.html @@ -17,20 +17,25 @@ </style> </head> <body> - <table width="100%"> - <tr> - <td valign="top" width="50%"> - <div id="myGrid" style="width:600px;height:500px;"></div> - </td> - <td valign="top"> - <h2>Demonstrates:</h2> - <ul> - <li>adding basic keyboard navigation and editing</li> - <li>custom editors and validators</li> - </ul> - </td> - </tr> - </table> + + + <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>adding basic keyboard navigation and editing</li> + <li>custom editors and validators</li> + <li>auto-edit settings</li> + </ul> + + <h2>Options:</h2> + <button onclick="grid.setOptions({autoEdit:true})">Auto-edit ON</button> + + <button onclick="grid.setOptions({autoEdit:false})">Auto-edit OFF</button> + </div> <script language="JavaScript" src="../lib/firebugx.js"></script> @@ -58,7 +63,8 @@ var columns = [ {id:"title", name:"Title", field:"title", width:120, cssClass:"cell-title", editor:TextCellEditor, validator:requiredFieldValidator}, - {id:"duration", name:"Duration", field:"duration", editor:TextCellEditor}, + {id:"desc", name:"Description", field:"description", width:100, editor:LongTextCellEditor}, + {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}, @@ -80,6 +86,7 @@ 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"; diff --git a/examples/example4-model.html b/examples/example4-model.html index b3a02ef..691e404 100644 --- a/examples/example4-model.html +++ b/examples/example4-model.html @@ -232,7 +232,8 @@ selectedRowIds = []; var rows = grid.getSelectedRows(); for (var i = 0, l = rows.length; i < l; i++) { - selectedRowIds.push(dataView.rows[rows[i]].id); + var item = dataView.rows[rows[i]]; + if (item) selectedRowIds.push(item.id); } }; diff --git a/slick.editors.js b/slick.editors.js index 4adbb9d..b954fb4 100644 --- a/slick.editors.js +++ b/slick.editors.js @@ -1,649 +1,694 @@ +/* THESE FORMATTERS & EDITORS ARE JUST SAMPLES! */ + (function($) { -var SlickEditor = { + var SlickEditor = { - SelectorCellFormatter : function(row, cell, value, columnDef, dataContext) { - return (!dataContext ? "" : row); - }, + SelectorCellFormatter : function(row, cell, value, columnDef, dataContext) { + return (!dataContext ? "" : row); + }, - PercentCompleteCellFormatter : function(row, cell, value, columnDef, dataContext) { - if (value == null || value === "") - return "-"; - else if (value < 50) - return "<span style='color:red;font-weight:bold;'>" + value + "%</span>"; - else - return "<span style='color:green'>" + value + "%</span>"; - }, + PercentCompleteCellFormatter : function(row, cell, value, columnDef, dataContext) { + if (value == null || value === "") + return "-"; + else if (value < 50) + return "<span style='color:red;font-weight:bold;'>" + value + "%</span>"; + else + return "<span style='color:green'>" + value + "%</span>"; + }, - GraphicalPercentCompleteCellFormatter : function(row, cell, value, columnDef, dataContext) { - if (value == null || value === "") - return ""; + GraphicalPercentCompleteCellFormatter : function(row, cell, value, columnDef, dataContext) { + if (value == null || value === "") + return ""; - var color; + var color; - if (value < 30) - color = "red"; - else if (value < 70) - color = "silver"; - else - color = "green"; + if (value < 30) + color = "red"; + else if (value < 70) + color = "silver"; + else + color = "green"; - return "<span class='percent-complete-bar' style='background:" + color + ";width:" + value + "%'></span>"; - }, + return "<span class='percent-complete-bar' style='background:" + color + ";width:" + value + "%'></span>"; + }, - YesNoCellFormatter : function(row, cell, value, columnDef, dataContext) { - return value ? "Yes" : "No"; - }, + YesNoCellFormatter : function(row, cell, value, columnDef, dataContext) { + return value ? "Yes" : "No"; + }, - BoolCellFormatter : function(row, cell, value, columnDef, dataContext) { - return value ? "<img src='../images/tick.png'>" : ""; - }, + BoolCellFormatter : function(row, cell, value, columnDef, dataContext) { + return value ? "<img src='../images/tick.png'>" : ""; + }, - TaskNameFormatter : function(row, cell, value, columnDef, dataContext) { - // todo: html encode - var spacer = "<span style='display:inline-block;height:1px;width:" + (2 + 15 * dataContext["indent"]) + "px'></span>"; - return spacer + " <img src='../images/expand.gif'> " + value; - }, + TaskNameFormatter : function(row, cell, value, columnDef, dataContext) { + // todo: html encode + var spacer = "<span style='display:inline-block;height:1px;width:" + (2 + 15 * dataContext["indent"]) + "px'></span>"; + return spacer + " <img src='../images/expand.gif'> " + value; + }, - ResourcesFormatter : function(row, cell, value, columnDef, dataContext) { - var resources = dataContext["resources"]; + ResourcesFormatter : function(row, cell, value, columnDef, dataContext) { + var resources = dataContext["resources"]; - if (!resources || resources.length == 0) - return ""; + if (!resources || resources.length == 0) + return ""; - if (columnDef.width < 50) - return (resources.length > 1 ? "<center><img src='../images/user_identity_plus.gif' " : "<center><img src='../images/user_identity.gif' ") + - " title='" + resources.join(", ") + "'></center>"; - else - return resources.join(", "); - }, + if (columnDef.width < 50) + return (resources.length > 1 ? "<center><img src='../images/user_identity_plus.gif' " : "<center><img src='../images/user_identity.gif' ") + + " title='" + resources.join(", ") + "'></center>"; + else + return resources.join(", "); + }, - StarFormatter : function(row, cell, value, columnDef, dataContext) { - return (value) ? "<img src='../images/bullet_star.png' align='absmiddle'>" : ""; - }, + StarFormatter : function(row, cell, value, columnDef, dataContext) { + return (value) ? "<img src='../images/bullet_star.png' align='absmiddle'>" : ""; + }, - TextCellEditor : function($container, columnDef, value, dataContext) { - var $input; - var defaultValue = value; - var scope = this; + TextCellEditor : function(args) { + var $input; + var defaultValue = args.value; + var scope = this; - this.init = function() { - $input = $("<INPUT type=text class='editor-text' />"); + this.init = function() { + $input = $("<INPUT type=text class='editor-text' />"); - if (value != null) - { - $input[0].defaultValue = value; - $input.val(defaultValue); - } + if (args.value != null) { + $input[0].defaultValue = args.value; + $input.val(defaultValue); + } - $input.appendTo($container); + $input.appendTo(args.container); - $input.bind("keydown.nav", function(e) { - if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { - e.stopImmediatePropagation(); - } - }); - - $input.focus().select(); - }; - - this.destroy = function() { - $input.remove(); - }; - - this.focus = function() { - $input.focus(); - }; - - this.setValue = function(value) { - $input.val(value); - defaultValue = value; - }; - - this.getValue = function() { - return $input.val(); - }; - - this.isValueChanged = function() { - return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); - }; - - this.validate = function() { - if (columnDef.validator) - { - var validationResults = columnDef.validator(scope.getValue()); - if (!validationResults.valid) - return validationResults; - } + $input.bind("keydown.nav", function(e) { + if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { + e.stopImmediatePropagation(); + } + }); - return { - valid: true, - msg: null + $input.focus().select(); }; - }; - this.init(); - }, + this.destroy = function() { + $input.remove(); + }; - IntegerCellEditor : function($container, columnDef, value, dataContext) { - var $input; - var defaultValue = value; - var scope = this; + this.focus = function() { + $input.focus(); + }; - this.init = function() { - $input = $("<INPUT type=text class='editor-text' />"); + this.setValue = function(value) { + $input.val(value); + defaultValue = value; + }; - if (value != null) - { - $input[0].defaultValue = value; - $input.val(defaultValue); - } + this.getValue = function() { + return $input.val(); + }; - $input.bind("keydown.nav", function(e) { - if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { - e.stopImmediatePropagation(); + this.isValueChanged = function() { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); + }; + + this.validate = function() { + if (args.column.validator) { + var validationResults = args.column.validator(scope.getValue()); + if (!validationResults.valid) + return validationResults; } - }); - $input.appendTo($container); - $input.focus().select(); - }; + return { + valid: true, + msg: null + }; + }; + this.init(); + }, - this.destroy = function() { - $input.remove(); - }; + IntegerCellEditor : function(args) { + var $input; + var defaultValue = args.value; + var scope = this; - this.focus = function() { - $input.focus(); - }; + this.init = function() { + $input = $("<INPUT type=text class='editor-text' />"); - this.setValue = function(value) { - $input.val(value); - defaultValue = value; - }; + if (args.value != null) { + $input[0].defaultValue = args.value; + $input.val(defaultValue); + } - this.getValue = function() { - var val = $.trim($input.val()); - return (val == "") ? 0 : parseInt($input.val(), 10); - }; + $input.bind("keydown.nav", function(e) { + if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { + e.stopImmediatePropagation(); + } + }); - this.isValueChanged = function() { - return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); - }; + $input.appendTo(args.container); + $input.focus().select(); + }; - this.validate = function() { - if (isNaN($input.val())) - return { - valid: false, - msg: "Please enter a valid integer" - }; + this.destroy = function() { + $input.remove(); + }; + + this.focus = function() { + $input.focus(); + }; - return { - valid: true, - msg: null + this.setValue = function(value) { + $input.val(value); + defaultValue = value; }; - }; - this.init(); - }, + this.getValue = function() { + var val = $.trim($input.val()); + return (val == "") ? 0 : parseInt($input.val(), 10); + }; - DateCellEditor : function($container, columnDef, value, dataContext) { - var $input; - var defaultValue = value; - var scope = this; + this.isValueChanged = function() { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); + }; - this.init = function() { - $input = $("<INPUT type=text class='editor-text' />"); + this.validate = function() { + if (isNaN($input.val())) + return { + valid: false, + msg: "Please enter a valid integer" + }; - if (value != null) - { - $input[0].defaultValue = value; - $input.val(defaultValue); - } + return { + valid: true, + msg: null + }; + }; - $input.appendTo($container); - $input.focus().select(); - $input.datepicker({ - showOn: "button", - buttonImageOnly: true, - buttonImage: "../images/calendar.gif" - }); - $input.width($input.width() - 18); - }; + this.init(); + }, + DateCellEditor : function(args) { + var $input; + var defaultValue = args.value; + var scope = this; + var calendarOpen = false; - this.destroy = function() { - $input.datepicker("hide"); - $input.datepicker("destroy"); - $input.remove(); - }; + this.init = function() { + $input = $("<INPUT type=text class='editor-text' />"); + if (args.value != null) { + $input[0].defaultValue = args.value; + $input.val(defaultValue); + } - this.focus = function() { - $input.focus(); - }; + $input.appendTo(args.container); + $input.focus().select(); + $input.datepicker({ + showOn: "button", + buttonImageOnly: true, + buttonImage: "../images/calendar.gif", + beforeShow: function() { calendarOpen = true }, + onClose: function() { calendarOpen = false } + }); + $input.width($input.width() - 18); + }; - this.setValue = function(value) { - $input.val(value); - defaultValue = value; - }; + this.destroy = function() { + $.datepicker.dpDiv.stop(true,true); + $input.datepicker("hide"); + $input.datepicker("destroy"); + $input.remove(); + }; - this.getValue = function() { - return $input.val(); - }; + this.show = function() { + if (calendarOpen) { + $.datepicker.dpDiv.stop(true,true).show(); + } + }; - this.isValueChanged = function() { - return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); - }; + this.hide = function() { + if (calendarOpen) { + $.datepicker.dpDiv.stop(true,true).hide(); + } + }; - this.validate = function() { - return { - valid: true, - msg: null + this.position = function(position) { + if (!calendarOpen) return; + $.datepicker.dpDiv + .css("top", position.top + 30) + .css("left", position.left); }; - }; - this.init(); - }, + this.focus = function() { + $input.focus(); + }; - YesNoSelectCellEditor : function($container, columnDef, value, dataContext) { - var $select; - var defaultValue = value; - var scope = this; + this.setValue = function(value) { + $input.val(value); + defaultValue = value; + }; - this.init = function() { - $select = $("<SELECT tabIndex='0' class='editor-yesno'><OPTION value='yes'>Yes</OPTION><OPTION value='no'>No</OPTION></SELECT>"); + this.getValue = function() { + return $input.val(); + }; - if (defaultValue) - $select.val('yes'); - else - $select.val('no'); + this.isValueChanged = function() { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); + }; - $select.appendTo($container); + this.validate = function() { + return { + valid: true, + msg: null + }; + }; - $select.focus(); - }; + this.init(); + }, + YesNoSelectCellEditor : function(args) { + var $select; + var defaultValue = args.value; + var scope = this; - this.destroy = function() { - $select.remove(); - }; + this.init = function() { + $select = $("<SELECT tabIndex='0' class='editor-yesno'><OPTION value='yes'>Yes</OPTION><OPTION value='no'>No</OPTION></SELECT>"); + if (defaultValue) + $select.val('yes'); + else + $select.val('no'); - this.focus = function() { - $select.focus(); - }; + $select.appendTo(args.container); - this.setValue = function(value) { - $select.val(value); - defaultValue = value; - }; + $select.focus(); + }; - this.getValue = function() { - return ($select.val() == 'yes'); - }; + this.destroy = function() { + $select.remove(); + }; - this.isValueChanged = function() { - return ($select.val() != defaultValue); - }; + this.focus = function() { + $select.focus(); + }; - this.validate = function() { - return { - valid: true, - msg: null + this.setValue = function(value) { + $select.val(value); + defaultValue = value; }; - }; - this.init(); - }, + this.getValue = function() { + return ($select.val() == 'yes'); + }; - YesNoCheckboxCellEditor : function($container, columnDef, value, dataContext) { - var $select; - var defaultValue = value; - var scope = this; + this.isValueChanged = function() { + return ($select.val() != defaultValue); + }; - this.init = function() { - $select = $("<INPUT type=checkbox value='true' class='editor-checkbox' hideFocus>"); + this.validate = function() { + return { + valid: true, + msg: null + }; + }; - if (defaultValue) - $select.attr("checked", "checked"); + this.init(); + }, - $select.appendTo($container); - $select.focus(); - }; + YesNoCheckboxCellEditor : function(args) { + var $select; + var defaultValue = args.value; + var scope = this; + this.init = function() { + $select = $("<INPUT type=checkbox value='true' class='editor-checkbox' hideFocus>"); - this.destroy = function() { - $select.remove(); - }; + if (defaultValue) + $select.attr("checked", "checked"); + $select.appendTo(args.container); + $select.focus(); + }; - this.focus = function() { - $select.focus(); - }; - this.setValue = function(value) { - if (value) - $select.attr("checked", "checked"); - else - $select.removeAttr("checked"); + this.destroy = function() { + $select.remove(); + }; - defaultValue = value; - }; - this.getValue = function() { - return $select.attr("checked"); - }; + this.focus = function() { + $select.focus(); + }; - this.isValueChanged = function() { - return (scope.getValue() != defaultValue); - }; + this.setValue = function(value) { + if (value) + $select.attr("checked", "checked"); + else + $select.removeAttr("checked"); - this.validate = function() { - return { - valid: true, - msg: null + defaultValue = value; }; - }; - this.init(); - }, + this.getValue = function() { + return $select.attr("checked"); + }; - PercentCompleteCellEditor : function($container, columnDef, value, dataContext) { - var $input, $picker; - var defaultValue = value; - var scope = this; + this.isValueChanged = function() { + return (scope.getValue() != defaultValue); + }; - this.init = function() { - $input = $("<INPUT type=text class='editor-percentcomplete' />"); + this.validate = function() { + return { + valid: true, + msg: null + }; + }; - if (value != null) - { - $input[0].defaultValue = value; - $input.val(defaultValue); - } + this.init(); + }, - $input.width($container.innerWidth() - 25); - $input.appendTo($container); + PercentCompleteCellEditor : function(args) { + var $input, $picker; + var defaultValue = args.value; + var scope = this; - $picker = $("<div class='editor-percentcomplete-picker' />").appendTo($container); + this.init = function() { + $input = $("<INPUT type=text class='editor-percentcomplete' />"); - $picker.append("<div class='editor-percentcomplete-helper'><div class='editor-percentcomplete-wrapper'><div class='editor-percentcomplete-slider' /><div class='editor-percentcomplete-buttons' /></div></div>"); + if (args.value != null) { + $input[0].defaultValue = args.value; + $input.val(defaultValue); + } - $picker.find(".editor-percentcomplete-buttons").append("<button val=0>Not started</button><br/><button val=50>In Progress</button><br/><button val=100>Complete</button>"); + $input.width($(args.container).innerWidth() - 25); + $input.appendTo(args.container); - $input.focus().select(); + $picker = $("<div class='editor-percentcomplete-picker' />").appendTo(args.container); - $picker.find(".editor-percentcomplete-slider").slider({ - orientation: "vertical", - range: "min", - value: defaultValue, - slide: function(event, ui) { - $input.val(ui.value) - } - }); + $picker.append("<div class='editor-percentcomplete-helper'><div class='editor-percentcomplete-wrapper'><div class='editor-percentcomplete-slider' /><div class='editor-percentcomplete-buttons' /></div></div>"); - $picker.find(".editor-percentcomplete-buttons button").bind("click", function(e) { - $input.val($(this).attr("val")); - $picker.find(".editor-percentcomplete-slider").slider("value", $(this).attr("val")); - }) - }; + $picker.find(".editor-percentcomplete-buttons").append("<button val=0>Not started</button><br/><button val=50>In Progress</button><br/><button val=100>Complete</button>"); + $input.focus().select(); - this.destroy = function() { - $input.remove(); - $picker.remove(); - }; + $picker.find(".editor-percentcomplete-slider").slider({ + orientation: "vertical", + range: "min", + value: defaultValue, + slide: function(event, ui) { + $input.val(ui.value) + } + }); + $picker.find(".editor-percentcomplete-buttons button").bind("click", function(e) { + $input.val($(this).attr("val")); + $picker.find(".editor-percentcomplete-slider").slider("value", $(this).attr("val")); + }) + }; - this.focus = function() { - $input.focus(); - }; - this.setValue = function(value) { - $input.val(value); - defaultValue = value; - }; + this.destroy = function() { + $input.remove(); + $picker.remove(); + }; - this.getValue = function() { - var val = $.trim($input.val()); - return (val == "") ? 0 : parseInt($input.val(), 10); - }; - this.isValueChanged = function() { - return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); - }; + this.focus = function() { + $input.focus(); + }; + + this.setValue = function(value) { + $input.val(value); + defaultValue = value; + }; + + this.getValue = function() { + var val = $.trim($input.val()); + return (val == "") ? 0 : parseInt($input.val(), 10); + }; + + this.isValueChanged = function() { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); + }; + + this.validate = function() { + if (isNaN($input.val())) + return { + valid: false, + msg: "Please enter a valid positive number" + }; - this.validate = function() { - if (isNaN($input.val())) return { - valid: false, - msg: "Please enter a valid positive number" + valid: true, + msg: null }; + }; + + this.init(); + }, + + TaskNameCellEditor : function(args) { + var $input; + var defaultValue = args.value; + var scope = this; + + this.init = function() { + $input = $("<INPUT type=text class='editor-text' />"); - return { - valid: true, - msg: null + if (args.value != null) { + $input[0].defaultValue = args.value; + $input.val(defaultValue); + } + + $input.bind("keydown.nav", function(e) { + if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { + e.stopImmediatePropagation(); + } + }); + $input.appendTo(args.container); + $input.focus().select(); }; - }; - this.init(); - }, + this.destroy = function() { + $input.remove(); + }; - TaskNameCellEditor : function($container, columnDef, value, dataContext) { - var $input; - var defaultValue = value; - var scope = this; + this.focus = function() { + $input.focus(); + }; - this.init = function() { - $input = $("<INPUT type=text class='editor-text' />"); + this.setValue = function(value) { + $input.val(value); + defaultValue = value; + }; - if (value != null) - { - $input[0].defaultValue = value; - $input.val(defaultValue); - } + this.getValue = function() { + return $input.val(); + }; - $input.bind("keydown.nav", function(e) { - if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { - e.stopImmediatePropagation(); + this.isValueChanged = function() { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); + }; + + this.validate = function() { + if (columnDef.validator) { + var validationResults = columnDef.validator(scope.getValue()); + if (!validationResults.valid) + return validationResults; } - }); - $input.appendTo($container); - $input.focus().select(); - }; - - this.destroy = function() { - $input.remove(); - }; - - this.focus = function() { - $input.focus(); - }; - - this.setValue = function(value) { - $input.val(value); - defaultValue = value; - }; - - this.getValue = function() { - return $input.val(); - }; - - this.isValueChanged = function() { - return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); - }; - - this.validate = function() { - if (columnDef.validator) - { - var validationResults = columnDef.validator(scope.getValue()); - if (!validationResults.valid) - return validationResults; - } - if ($input.val() == "") + if ($input.val() == "") + return { + valid: false, + msg: "This field cannot be empty" + }; + return { - valid: false, - msg: "This field cannot be empty" + valid: true, + msg: null }; - - return { - valid: true, - msg: null }; - }; - - this.init(); - }, - ResourcesCellEditor : function($container, columnDef, value, dataContext) { - var $input; - var defaultValue = []; - var scope = this; + this.init(); + }, - this.init = function() { - $input = $("<INPUT type=text class='editor-text' />"); + StarCellEditor : function(args) { + var $input; + var defaultValue = args.value; + var scope = this; - var resources = dataContext ? dataContext["resources"] : null; + function toggle(e) { + if (e.type == "keydown" && e.which != 32) return; - defaultValue = resources ? resources.concat() : []; + if ($input.css("opacity") == "1") + $input.css("opacity", 0.5); + else + $input.css("opacity", 1); - if (resources != null) - { - $input[0].defaultValue = defaultValue.join(", "); - $input.val(defaultValue.join(", ")); + e.preventDefault(); + e.stopPropagation(); + return false; } - $input.bind("keydown.nav", function(e) { - if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { - e.stopImmediatePropagation(); - } - }); - $input.appendTo($container); - $input.focus().select(); - }; - - this.destroy = function() { - $input.remove(); - }; - - this.focus = function() { - $input.focus(); - }; - - this.setValue = function(value) { - defaultValue = value ? value : []; - $input.val(defaultValue.join(", ")); - }; - - this.getValue = function() { - if ($input.val() == "") - return []; - - var names = $input.val().split(","); - - for (var i = 0; i < names.length; i++) - names[i] = $.trim(names[i]); - - return names; - }; - - this.isValueChanged = function() { - // todo: implement - return true; - }; - - this.validate = function() { - if (columnDef.validator) - { - var validationResults = columnDef.validator(scope.getValue()); - if (!validationResults.valid) - return validationResults; - } + this.init = function() { + $input = $("<IMG src='../images/bullet_star.png' align=absmiddle tabIndex=0 title='Click or press Space to toggle' />"); - // todo: implement + if (defaultValue) + $input.css("opacity", 1); + else + $input.css("opacity", 0.5); - return { - valid: true, - msg: null + $input.bind("click keydown", toggle); + + $input.appendTo(args.container); + $input.focus(); }; - }; - this.init(); - }, + this.destroy = function() { + $input.unbind("click keydown", toggle); + $input.remove(); + }; - StarCellEditor : function($container, columnDef, value, dataContext) { - var $input; - var defaultValue = value; - var scope = this; + this.focus = function() { + $input.focus(); + }; - function toggle(e) { - if (e.type == "keydown" && e.which != 32) return; + this.setValue = function(value) { + defaultValue = value; - if ($input.css("opacity") == "1") - $input.css("opacity", 0.5); - else - $input.css("opacity", 1); + if (defaultValue) + $input.css("opacity", 1); + else + $input.css("opacity", 0.2); + }; - e.preventDefault(); - e.stopPropagation(); - return false; - } + this.getValue = function() { + return $input.css("opacity") == "1"; + }; - this.init = function() { - $input = $("<IMG src='../images/bullet_star.png' align=absmiddle tabIndex=0 title='Click or press Space to toggle' />"); + this.isValueChanged = function() { + return defaultValue != scope.getValue(); + }; - if (defaultValue) - $input.css("opacity", 1); - else - $input.css("opacity", 0.5); + this.validate = function() { + return { + valid: true, + msg: null + }; + }; - $input.bind("click keydown", toggle); + this.init(); + }, - $input.appendTo($container); - $input.focus(); - }; + /* + * An example of a "detached" editor. + * The UI is added onto document BODY and .position(), .show() and .hide() are implemented. + * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. + */ + LongTextCellEditor : function (args) { + var $input, $wrapper; + var defaultValue = args.value; + var scope = this; - this.destroy = function() { - $input.unbind("click keydown", toggle); - $input.remove(); - }; + this.init = function() { + var $container = $("body"); - this.focus = function() { - $input.focus(); - }; + $wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>") + .appendTo($container); - this.setValue = function(value) { - defaultValue = value; + $input = $("<TEXTAREA hidefocus rows=5 style='backround:white;width:250px;height:80px;border:0;outline:0'>") + .appendTo($wrapper); - if (defaultValue) - $input.css("opacity", 1); - else - $input.css("opacity", 0.2); - }; + $("<DIV style='text-align:right'><BUTTON>Save</BUTTON><BUTTON>Cancel</BUTTON></DIV>") + .appendTo($wrapper); + + $wrapper.find("button:first").bind("click", this.save); + $wrapper.find("button:last").bind("click", this.cancel); + $input.bind("keydown", this.handleKeyDown); + + if (args.value != null) { + $input[0].defaultValue = args.value; + $input.val(defaultValue); + } + + scope.position(args.position); + $input.focus().select(); + }; + + this.handleKeyDown = function(e) { + if (e.which == $.ui.keyCode.ENTER && e.ctrlKey) { + scope.save(); + } + else if (e.which == $.ui.keyCode.ESCAPE) { + e.preventDefault(); + scope.cancel(); + } + else if (e.which == $.ui.keyCode.TAB && e.shiftKey) { + e.preventDefault(); + grid.navigatePrev(); + } + else if (e.which == $.ui.keyCode.TAB) { + e.preventDefault(); + grid.navigateNext(); + } + }; + + this.save = function() { + args.commitChanges(); + }; + + this.cancel = function() { + $input.val(defaultValue); + args.cancelChanges(); + }; - this.getValue = function() { - return $input.css("opacity") == "1"; - }; + this.hide = function() { + $wrapper.hide(); + }; + + this.show = function() { + $wrapper.show(); + }; + + this.position = function(position) { + $wrapper + .css("top", position.top - 5) + .css("left", position.left - 5) + }; + + this.destroy = function() { + $wrapper.remove(); + }; + + this.focus = function() { + $input.focus(); + }; - this.isValueChanged = function() { - return (defaultValue == true) != scope.getValue(); - }; + this.setValue = function(value) { + $input.val(value); + defaultValue = value; + }; + + this.getValue = function() { + return $input.val(); + }; - this.validate = function() { - return { - valid: true, - msg: null + this.isValueChanged = function() { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); }; - }; - this.init(); - } - }; + this.validate = function() { + return { + valid: true, + msg: null + }; + }; + + this.init(); + } + + }; - $.extend(window, SlickEditor); + $.extend(window, SlickEditor); })(jQuery); diff --git a/slick.grid.js b/slick.grid.js index a20cf39..42ac051 100644 --- a/slick.grid.js +++ b/slick.grid.js @@ -429,6 +429,7 @@ if (!jQuery.fn.drag) { resizeAndRender(); + bindAncestorScrollEvents(); $viewport.bind("scroll", handleScroll); $container.bind("resize", resizeAndRender); $canvas.bind("keydown", handleKeyDown); @@ -439,6 +440,20 @@ if (!jQuery.fn.drag) { $headerScroller.bind("contextmenu", handleHeaderContextMenu); } + // TODO: this is static. need to handle page mutation. + function bindAncestorScrollEvents() { + var elem = $canvas[0]; + while ((elem = elem.parentNode) != document.body) { + // bind to scroll containers only + if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) + $(elem).bind("scroll.slickgrid", repositionCurrentCellEditor); + } + } + + function unbindAncestorScrollEvents() { + $canvas.parents().unbind("scroll.slickgrid"); + } + function createColumnHeaders() { var i; @@ -837,6 +852,7 @@ if (!jQuery.fn.drag) { if (self.onBeforeDestroy) { self.onBeforeDestroy(); } if ($headers.sortable) { $headers.sortable("destroy"); } + unbindAncestorScrollEvents(); $container.unbind("resize", resizeCanvas); removeCssRules(); @@ -1087,6 +1103,9 @@ if (!jQuery.fn.drag) { } function removeAllRows() { + if (currentEditor) { + makeSelectedCellNormal(); + } $canvas[0].innerHTML = ""; rowsCache= {}; postProcessedRows = {}; @@ -1111,7 +1130,7 @@ if (!jQuery.fn.drag) { var nodes = []; for (i=0, rl=rows.length; i<rl; i++) { if (currentEditor && currentRow === i) { - throw "Grid : removeRow : Cannot remove a row that is currently in edit mode"; + makeSelectedCellNormal(); } if (rowsCache[rows[i]]) { @@ -1391,55 +1410,47 @@ if (!jQuery.fn.drag) { if (!options.editorLock.isActive()) { return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event) } - options.editorLock.cancelCurrentEdit(); - if (currentCellNode) { - currentCellNode.focus(); - } + cancelEditAndSetFocus(); break; case 9: // tab - gotoDir(0, (e.shiftKey) ? -1 : 1, true); + if (e.shiftKey) + navigatePrev(); + else + navigateNext(); break; case 37: // left - gotoDir(0, -1, false); + navigatePrev(); break; case 39: // right - gotoDir(0, 1, false); + navigateRight(); break; case 38: // up - gotoDir(-1, 0, false); + navigateUp(); break; case 40: // down - gotoDir(1, 0, false); + navigateDown(); break; case 13: // enter - if (options.autoEdit) { - gotoDir(1, 0, false); - } - else if (options.editable) { + if (options.editable) { if (currentEditor) { // adding new row if (currentRow === data.length) { - gotoDir(1, 0, false); + navigateDown(); } else { - // if the commit fails, it would do so due to a validation error - // if so, do not steal the focus from the editor - if (options.editorLock.commitCurrentEdit()) { - currentCellNode.focus(); - } + commitEditAndSetFocus(); } } else { if (options.editorLock.commitCurrentEdit()) { makeSelectedCellEditable(); } } - } break; @@ -1593,7 +1604,6 @@ if (!jQuery.fn.drag) { } } - // TODO: PERF: throttle event function handleHover(e) { if (!options.enableAutoTooltips) return; var $cell = $(e.target).closest(".slick-cell",$canvas); @@ -1623,6 +1633,7 @@ if (!jQuery.fn.drag) { ////////////////////////////////////////////////////////////////////////////////////////////// // Cell switching + function resetCurrentCell() { setSelectedCell(null,false,false); } @@ -1716,14 +1727,16 @@ if (!jQuery.fn.drag) { self.onBeforeCellEditorDestroy(currentEditor); } currentEditor.destroy(); - $(currentCellNode).removeClass("editable invalid"); + currentEditor = null; - if (gridDataGetItem(currentRow)) { - currentCellNode.innerHTML = columns[currentCell].formatter(currentRow, currentCell, gridDataGetItem(currentRow)[columns[currentCell].field], columns[currentCell], gridDataGetItem(currentRow)); - invalidatePostProcessingResults(currentRow); - } + if (currentCellNode) { + $(currentCellNode).removeClass("editable invalid"); - currentEditor = null; + if (gridDataGetItem(currentRow)) { + currentCellNode.innerHTML = columns[currentCell].formatter(currentRow, currentCell, gridDataGetItem(currentRow)[columns[currentCell].field], columns[currentCell], gridDataGetItem(currentRow)); + invalidatePostProcessingResults(currentRow); + } + } // if there previously was text selected on a page (such as selected text in the edit cell just removed), // IE can't set focus to anything else correctly @@ -1745,8 +1758,6 @@ if (!jQuery.fn.drag) { return; } - - if (self.onBeforeEditCell && self.onBeforeEditCell(currentRow,currentCell,gridDataGetItem(currentRow)) === false) { currentCellNode.focus(); return; @@ -1764,13 +1775,81 @@ if (!jQuery.fn.drag) { currentCellNode.innerHTML = ""; - currentEditor = new columns[currentCell].editor($(currentCellNode), columns[currentCell], value, gridDataGetItem(currentRow), function() { - // if the commit fails, it would do so due to a validation error - // if so, do not steal the focus from the editor - if (options.editorLock.commitCurrentEdit()) { - currentCellNode.focus(); - } + var columnDef = columns[currentCell]; + + currentEditor = new columnDef.editor({ + grid: self, + gridPosition: absBox($container[0]), + position: absBox(currentCellNode), + container: currentCellNode, + column: columnDef, + value: value, + item: gridDataGetItem(currentRow), + commitChanges: commitEditAndSetFocus, + cancelChanges: cancelEditAndSetFocus }); + + if (currentEditor.position) + repositionCurrentCellEditor(); + } + + function commitEditAndSetFocus() { + // if the commit fails, it would do so due to a validation error + // if so, do not steal the focus from the editor + if (options.editorLock.commitCurrentEdit()) { + currentCellNode.focus(); + } + + if (options.autoEdit) { + navigateDown(); + } + } + + function cancelEditAndSetFocus() { + if (options.editorLock.cancelCurrentEdit()) { + currentCellNode.focus(); + } + } + + // TODO: may be some issues with floats. investigate. + function absBox(elem) { + var box = {top:elem.offsetTop, left:elem.offsetLeft, bottom:0, right:0, width:$(elem).outerWidth(), height:$(elem).outerHeight(), visible:true}; + box.bottom = box.top + box.height; + box.right = box.left + box.width; + + // walk up the tree + var offsetParent = elem.offsetParent; + while ((elem = elem.parentNode) != document.body) { + box.visible = box.visible + && !(box.bottom <= elem.scrollTop || box.top >= elem.offsetTop + elem.scrollTop + elem.clientHeight) + && !(box.right <= elem.scrollLeft || box.left >= elem.offsetLeft + elem.scrollLeft + elem.clientWidth); + + box.left -= elem.scrollLeft; + box.top -= elem.scrollTop; + + if (elem === offsetParent) { + box.left += elem.offsetLeft; + box.top += elem.offsetTop; + offsetParent = elem.offsetParent; + } + + box.bottom = box.top + box.height; + box.right = box.left + box.width; + } + + return box; + } + + function repositionCurrentCellEditor() { + if (!currentEditor || !currentCellNode || !currentEditor.position) return; + var cellBox = absBox(currentCellNode); + if (currentEditor.show && currentEditor.hide) { + if (!cellBox.visible) + currentEditor.hide(); + else + currentEditor.show(); + } + currentEditor.position(cellBox); } function getCellEditor() { @@ -1875,6 +1954,29 @@ if (!jQuery.fn.drag) { } } + function navigateUp() { + gotoDir(-1, 0, false); + } + + function navigateDown() { + gotoDir(1, 0, false); + } + + function navigateLeft() { + gotoDir(0, -1, false); + } + + function navigateRight() { + gotoDir(0, 1, false); + } + + function navigatePrev() { + gotoDir(0, -1, true); + } + + function navigateNext() { + gotoDir(0, 1, true); + } ////////////////////////////////////////////////////////////////////////////////////////////// // IEditor implementation for the editor lock @@ -2038,6 +2140,12 @@ if (!jQuery.fn.drag) { "getCurrentCell": getCurrentCell, "getCurrentCellNode": getCurrentCellNode, "resetCurrentCell": resetCurrentCell, + "navigatePrev": navigatePrev, + "navigateNext": navigateNext, + "navigateUp": navigateUp, + "navigateDown": navigateDown, + "navigateLeft": navigateLeft, + "navigateRight": navigateRight, "gotoCell": gotoCell, "editCurrentCell": makeSelectedCellEditable, "getCellEditor": getCellEditor, |