diff options
-rw-r--r-- | examples/example1-simple.html | 4 | ||||
-rw-r--r-- | examples/example10-async-post-render.html | 2 | ||||
-rw-r--r-- | examples/example2-formatters.html | 9 | ||||
-rw-r--r-- | examples/example3-editing.html | 2 | ||||
-rw-r--r-- | examples/example4-model.html | 16 | ||||
-rw-r--r-- | examples/example5-collapsing.html | 20 | ||||
-rw-r--r-- | examples/example6-ajax-loading.html | 14 | ||||
-rw-r--r-- | examples/example7-events.html | 2 | ||||
-rw-r--r-- | examples/example8-alternative-display.html | 2 | ||||
-rw-r--r-- | examples/example9-row-reordering.html | 4 | ||||
-rw-r--r-- | examples/grid.html | 361 | ||||
-rw-r--r-- | examples/simpledropdown.css | 83 | ||||
-rw-r--r-- | slick.columnpicker.js | 116 | ||||
-rw-r--r-- | slick.globaleditorlock.js | 112 | ||||
-rw-r--r-- | slick.grid.css | 1 | ||||
-rw-r--r-- | slick.grid.js | 2305 | ||||
-rw-r--r-- | slick.model.js | 391 | ||||
-rw-r--r-- | slick.pager.js | 248 | ||||
-rw-r--r-- | slick.remotemodel.js | 276 | ||||
-rw-r--r-- | tests/model benchmarks.html | 4 | ||||
-rw-r--r-- | tests/scrolling benchmarks.html | 4 |
21 files changed, 1757 insertions, 2219 deletions
diff --git a/examples/example1-simple.html b/examples/example1-simple.html index 2c1f93f..adbc4d4 100644 --- a/examples/example1-simple.html +++ b/examples/example1-simple.html @@ -76,9 +76,7 @@ d["effortDriven"] = (i % 5 == 0); } - - - grid = new SlickGrid($("#myGrid"), data, columns, options); + grid = new Slick.Grid($("#myGrid"), data, columns, options); $("#myGrid").resizable(); }) diff --git a/examples/example10-async-post-render.html b/examples/example10-async-post-render.html index 2d072e0..1e7d9f6 100644 --- a/examples/example10-async-post-render.html +++ b/examples/example10-async-post-render.html @@ -112,7 +112,7 @@ } - grid = new SlickGrid($("#myGrid"), data, columns, options); + grid = new Slick.Grid($("#myGrid"), data, columns, options); grid.onPostProcessRowNode = function(rowNode, row, dataContext) { diff --git a/examples/example2-formatters.html b/examples/example2-formatters.html index 10c3538..037e877 100644 --- a/examples/example2-formatters.html +++ b/examples/example2-formatters.html @@ -70,7 +70,6 @@ }; - $(function() { for (var i=0; i<500; i++) { @@ -84,13 +83,7 @@ d["effortDriven"] = (i % 5 == 0); } - - grid = new SlickGrid($("#myGrid"), data, columns, options); - - grid.onHeaderContextMenu = function() { - console.log(1) - - } + grid = new Slick.Grid($("#myGrid"), data, columns, options); }) </script> diff --git a/examples/example3-editing.html b/examples/example3-editing.html index 298a6fc..0de321d 100644 --- a/examples/example3-editing.html +++ b/examples/example3-editing.html @@ -94,7 +94,7 @@ } - grid = new SlickGrid($("#myGrid"), data, columns, options); + grid = new Slick.Grid($("#myGrid"), data, columns, options); }) </script> diff --git a/examples/example4-model.html b/examples/example4-model.html index a97db68..63936a4 100644 --- a/examples/example4-model.html +++ b/examples/example4-model.html @@ -163,10 +163,10 @@ } - dataView = new DataView(); - grid = new SlickGrid($("#myGrid"), dataView.rows, columns, options); - var pager = new SlickGridPager(dataView, grid, $("#pager")); - var columnpicker = new SlickColumnPicker(columns, grid); + dataView = new Slick.Data.DataView(); + grid = new Slick.Grid($("#myGrid"), dataView.rows, columns, options); + var pager = new Slick.Controls.Pager(dataView, grid, $("#pager")); + var columnpicker = new Slick.Controls.ColumnPicker(columns, grid); grid.onAddNewRow = addItem; @@ -240,8 +240,8 @@ $("#pcSlider").slider({ "range": "min", "slide": function(event,ui) { - if (GlobalEditorLock.isEditing()) - GlobalEditorLock.cancelCurrentEdit(); + if (Slick.GlobalEditorLock.isEditing()) + Slick.GlobalEditorLock.cancelCurrentEdit(); if (percentCompleteThreshold != ui.value) { @@ -255,8 +255,8 @@ // wire up the search textbox to apply the filter to the model $("#txtSearch").keyup(function(e) { - if (GlobalEditorLock.isEditing()) - GlobalEditorLock.cancelCurrentEdit(); + if (Slick.GlobalEditorLock.isEditing()) + Slick.GlobalEditorLock.cancelCurrentEdit(); // clear on Esc if (e.which == 27) diff --git a/examples/example5-collapsing.html b/examples/example5-collapsing.html index 4c820a7..1572cec 100644 --- a/examples/example5-collapsing.html +++ b/examples/example5-collapsing.html @@ -15,14 +15,6 @@ text-align: center; } - .sort-asc { - background: silver url('../images/sort-asc.png') no-repeat right center !important; - } - - .sort-desc { - background: silver url('../images/sort-desc.png') no-repeat right center !important; - } - .toggle { height: 9px; width: 9px; @@ -212,7 +204,7 @@ // initialize the model - dataView = new DataView(); + dataView = new Slick.Data.DataView(); dataView.beginUpdate(); dataView.setItems(data); dataView.setFilter(myFilter); @@ -220,7 +212,7 @@ // initialize the grid - grid = new SlickGrid($("#myGrid"), dataView.rows, columns, options); + grid = new Slick.Grid($("#myGrid"), dataView.rows, columns, options); grid.onAddNewRow = addItem; @@ -261,8 +253,8 @@ $("#pcSlider").slider({ "range": "min", "slide": function(event,ui) { - if (GlobalEditorLock.isEditing()) - GlobalEditorLock.cancelCurrentEdit(); + if (Slick.GlobalEditorLock.isEditing()) + Slick.GlobalEditorLock.cancelCurrentEdit(); if (percentCompleteThreshold != ui.value) { @@ -276,8 +268,8 @@ // wire up the search textbox to apply the filter to the model $("#txtSearch").keyup(function(e) { - if (GlobalEditorLock.isEditing()) - GlobalEditorLock.cancelCurrentEdit(); + if (Slick.GlobalEditorLock.isEditing()) + Slick.GlobalEditorLock.cancelCurrentEdit(); // clear on Esc if (e.which == 27) diff --git a/examples/example6-ajax-loading.html b/examples/example6-ajax-loading.html index 217e394..78bbb07 100644 --- a/examples/example6-ajax-loading.html +++ b/examples/example6-ajax-loading.html @@ -10,15 +10,7 @@ .cell-story { white-space: normal!important; } - - .sort-asc { - background: silver url('../images/sort-asc.png') no-repeat right center !important; - } - - .sort-desc { - background: silver url('../images/sort-desc.png') no-repeat right center !important; - } - + .loading-indicator { display: inline-block; padding: 12px; @@ -78,7 +70,7 @@ <script> var grid; var data = []; - var loader = new RemoteModel(); + var loader = new Slick.Data.RemoteModel(); var storyTitleFormatter = function(row, cell, value, columnDef, dataContext) { return "<b><a href='" + dataContext["link"] + "' target=_blank>" + dataContext["title"] + "</a></b><br/>" + dataContext["description"]; @@ -104,7 +96,7 @@ $(function() { - grid = new SlickGrid($("#myGrid"), loader.data, columns, options); + grid = new Slick.Grid($("#myGrid"), loader.data, columns, options); grid.onViewportChanged = function() { var vp = grid.getViewport(); diff --git a/examples/example7-events.html b/examples/example7-events.html index 3660cc2..e268861 100644 --- a/examples/example7-events.html +++ b/examples/example7-events.html @@ -112,7 +112,7 @@ } - grid = new SlickGrid($("#myGrid"), data, columns, options); + grid = new Slick.Grid($("#myGrid"), data, columns, options); grid.onContextMenu = function (e, row, cell) { diff --git a/examples/example8-alternative-display.html b/examples/example8-alternative-display.html index fdd44dd..9694391 100644 --- a/examples/example8-alternative-display.html +++ b/examples/example8-alternative-display.html @@ -170,7 +170,7 @@ } - grid = new SlickGrid($("#myGrid"), data, columns, options); + grid = new Slick.Grid($("#myGrid"), data, columns, options); }) </script> diff --git a/examples/example9-row-reordering.html b/examples/example9-row-reordering.html index a245f94..bc08bbf 100644 --- a/examples/example9-row-reordering.html +++ b/examples/example9-row-reordering.html @@ -62,7 +62,6 @@ width: 40, behavior: "move", unselectable: true, - sortable: false, resizable: false, cssClass: "cell-reorder" }, { @@ -76,7 +75,6 @@ }, { id: "complete", name: "Complete", - sortable: false, width: 60, cssClass: "cell-effort-driven", field: "complete", @@ -109,7 +107,7 @@ { name: "Find out who's nice", complete: false} ]; - grid = new SlickGrid($("#myGrid"), data, columns, options); + grid = new Slick.Grid($("#myGrid"), data, columns, options); grid.onAddNewRow = function addItem(columnDef,value) { var item = {name:"New task", complete: false}; diff --git a/examples/grid.html b/examples/grid.html deleted file mode 100644 index 4bf45d2..0000000 --- a/examples/grid.html +++ /dev/null @@ -1,361 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> -<html> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> - <title>Spreadsheet prototype</title> - <link rel="stylesheet" href="simpledropdown.css" type="text/css" media="screen" charset="utf-8" /> - <link rel="stylesheet" href="../slick.grid.css" type="text/css" media="screen" charset="utf-8" /> - <link rel="stylesheet" href="../css/custom-theme/jquery-ui-1.7.2.custom.css" type="text/css" media="screen" charset="utf-8" /> - <style> - * - { - font-family: arial; - font-size: 8pt; - } - - hr { - height: 1px; - border: 0; - background: silver; - } - - #grid { - background: white; - outline: 0px; - } - - ul { - padding: 0px; - cursor: default; - } - - li { - background: url("../images/arrow_right_spearmint.png") no-repeat 0px center; - padding-left: 12px; - list-style: none; - margin: 0px; - } - - li:hover { - background: url("../images/arrow_right_peppermint.png") no-repeat 0px center; - } - - .listview-header { - height: 24px; - line-height: 24px; - width: 100%; - background: url("../images/listview.gif") no-repeat top left; - } - - .listview-header .r { - float:right; - text-align: right; - width: 50%; - height: 24px; - background: white url("../images/listview.gif") no-repeat top right; - line-height: 24px; - } - - .listview-header input { - border: 1px solid silver; - } - .listview-footer { - height: 24px; - line-height: 24px; - width: 100%; - background: url("../images/listview.gif") no-repeat left -24px; - } - - .listview-footer .r { - float:right; - text-align: right; - width: 50%; - height: 24px; - background: white url("../images/listview.gif") no-repeat right -24px; - line-height: 24px; - } - </style> - </head> - <body> - <script language="JavaScript" src="../lib/firebugx.js"></script> - <script language="JavaScript" src="../lib/jquery-1.3.2.min.js"></script> - <script language="JavaScript" src="../lib/jquery-ui-1.7.2.custom.min.js"></script> - <script language="JavaScript" src="../lib/jquery.getScrollbarWidth.js"></script> - <script language="JavaScript" src="../lib/jquery.rule-1.0.1-min.js"></script> - <script language="JavaScript" src="../lib/jquery.event.drag.custom.js"></script> - - <script language="JavaScript" src="../slick.editors.js"></script> - <script language="JavaScript" src="../slick.grid.js"></script> - <script language="JavaScript" src="../slick.globaleditorlock.js"></script> - - - <div class="listview-header"> - <div style="float:left;padding-left:14px;"> - - <span class="simple-dropdown"> - <em>Reload grid</em> - <div> - <a href="javascript:reinitGrid(10)">Load 10 rows</a> - <a href="javascript:reinitGrid(100)">Load 100 rows</a> - <a href="javascript:reinitGrid(500)">Load 500 rows</a> - <a href="javascript:reinitGrid(1000)">Load 1'000 rows</a> - <a href="javascript:reinitGrid(5000)">Load 5'000 rows</a> - <a href="javascript:reinitGrid(50000)">Load 50'000 rows</a> - </div> - </span> - - <span class="simple-dropdown"> - <em>Change column styles</em> - <div> - <a href="javascript:updateModel('%', {formatter:PercentCompleteCellFormatter, cssClass:'task-percent'})">Render <b>% Complete</b> as text</a> - <a href="javascript:updateModel('%', {formatter:GraphicalPercentCompleteCellFormatter, cssClass:null})">Render <b>% Complete</b> as graph</a> - </div> - </span> - - <span class="simple-dropdown"> - <em>Select rows</em> - <div> - <a href="javascript:grid.setSelectedRows([0,1,2,3,4])">0 - 5</a> - </div> - </span> - - <span class="simple-dropdown"> - <em>Options</em> - <div> - <a href="javascript:grid.setOptions({enableAddRow:true})">enableAddRow = true</a> - <a href="javascript:grid.setOptions({enableAddRow:false})">enableAddRow = false</a> - <hr/> - <a href="javascript:grid.setOptions({editable:true})">editable = true</a> - <a href="javascript:grid.setOptions({editable:false})">editable = false</a> - <hr/> - <a href="javascript:grid.setOptions({editOnDoubleClick:true})">editOnDoubleClick = true</a> - <a href="javascript:grid.setOptions({editOnDoubleClick:false})">editOnDoubleClick = false</a> - <hr/> - <a href="javascript:grid.setOptions({enableCellNavigation:true})">enableCellNavigation = true</a> - <a href="javascript:grid.setOptions({enableCellNavigation:false})">enableCellNavigation = false</a> - <hr/> - <a href="javascript:grid.setOptions({asyncEditorLoading:true})">asyncEditorLoading = true</a> - <a href="javascript:grid.setOptions({asyncEditorLoading:false})">asyncEditorLoading = false</a> - </div> - </span> - - <span class="simple-dropdown"> - <em>Debug</em> - <div> - <a href="javascript:grid.benchmarkFn('removeAllRows')">benchmark removeAllRows</a> - <a href="javascript:grid.benchmarkFn('render')">benchmark render</a> - <a href="javascript:grid.benchmarkFn('benchmark_render_200')">benchmark render 200</a> - <hr/> - <a href="javascript:grid.stressTest()">stress test (infinite render/clear cycle)</a> - <hr/> - <a href="javascript:grid.debug()">internal state</a> - </div> - </span> - - <span class="simple-dropdown"> - <em style="padding-left:16px;background:url('../images/info.gif') no-repeat center left;">Quick info</em> - <div style="max-width:600px"> - - This page demonstrate the use of a SlickGrid control. - <br><br> - <b>Key features:</b> - <ul> - <li>Virtual rendering/scrolling (hundreds of thousands of rows)</li> - <li>Extremely fast rendering speed</li> - <li>Support for a Model data source</li> - <li>Support for Ajax-loaded data</li> - <li>Multiple row selection</li> - <li>Edit and add new rows</li> - <li>Keyboard navigation for cell selection</li> - <li>Custom renderers for cells with conditional formatting</li> - <li>Formatters adaptive to column width</li> - <li>Custom editors for cells</li> - <li>Resizable and reorderable columns</li> - <li>Highly customizable & configurable</li> - <li>Built-in validators</li> - <li>Callbacks for events</li> - <li>Much much more...</li> - </ul> - - </div> - </span> - - </div> - - <div class="r"> - <div style="padding-right:14px;"> - <label>Filter:</label> <input type=text> - - </div> - </div> - </div> - <div id="myGrid" style="width:100%;height:500px;"></div> - <div class="listview-footer"> - <div style="float:left;padding-left:14px;"> - </div> - - <div class="r"> - <div style="padding-right:14px;"> - </div> - </div> - </div> - - <br/> - - <script> - - $(".simple-dropdown a").click(function(e) { - var dd = $(this).closest(".simple-dropdown > div"); - - dd.css("display", "none"); - window.setTimeout(function () { dd.css("display", ""); }, 10); - }); - - - function nonEmptyValidator(value) { - if (value == null || value == undefined || !value.length) - return {valid:false, msg:"This is a required field"}; - else - return {valid:true, msg:null}; - } - - function setTaskResources(resourcesNamesArray, cellInfo, dataContext) - { - dataContext["resources"] = resourcesNamesArray.length > 0 ? resourcesNamesArray.concat() : null; - } - - var data = []; - var grid; - var model = [ - {id:"#", name:"#", cssClass:"cell-move-handle", width:60, resizable:false, unselectable:true, formatter:SelectorCellFormatter}, - {id:"title", name:"Title", field:"title", formatter:TaskNameFormatter, width:300, editor:TextCellEditor, validator:nonEmptyValidator}, - {id:"star", name:"<img src='../images/bullet_star.png' align='absmiddle'>", field:"starred", formatter:StarFormatter, editor:StarCellEditor, width:16, resizable:false, cannotTriggerInsert:true}, - {id:"duration", name:"Duration", field:"duration", width:80, editor:TextCellEditor}, - {id:"%", name:"% Complete", field:"percentComplete", formatter:GraphicalPercentCompleteCellFormatter, width:60, editor:PercentCompleteCellEditor}, - {id:"start", name:"Start", field:"start", width:100, editor:DateCellEditor}, - {id:"finish", name:"Finish", field:"finish", width:100, editor:DateCellEditor}, - {id:"resources", name:"Resources <img src='../images/help.png' align='absmiddle' title='This column has an adaptive formatter. Resize to a smaller size to see alternative data representation.'>", formatter:ResourcesFormatter, rerenderOnResize:true, width:200, editor:ResourcesCellEditor, setValueHandler:setTaskResources, cannotTriggerInsert:true, minWidth:16, maxWidth:200}, - {id:"preds", name:"Predecessors", width:100, editor:TextCellEditor, cannotTriggerInsert:true} - , - {id:"e", name:"Deliverable", field:4, formatter:YesNoCellFormatter, width:60, editor:YesNoSelectCellEditor, cannotTriggerInsert:true}, - {id:"f", name:"Effort-driven", field:5, formatter:YesNoCellFormatter, width:50, editor:YesNoCheckboxCellEditor, cannotTriggerInsert:true} - ]; - - - - function updateModel(id, args) { - if (GlobalEditorLock.isEditing() && !GlobalEditorLock.commitCurrentEdit()) return; - - for (var i=0; i<model.length; i++) - { - if (model[i].id == id) { - model[i] = $.extend(model[i], args); - grid.removeAllRows(); - grid.render(); - return; - } - } - } - - - $(function() - { - $("#myGrid")[0].unselectable = true; - - reinitGrid(10); - }) - - - function enterEdit() { - - GlobalEditorLock.enterEditMode({ - commitCurrentEdit: function() { alert('validation error'); return false }, - cancelCurrentEdit: function() {} - }); - - } - - - function doSomething() { - - if (GlobalEditorLock.isEditing() && !GlobalEditorLock.commitCurrentEdit()) return; - -/* - if (GlobalEditorLock.isEditing()) - { - if (confirm("Cancel current edit?")) - GlobalEditorLock.cancelCurrentEdit(); - else - return; - } -*/ - - alert("Editor lock acquired") - - } - - - function reinitGrid(loadedRows,totalRows) { - - data = []; - - for (var i = 0; i < loadedRows; i++) - { - var d = (data[i] = {}); - - d["title"] = "Task " + i; - d["duration"] = "5 days"; - d["percentComplete"] = Math.round(Math.random() * 100); - d["start"] = "01/01/2009"; - d["finish"] = "01/05/2009"; - d["indent"] = i % 5; - d["resources"] = (i % 7 == 0) ? ["Boris The Blade", "Bullet Tooth"] : (i % 11 == 0 ? ["Bricktop"] : null); - } - - - if (grid) grid.destroy(); - - - grid = new SlickGrid($("#myGrid"), data, model, {}); - - grid.onValidationError = function(elem, validationResults, row, cell, cellInfo) { - console.warn(validationResults.msg); - } - - grid.onAddNewRow = function(cellInfo, value) { - var item = {title:"New task", indent:0, duration:"1 day", percentComplete:0, start:"01/01/2009", finish:"01/01/2009"}; - - item[cellInfo.field] = value; - - data[data.length] = item; - - grid.updateRowCount(); - grid.render(); - grid.updateRow(data.length-1); - } - - grid.onClick = function(e, row, cell) { - // toggle expand/collapse icon - if (model[cell].id == "title" && $(e.target).is("img")) - { - $(e.target).attr("src", $(e.target).attr("src") != "../images/collapse.gif" ? "../images/collapse.gif" : "../images/expand.gif"); - return true; - } - - if (model[cell].id == "#") - { - grid.setSelectedRows([row]); - } - - // pass the event through - return false; - } - } - - - </script> - - - <button onclick="doSomething()">Test GlobalEditorLock lock</button> - </body> -</html> diff --git a/examples/simpledropdown.css b/examples/simpledropdown.css deleted file mode 100644 index ffd71f7..0000000 --- a/examples/simpledropdown.css +++ /dev/null @@ -1,83 +0,0 @@ -.simple-dropdown { - z-index: 100; - overflow: visible; - display: inline-block; - line-height: normal; - - border: 1px solid silver; - background: #eee url("images/down.gif") no-repeat center right; - padding: 2px; - padding-left: 8px; - padding-right: 20px; - margin: 2px; - - -moz-border-radius: 5px; - -webkit-border-radius: 5px; -} - -.simple-dropdown > em { - display: block; - - text-decoration: none; - font-style: normal; - cursor: default; -} - -.simple-dropdown:hover { - background-color: #777; - color: white; - border-color: gray; -} - -.simple-dropdown:hover > div { - -display: block; - visibility: visible; - - opacity: 1; - -webkit-transition: opacity 0.5s; -} - -.simple-dropdown > div { - z-index: 100; - -display: none; - visibility: hidden; - position: absolute; - margin-left: -9px; - min-width: 120px; - max-width: 200px; - - color: black; - text-align: left; - padding: 4px; - background: #fafafa; - border: 1px solid gray; - - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - -moz-border-radius-topleft: 0px; - -webkit-border-top-left-radius: 0px; - -moz-box-shadow: 2px 2px 2px silver; - -webkit-box-shadow: 2px 2px 2px silver; - - opacity: 0; -} - -.simple-dropdown > div a { - display: block; - padding: 2px; - outline: 0px; - text-decoration: none; - color: black; - cursor: default; - zbackground: #fafafa url("images/arrow_right_spearmint.png") no-repeat center left; - padding-left: 12px; - padding-right: 6px; -} - - -.simple-dropdown > div a:hover { - background: skyblue url("images/arrow_right_peppermint.png") no-repeat center left; - - -moz-border-radius: 3px; - -webkit-border-radius: 3px; -}
\ No newline at end of file diff --git a/slick.columnpicker.js b/slick.columnpicker.js index 012394c..476c66b 100644 --- a/slick.columnpicker.js +++ b/slick.columnpicker.js @@ -1,71 +1,75 @@ -function SlickColumnPicker(columns,grid) -{ - var $menu; - - function init() { - grid.onHeaderContextMenu = displayContextMenu; - - $menu = $("<span class='slick-columnpicker' style='display:none;position:absolute;z-index:20;' />").appendTo(document.body); - - $menu.bind("mouseleave", function(e) { $(this).fadeOut() }); - $menu.bind("click", updateColumn); - - } - - function displayContextMenu(e) +(function() { + function SlickColumnPicker(columns,grid) { - $menu.empty(); + var $menu; - for (var i=0; i<columns.length; i++) { - var $li = $("<li />").appendTo($menu); + function init() { + grid.onHeaderContextMenu = displayContextMenu; - var $input = $("<input type='checkbox' />") - .attr("id", "columnpicker_" + i) - .data("id", columns[i].id) - .appendTo($li) + $menu = $("<span class='slick-columnpicker' style='display:none;position:absolute;z-index:20;' />").appendTo(document.body); - if (!columns[i].hidden) - $input.attr("checked","checked"); + $menu.bind("mouseleave", function(e) { $(this).fadeOut() }); + $menu.bind("click", updateColumn); - $("<label for='columnpicker_" + i + "' />") - .text(columns[i].name) - .appendTo($li); } - $("<hr/><li><a id='autoresize'>Autosize</a></li>").appendTo($menu); - - $menu - .css("top", e.pageY - 10) - .css("left", e.pageX - 10) - .fadeIn(); - } - - function updateColumn(e) - { - if ($(e.target).is("a")) { - grid.autosizeColumns(); - $menu.fadeOut(); - return; + function displayContextMenu(e) + { + $menu.empty(); + + for (var i=0; i<columns.length; i++) { + var $li = $("<li />").appendTo($menu); + + var $input = $("<input type='checkbox' />") + .attr("id", "columnpicker_" + i) + .data("id", columns[i].id) + .appendTo($li) + + if (!columns[i].hidden) + $input.attr("checked","checked"); + + $("<label for='columnpicker_" + i + "' />") + .text(columns[i].name) + .appendTo($li); + } + + $("<hr/><li><a id='autoresize'>Autosize</a></li>").appendTo($menu); + + $menu + .css("top", e.pageY - 10) + .css("left", e.pageX - 10) + .fadeIn(); } - if ($(e.target).is(":checkbox")) { - if ($menu.find(":checkbox:checked").length == 0) { - $(e.target).attr("checked","checked"); - return; + function updateColumn(e) + { + if ($(e.target).is("a")) { + grid.autosizeColumns(); + $menu.fadeOut(); + return; } - var id =$(e.target).data("id"); - for (var i=0; i<columns.length; i++) { - if (columns[i].id == id) { - columns[i].hidden = !$(e.target).is(":checked"); - grid.setColumnVisibility(columns[i], $(e.target).is(":checked")); - return; + if ($(e.target).is(":checkbox")) { + if ($menu.find(":checkbox:checked").length == 0) { + $(e.target).attr("checked","checked"); + return; } - } + + var id =$(e.target).data("id"); + for (var i=0; i<columns.length; i++) { + if (columns[i].id == id) { + columns[i].hidden = !$(e.target).is(":checked"); + grid.setColumnVisibility(columns[i], $(e.target).is(":checked")); + return; + } + } + } } + + + init(); } - - init(); -} - + // Slick.Controls.ColumnPicker + $.extend(true, window, { Slick: { Controls: { ColumnPicker: SlickColumnPicker }}}); +})();
\ No newline at end of file diff --git a/slick.globaleditorlock.js b/slick.globaleditorlock.js index de6bc26..da64a0e 100644 --- a/slick.globaleditorlock.js +++ b/slick.globaleditorlock.js @@ -1,54 +1,58 @@ -/*** - * A singleton for controlling access to the editing functionality for multiple components capable of editing the same data. - */ -var GlobalEditorLock = new function() -{ - var currentEditor = null; - - this.isEditing = function() - { - return (currentEditor != null); - } - - this.hasLock = function(editor) - { - return (currentEditor == editor); - } - - this.enterEditMode = function(editor) - { - if (currentEditor != null) - throw "GlobalEditorLock : enterEditMode : currentEditor == null"; - - if (!editor.commitCurrentEdit) - throw "GlobalEditorLock : enterEditMode : editor must implement .commitCurrentEdit()"; - - if (!editor.cancelCurrentEdit) - throw "GlobalEditorLock : enterEditMode : editor must implement .cancelCurrentEdit()"; - - currentEditor = editor; - } - - this.leaveEditMode = function(editor) - { - if (currentEditor != editor) - throw "GlobalEditorLock : leaveEditMode() : currentEditor != editor"; - - currentEditor = null; - } - - this.commitCurrentEdit = function() - { - if (currentEditor) - return currentEditor.commitCurrentEdit(); - - return true; - } - - this.cancelCurrentEdit = function() - { - if (currentEditor) - currentEditor.cancelCurrentEdit(); - } -}; - +(function() { + /*** + * A singleton for controlling access to the editing functionality for multiple components capable of editing the same data. + */ + var GlobalEditorLock = new function() + { + var currentEditor = null; + + this.isEditing = function() + { + return (currentEditor != null); + } + + this.hasLock = function(editor) + { + return (currentEditor == editor); + } + + this.enterEditMode = function(editor) + { + if (currentEditor != null) + throw "GlobalEditorLock : enterEditMode : currentEditor == null"; + + if (!editor.commitCurrentEdit) + throw "GlobalEditorLock : enterEditMode : editor must implement .commitCurrentEdit()"; + + if (!editor.cancelCurrentEdit) + throw "GlobalEditorLock : enterEditMode : editor must implement .cancelCurrentEdit()"; + + currentEditor = editor; + } + + this.leaveEditMode = function(editor) + { + if (currentEditor != editor) + throw "GlobalEditorLock : leaveEditMode() : currentEditor != editor"; + + currentEditor = null; + } + + this.commitCurrentEdit = function() + { + if (currentEditor) + return currentEditor.commitCurrentEdit(); + + return true; + } + + this.cancelCurrentEdit = function() + { + if (currentEditor) + currentEditor.cancelCurrentEdit(); + } + }; + + // Slick.GlobalEditorLock + $.extend(true, window, { Slick: { GlobalEditorLock: GlobalEditorLock }}); +})(); diff --git a/slick.grid.css b/slick.grid.css index 0df061a..5b5a683 100644 --- a/slick.grid.css +++ b/slick.grid.css @@ -126,6 +126,7 @@ padding: 2px; padding-top: 1px; padding-left: 1px; + white-space: nowrap; } .grid-canvas .r .c.editable { diff --git a/slick.grid.js b/slick.grid.js index 29bf468..e92ee02 100644 --- a/slick.grid.js +++ b/slick.grid.js @@ -11,7 +11,7 @@ * * KNOWN ISSUES: * - keyboard navigation doesn't "jump" over unselectable cells for now -* + * * * OPTIONS: * rowHeight - Row height in pixels. @@ -65,1299 +65,1286 @@ * @param {Object} options Grid options. * */ -function SlickGrid($container,data,columns,options) -{ - // settings - var defaults = { - rowHeight: 25, - defaultColumnWidth: 80, - enableAddRow: false, - leaveSpaceForNewRows: false, - manualScrolling: false, - editable: false, - editOnDoubleClick: false, - enableCellNavigation: true, - enableColumnReorder: true, - asyncEditorLoading: false, - forceFitColumns: false - }; - - var columnDefaults = { - resizable: true, - sortable: false, - formatter: defaultFormatter - } - - // consts - var CAPACITY = 50; - var MIN_BUFFER = 5; - var BUFFER = MIN_BUFFER; // will be set to equal one page - var POSTPROCESSING_DELAY = 50; - - // private - var uid = "slickgrid_" + Math.round(1000000 * Math.random()); - var self = this; - var $divHeadersScroller; - var $divHeaders; - var $divMainScroller; - var $divMain; - var viewportH, viewportW; - var headerColumnWidthDiff, headerColumnHeightDiff, cellWidthDiff, cellHeightDiff; // padding+border - - var currentRow, currentCell; - var currentCellNode = null; - var currentEditor = null; - - var rowsCache = {}; - var renderedRows = 0; - var numVisibleRows; - var lastRenderedScrollTop = 0; - var currentScrollTop = 0; - var currentScrollLeft = 0; - var scrollDir = 1; - var avgRowRenderTime = 10; - - var selectedRows = []; - var selectedRowsLookup = {}; - var columnsById = {}; - - // async call handles - var h_editorLoader = null; - var h_render = null; - var h_postrender = null; - var postProcessedRows = {}; - var rowsToPostProcess = []; - - // perf counters - var counter_rows_rendered = 0; - var counter_rows_removed = 0; - - - function init() { - options = $.extend({},defaults,options); - columnDefaults.width = options.defaultColumnWidth; - - $container - .empty() - .attr("tabIndex",0) - .attr("hideFocus",true) - .css("overflow","hidden") - .css("outline",0) - .css("position","relative") - .addClass(uid); - - $divHeadersScroller = $("<div class='slick-header' style='overflow:hidden;position:relative;' />").appendTo($container); - $divHeaders = $("<div class='slick-header-columns' style='width:100000px' />").appendTo($divHeadersScroller); - $divMainScroller = $("<div tabIndex='0' hideFocus style='width:100%;overflow-x:auto;overflow-y:scroll;outline:0;position:relative;outline:0px;'>").appendTo($container); - $divMain = $("<div class='grid-canvas' tabIndex='0' hideFocus />").appendTo($divMainScroller); - - // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?) - // calculate the diff so we can set consistent sizes - measureCellPaddingAndBorder(); - - $divMainScroller.height($container.innerHeight() - $divHeadersScroller.outerHeight()); - - if ($.browser.msie) - $divMainScroller[0].onselectstart = function() { - if (event.srcElement.tagName != "INPUT" && event.srcElement.tagName != "TEXTAREA") - return false; - }; - - $divHeaders.disableSelection(); - - createColumnHeaders(); - setupMoveEvents(); - createCssRules(); - resizeCanvas(); - if (options.forceFitColumns) - autosizeColumns(); - render(); - - if (!options.manualScrolling) - $divMainScroller.bind("scroll", handleScroll); - - $container.bind("resize", resizeCanvas); +(function() { + function SlickGrid($container,data,columns,options) + { + // settings + var defaults = { + rowHeight: 25, + defaultColumnWidth: 80, + enableAddRow: false, + leaveSpaceForNewRows: false, + manualScrolling: false, + editable: false, + editOnDoubleClick: false, + enableCellNavigation: true, + enableColumnReorder: true, + asyncEditorLoading: false, + forceFitColumns: false + }; + + var columnDefaults = { + resizable: true, + sortable: false, + formatter: defaultFormatter + } - $divMain.bind("keydown", handleKeyDown); - $divMain.bind("click", handleClick); - $divMain.bind("dblclick", handleDblClick); - $divMain.bind("contextmenu", handleContextMenu) - $divHeadersScroller.bind("contextmenu", handleHeaderContextMenu); - } + // consts + var CAPACITY = 50; + var MIN_BUFFER = 5; + var BUFFER = MIN_BUFFER; // will be set to equal one page + var POSTPROCESSING_DELAY = 50, EDITOR_LOAD_DELAY = 100; + + // private + var uid = "slickgrid_" + Math.round(1000000 * Math.random()); + var self = this; + var $divHeadersScroller; + var $divHeaders; + var $divMainScroller; + var $divMain; + var viewportH, viewportW; + var headerColumnWidthDiff, headerColumnHeightDiff, cellWidthDiff, cellHeightDiff; // padding+border + + var currentRow, currentCell; + var currentCellNode = null; + var currentEditor = null; + + var rowsCache = {}; + var renderedRows = 0; + var numVisibleRows; + var lastRenderedScrollTop = 0; + var currentScrollTop = 0; + var currentScrollLeft = 0; + var scrollDir = 1; + var avgRowRenderTime = 10; + + var selectedRows = []; + var selectedRowsLookup = {}; + var columnsById = {}; + + // async call handles + var h_editorLoader = null; + var h_render = null; + var h_postrender = null; + var postProcessedRows = {}; + var rowsToPostProcess = []; + + // perf counters + var counter_rows_rendered = 0; + var counter_rows_removed = 0; + + + function init() { + options = $.extend({},defaults,options); + columnDefaults.width = options.defaultColumnWidth; + + $container + .empty() + .attr("tabIndex",0) + .attr("hideFocus",true) + .css("overflow","hidden") + .css("outline",0) + .css("position","relative") + .addClass(uid); + + $divHeadersScroller = $("<div class='slick-header' style='overflow:hidden;position:relative;' />").appendTo($container); + $divHeaders = $("<div class='slick-header-columns' style='width:100000px' />").appendTo($divHeadersScroller); + $divMainScroller = $("<div tabIndex='0' hideFocus style='width:100%;overflow-x:auto;overflow-y:scroll;outline:0;position:relative;outline:0px;'>").appendTo($container); + $divMain = $("<div class='grid-canvas' tabIndex='0' hideFocus />").appendTo($divMainScroller); + + // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?) + // calculate the diff so we can set consistent sizes + measureCellPaddingAndBorder(); + + $divMainScroller.height($container.innerHeight() - $divHeadersScroller.outerHeight()); + + if ($.browser.msie) + $divMainScroller[0].onselectstart = function() { + if (event.srcElement.tagName != "INPUT" && event.srcElement.tagName != "TEXTAREA") + return false; + }; + + $divHeaders.disableSelection(); - function createColumnHeaders() { - for (var i = 0; i < columns.length; i++) { - var m = columns[i] = $.extend({},columnDefaults,columns[i]); - columnsById[m.id] = i; - - var header = $("<div class='slick-header-column' cell=" + i + " id='" + m.id + "' />") - .html(m.name) - .width(m.width - headerColumnWidthDiff) - .appendTo($divHeaders); - - if (m.sortable) header.append("<span class='slick-sort-indicator' />") - if (m.resizable) header.append("<div class='slick-resizable-handle' />"); + createColumnHeaders(); + setupMoveEvents(); + createCssRules(); + resizeCanvas(); + if (options.forceFitColumns) + autosizeColumns(); + render(); + + if (!options.manualScrolling) + $divMainScroller.bind("scroll", handleScroll); + + $container.bind("resize", resizeCanvas); + + $divMain.bind("keydown", handleKeyDown); + $divMain.bind("click", handleClick); + $divMain.bind("dblclick", handleDblClick); + $divMain.bind("contextmenu", handleContextMenu) + $divHeadersScroller.bind("contextmenu", handleHeaderContextMenu); } - setupColumnSort(); - setupColumnResizeEvents(); - if (options.enableColumnReorder) - setupColumnReorderEvents(); - } - - function setupColumnSort() { - $divHeaders.click(function(e) { - var $col = $(e.target); - if (!$col.hasClass("slick-header-column") || !columns[columnsById[$col.attr("id")]].sortable) - return; - - if (currentEditor && !commitCurrentEdit()) return; - - if ($col.is(".slick-header-column-sorted")) - { - $col.find(".slick-sort-indicator").toggleClass("slick-sort-indicator-asc").toggleClass("slick-sort-indicator-desc"); - } - else - { - $divHeaders.children().removeClass("slick-header-column-sorted"); - $divHeaders.find(".slick-sort-indicator").removeClass("slick-sort-indicator-asc slick-sort-indicator-desc"); - $col.addClass("slick-header-column-sorted"); - $col.find(".slick-sort-indicator").addClass("slick-sort-indicator-asc"); - } - - if (self.onSort) - self.onSort(columns[columnsById[$col.attr("id")]], $col.find(".slick-sort-indicator").hasClass("slick-sort-indicator-asc")); - }) - } - - function setupColumnReorderEvents() { - $divHeaders.sortable({ - axis: "x", - cursor: "default", - tolerance: "intersect", - helper: "clone", - placeholder: "slick-sortable-placeholder slick-header-column", - forcePlaceholderSize: true, - start: function(e, ui) { $(ui.helper).addClass("slick-header-column-active") }, - beforeStop: function(e, ui) { $(ui.helper).removeClass("slick-header-column-active") }, - stop: function(e, ui) { - if (currentEditor && !commitCurrentEdit()) { - $(this).sortable("cancel"); - return; - } + function createColumnHeaders() { + for (var i = 0; i < columns.length; i++) { + var m = columns[i] = $.extend({},columnDefaults,columns[i]); + columnsById[m.id] = i; - var newOrder = $divHeaders.sortable("toArray"), lookup = {}; - for (var i=0; i<columns.length; i++) { - lookup[columns[i].id] = columns[i]; - } - - for (var i=0; i<newOrder.length; i++) { - columnsById[newOrder[i]] = i; - columns[i] = lookup[newOrder[i]]; - } + var header = $("<div class='slick-header-column' cell=" + i + " id='" + m.id + "' />") + .html(m.name) + .width(m.width - headerColumnWidthDiff) + .appendTo($divHeaders); - removeAllRows(); - removeCssRules(); - createCssRules(); - render(); + if (m.sortable) header.append("<span class='slick-sort-indicator' />") + if (m.resizable) header.append("<div class='slick-resizable-handle' />"); + } + + setupColumnSort(); + setupColumnResizeEvents(); + if (options.enableColumnReorder) + setupColumnReorderEvents(); + } + + function setupColumnSort() { + $divHeaders.click(function(e) { + var $col = $(e.target); + if (!$col.hasClass("slick-header-column") || !columns[columnsById[$col.attr("id")]].sortable) + return; - if (self.onColumnsReordered) - self.onColumnsReordered(); - - e.stopPropagation(); - } - }) - } + if (currentEditor && !commitCurrentEdit()) return; - function setupColumnResizeEvents() { - $divHeaders - .find(".slick-resizable-handle") - .bind('dragstart', function(e) { - var $col = $(this).parent(); - var colId = $col.attr("id"); - if (!columns[columnsById[colId]].resizable) return false; - if (currentEditor && !commitCurrentEdit()) return false; - - $col - .data("colId", colId) - .data("width", $col.width()) - .data("pageX", e.pageX) - .addClass("slick-header-column-active"); - }) - .bind('drag', function(e) { - var $col = $(this).parent(), w = $col.data("width") - $col.data("pageX") + e.pageX; - var cell = columnsById[$col.data("colId")]; - var m = columns[cell]; - if (m.minWidth) w = Math.max(m.minWidth - headerColumnWidthDiff,w); - if (m.maxWidth) w = Math.min(m.maxWidth - headerColumnWidthDiff,w); - $col.css({ width: Math.max(0, w) }); - }) - .bind('dragend', function(e) { - var $col = $(this).parent(); - var cell = columnsById[$col.data("colId")]; - $col.removeClass("slick-header-column-active"); - columns[cell].width = $col.outerWidth(); - - if (options.forceFitColumns) - autosizeColumns(columns[cell]); - else { - updateColumnWidth(cell, $col.outerWidth()); - resizeCanvas(); - } - - if (columns[cell].rerenderOnResize) - removeAllRows(); - - render(); - }) - } - - function setupMoveEvents() { - $divMain - .bind("beforedragstart", function(e) { - var $cell = $(e.target).closest(".c"); - if ($cell.length == 0) return false; - if (parseInt($cell.parent().attr("row")) >= data.length) return false; - var colDef = columns[$cell.attr("cell")]; - if (colDef.behavior != "move") return false; - }) - .bind("dragstart", function(e) { - if (currentEditor && !commitCurrentEdit()) return false; - - var row = parseInt($(e.target).closest(".r").attr("row")); + if ($col.is(".slick-header-column-sorted")) + { + $col.find(".slick-sort-indicator").toggleClass("slick-sort-indicator-asc").toggleClass("slick-sort-indicator-desc"); + } + else + { + $divHeaders.children().removeClass("slick-header-column-sorted"); + $divHeaders.find(".slick-sort-indicator").removeClass("slick-sort-indicator-asc slick-sort-indicator-desc"); + $col.addClass("slick-header-column-sorted"); + $col.find(".slick-sort-indicator").addClass("slick-sort-indicator-asc"); + } - if (!selectedRowsLookup[row]) - setSelectedRows([row]); - - var $selectionProxy = $("<div class='slick-reorder-proxy'/>"); - $selectionProxy - .css("position", "absolute") - .css("zIndex", "99999") - .css("width", $(this).innerWidth()) - .css("height", options.rowHeight*selectedRows.length) - .appendTo($divMainScroller); - - $(this) - .data("selectionProxy", $selectionProxy) - .data("insertBefore", -1); - - var $guide = $("<div class='slick-reorder-guide'/>"); - $guide - .css("position", "absolute") - .css("zIndex", "99998") - .css("width", $(this).innerWidth()) - .css("top", -1000) - .appendTo($divMainScroller); - - return $guide; - }) - .bind("drag", function(e) { - var top = e.clientY - $(this).offset().top; - $(this).data("selectionProxy").css("top",top-5); - - var insertBefore = Math.max(0,Math.min(Math.round(top/options.rowHeight),data.length)); - if (insertBefore != $(this).data("insertBefore")) { - if (self.onBeforeMoveRows && self.onBeforeMoveRows(selectedRows.concat(),insertBefore) === false) - $(e.dragProxy).css("top", -1000); - else - $(e.dragProxy).css("top",insertBefore*options.rowHeight); - $(this).data("insertBefore", insertBefore); - } - }) - .bind("dragend", function(e) { - $(e.dragProxy).remove(); - $(this).data("selectionProxy").remove(); - var insertBefore = $(this).data("insertBefore"); - $(this).removeData("selectionProxy").removeData("insertBefore"); - if (self.onMoveRows) self.onMoveRows(selectedRows.concat(),insertBefore); - }) - } - - function measureCellPaddingAndBorder() { - var tmp = $("<div class='slick-header-column cell='' id='' style='visibility:hidden'>-</div>").appendTo($divHeaders); - headerColumnWidthDiff = tmp.outerWidth() - tmp.width(); - headerColumnHeightDiff = tmp.outerHeight() - tmp.height(); - tmp.remove(); - - var r = $("<div class='r' />").appendTo($divMain); - tmp = $("<div class='c' cell='' id='' style='visibility:hidden'>-</div>").appendTo(r); - cellWidthDiff = tmp.outerWidth() - tmp.width(); - cellHeightDiff = tmp.outerHeight() - tmp.height(); - r.remove(); - } - - function createCssRules() { - var $style = $("<style type='text/css' rel='stylesheet' lib='slickgrid' />").appendTo($("head")); - $.rule(".grid-canvas .r .c { height:" + (options.rowHeight - cellHeightDiff) + "px;}").appendTo($style); - - for (var i = 0; i < columns.length; i++) { - $.rule( - "." + uid + " .grid-canvas .c" + i + " { " + - "width:" + (columns[i].width - cellWidthDiff) + "px; " + - "display: " + (columns[i].hidden ? "none" : "block") + - " }").appendTo($style); + if (self.onSort) + self.onSort(columns[columnsById[$col.attr("id")]], $col.find(".slick-sort-indicator").hasClass("slick-sort-indicator-asc")); + }) } - } - - function removeCssRules() { - $("style[lib=slickgrid]").remove(); - } - function destroy() { - if (currentEditor) - cancelCurrentEdit(); - - $divHeaders.sortable("destroy"); - $container.unbind("resize", resizeCanvas); - removeCssRules(); + function setupColumnReorderEvents() { + $divHeaders.sortable({ + axis: "x", + cursor: "default", + tolerance: "intersect", + helper: "clone", + placeholder: "slick-sortable-placeholder slick-header-column", + forcePlaceholderSize: true, + start: function(e, ui) { $(ui.helper).addClass("slick-header-column-active") }, + beforeStop: function(e, ui) { $(ui.helper).removeClass("slick-header-column-active") }, + stop: function(e, ui) { + if (currentEditor && !commitCurrentEdit()) { + $(this).sortable("cancel"); + return; + } + + var newOrder = $divHeaders.sortable("toArray"), lookup = {}; + for (var i=0; i<columns.length; i++) { + lookup[columns[i].id] = columns[i]; + } + + for (var i=0; i<newOrder.length; i++) { + columnsById[newOrder[i]] = i; + columns[i] = lookup[newOrder[i]]; + } + + removeAllRows(); + removeCssRules(); + createCssRules(); + render(); + + if (self.onColumnsReordered) + self.onColumnsReordered(); + + e.stopPropagation(); + } + }) + } - $container.empty().removeClass(uid); - } - - ////////////////////////////////////////////////////////////////////////////////////////////// - // General + function setupColumnResizeEvents() { + $divHeaders + .find(".slick-resizable-handle") + .bind('dragstart', function(e) { + var $col = $(this).parent(); + var colId = $col.attr("id"); + if (!columns[columnsById[colId]].resizable) return false; + if (currentEditor && !commitCurrentEdit()) return false; + + $col + .data("colId", colId) + .data("width", $col.width()) + .data("pageX", e.pageX) + .addClass("slick-header-column-active"); + }) + .bind('drag', function(e) { + var $col = $(this).parent(), w = $col.data("width") - $col.data("pageX") + e.pageX; + var cell = columnsById[$col.data("colId")]; + var m = columns[cell]; + if (m.minWidth) w = Math.max(m.minWidth - headerColumnWidthDiff,w); + if (m.maxWidth) w = Math.min(m.maxWidth - headerColumnWidthDiff,w); + $col.css({ width: Math.max(0, w) }); + }) + .bind('dragend', function(e) { + var $col = $(this).parent(); + var cell = columnsById[$col.data("colId")]; + $col.removeClass("slick-header-column-active"); + columns[cell].width = $col.outerWidth(); + + if (options.forceFitColumns) + autosizeColumns(columns[cell]); + else { + updateColumnWidth(cell, $col.outerWidth()); + resizeCanvas(); + } + + if (columns[cell].rerenderOnResize) + removeAllRows(); + + render(); + }) + } - function getColumnIndex(id) { - return columnsById[id]; - } + function setupMoveEvents() { + $divMain + .bind("beforedragstart", function(e) { + var $cell = $(e.target).closest(".c"); + if ($cell.length == 0) return false; + if (parseInt($cell.parent().attr("row")) >= data.length) return false; + var colDef = columns[$cell.attr("cell")]; + if (colDef.behavior != "move") return false; + }) + .bind("dragstart", function(e) { + if (currentEditor && !commitCurrentEdit()) return false; + + var row = parseInt($(e.target).closest(".r").attr("row")); + + if (!selectedRowsLookup[row]) + setSelectedRows([row]); + + var $selectionProxy = $("<div class='slick-reorder-proxy'/>"); + $selectionProxy + .css("position", "absolute") + .css("zIndex", "99999") + .css("width", $(this).innerWidth()) + .css("height", options.rowHeight*selectedRows.length) + .appendTo($divMainScroller); - function autosizeColumns(columnToHold) { - var availWidth = viewportW-$.getScrollbarWidth(); - var total = 0; - var existingTotal = 0; - var minWidth = Math.max(headerColumnWidthDiff,cellWidthDiff); - - for (var i = 0; i < columns.length; i++) { - if (!columns[i].hidden) - existingTotal += columns[i].width; + $(this) + .data("selectionProxy", $selectionProxy) + .data("insertBefore", -1); + + var $guide = $("<div class='slick-reorder-guide'/>"); + $guide + .css("position", "absolute") + .css("zIndex", "99998") + .css("width", $(this).innerWidth()) + .css("top", -1000) + .appendTo($divMainScroller); + + return $guide; + }) + .bind("drag", function(e) { + var top = e.clientY - $(this).offset().top; + $(this).data("selectionProxy").css("top",top-5); + + var insertBefore = Math.max(0,Math.min(Math.round(top/options.rowHeight),data.length)); + if (insertBefore != $(this).data("insertBefore")) { + if (self.onBeforeMoveRows && self.onBeforeMoveRows(selectedRows.concat(),insertBefore) === false) + $(e.dragProxy).css("top", -1000); + else + $(e.dragProxy).css("top",insertBefore*options.rowHeight); + $(this).data("insertBefore", insertBefore); + } + }) + .bind("dragend", function(e) { + $(e.dragProxy).remove(); + $(this).data("selectionProxy").remove(); + var insertBefore = $(this).data("insertBefore"); + $(this).removeData("selectionProxy").removeData("insertBefore"); + if (self.onMoveRows) self.onMoveRows(selectedRows.concat(),insertBefore); + }) } - total = existingTotal; - - removeAllRows(); - - // shrink - var workdone = true; - while (total > availWidth && workdone) { - workdone = false; - for (var i = 0; i < columns.length && total > availWidth; i++) { - var c = columns[i]; - if (c.hidden || !c.resizable || c.minWidth == c.width || c.width == minWidth || (columnToHold && columnToHold.id == c.id)) continue; - total -= 1; - c.width -= 1; - workdone = true; - } + function measureCellPaddingAndBorder() { + var tmp = $("<div class='slick-header-column cell='' id='' style='visibility:hidden'>-</div>").appendTo($divHeaders); + headerColumnWidthDiff = tmp.outerWidth() - tmp.width(); + headerColumnHeightDiff = tmp.outerHeight() - tmp.height(); + tmp.remove(); + + var r = $("<div class='r' />").appendTo($divMain); + tmp = $("<div class='c' cell='' id='' style='visibility:hidden'>-</div>").appendTo(r); + cellWidthDiff = tmp.outerWidth() - tmp.width(); + cellHeightDiff = tmp.outerHeight() - tmp.height(); + r.remove(); } - // shrink the column being "held" as a last resort - if (total > availWidth && columnToHold && columnToHold.resizable && !columnToHold.hidden) { - while (total > availWidth) { - if (columnToHold.minWidth == columnToHold.width || columnToHold.width == minWidth) break; - total -= 1; - columnToHold.width -= 1; + function createCssRules() { + var $style = $("<style type='text/css' rel='stylesheet' lib='slickgrid' />").appendTo($("head")); + $.rule(".grid-canvas .r .c { height:" + (options.rowHeight - cellHeightDiff) + "px;}").appendTo($style); + + for (var i = 0; i < columns.length; i++) { + $.rule( + "." + uid + " .grid-canvas .c" + i + " { " + + "width:" + (columns[i].width - cellWidthDiff) + "px; " + + "display: " + (columns[i].hidden ? "none" : "block") + + " }").appendTo($style); } } - // grow - workdone = true; - while (total < availWidth && workdone) { - workdone = false; - for (var i = 0; i < columns.length && total < availWidth; i++) { - var c = columns[i]; - if (c.hidden || !c.resizable || c.maxWidth == c.width || (columnToHold && columnToHold.id == c.id)) continue; - total += 1; - c.width += 1; - workdone = true; - } + function removeCssRules() { + $("style[lib=slickgrid]").remove(); } - - // grow the column being "held" as a last resort - if (total < availWidth && columnToHold && columnToHold.resizable && !columnToHold.hidden) { - while (total < availWidth) { - if (columnToHold.maxWidth == columnToHold.width) break; - total += 1; - columnToHold.width += 1; - } - } - - for (var i=0; i<columns.length; i++) { - updateColumnWidth(i, columns[i].width); + + function destroy() { + if (currentEditor) + cancelCurrentEdit(); + + $divHeaders.sortable("destroy"); + $container.unbind("resize", resizeCanvas); + removeCssRules(); + + $container.empty().removeClass(uid); } - resizeCanvas(); - } - - function updateColumnWidth(index,width) { - columns[index].width = width; - $divHeaders.find(".slick-header-column[id=" + columns[index].id + "]").css("width",width - headerColumnWidthDiff); - $.rule("." + uid + " .grid-canvas .c" + index, "style[lib=slickgrid]").css("width", (columns[index].width - cellWidthDiff) + "px"); - } - - function setColumnVisibility(column,visible) { - var index = columnsById[column.id]; - columns[index].hidden = !visible; - resizeCanvas(); - var header = $divHeaders.find("[id=" + columns[index].id + "]"); - header.css("display", visible?"block":"none"); - $.rule("." + uid + " .grid-canvas .c" + index, "style[lib=slickgrid]").css("display", visible?"block":"none"); - - if (options.forceFitColumns) - autosizeColumns(columns[index]); - } - - function getSelectedRows() { - return selectedRows.concat(); - } - - function setSelectedRows(rows) { - if (GlobalEditorLock.isEditing() && !GlobalEditorLock.hasLock(self)) - throw "Grid : setSelectedRows : cannot set selected rows when somebody else has an edit lock"; - - var lookup = {}; - for (var i=0; i<rows.length; i++) - lookup[rows[i]] = true; + ////////////////////////////////////////////////////////////////////////////////////////////// + // General - // unselect old rows - for (var i=0; i<selectedRows.length; i++) { - var row = selectedRows[i]; - if (rowsCache[row] && !lookup[row]) - $(rowsCache[row]).removeClass("selected"); + function getColumnIndex(id) { + return columnsById[id]; } - - // select new ones - for (var i=0; i<rows.length; i++) { - var row = rows[i]; - if (rowsCache[row] && !selectedRowsLookup[row]) - $(rowsCache[row]).addClass("selected"); + + function autosizeColumns(columnToHold) { + var availWidth = viewportW-$.getScrollbarWidth(); + var total = 0; + var existingTotal = 0; + var minWidth = Math.max(headerColumnWidthDiff,cellWidthDiff); + + for (var i = 0; i < columns.length; i++) { + if (!columns[i].hidden) + existingTotal += columns[i].width; + } + + total = existingTotal; + + removeAllRows(); + + // shrink + var workdone = true; + while (total > availWidth && workdone) { + workdone = false; + for (var i = 0; i < columns.length && total > availWidth; i++) { + var c = columns[i]; + if (c.hidden || !c.resizable || c.minWidth == c.width || c.width == minWidth || (columnToHold && columnToHold.id == c.id)) continue; + total -= 1; + c.width -= 1; + workdone = true; + } + } + + // shrink the column being "held" as a last resort + if (total > availWidth && columnToHold && columnToHold.resizable && !columnToHold.hidden) { + while (total > availWidth) { + if (columnToHold.minWidth == columnToHold.width || columnToHold.width == minWidth) break; + total -= 1; + columnToHold.width -= 1; + } + } + + // grow + workdone = true; + while (total < availWidth && workdone) { + workdone = false; + for (var i = 0; i < columns.length && total < availWidth; i++) { + var c = columns[i]; + if (c.hidden || !c.resizable || c.maxWidth == c.width || (columnToHold && columnToHold.id == c.id)) continue; + total += 1; + c.width += 1; + workdone = true; + } + } + + // grow the column being "held" as a last resort + if (total < availWidth && columnToHold && columnToHold.resizable && !columnToHold.hidden) { + while (total < availWidth) { + if (columnToHold.maxWidth == columnToHold.width) break; + total += 1; + columnToHold.width += 1; + } + } + + for (var i=0; i<columns.length; i++) { + updateColumnWidth(i, columns[i].width); + } + + resizeCanvas(); } - - selectedRows = rows.concat(); - selectedRowsLookup = lookup; - } - - function setOptions(args) { - if (currentEditor && !commitCurrentEdit()) - return; - makeSelectedCellNormal(); + function updateColumnWidth(index,width) { + columns[index].width = width; + $divHeaders.find(".slick-header-column[id=" + columns[index].id + "]").css("width",width - headerColumnWidthDiff); + $.rule("." + uid + " .grid-canvas .c" + index, "style[lib=slickgrid]").css("width", (columns[index].width - cellWidthDiff) + "px"); + } - if (options.enableAddRow != args.enableAddRow) - removeRow(data.length); + function setColumnVisibility(column,visible) { + var index = columnsById[column.id]; + columns[index].hidden = !visible; + resizeCanvas(); + var header = $divHeaders.find("[id=" + columns[index].id + "]"); + header.css("display", visible?"block":"none"); + $.rule("." + uid + " .grid-canvas .c" + index, "style[lib=slickgrid]").css("display", visible?"block":"none"); - options = $.extend(options,args); - - render(); - } + if (options.forceFitColumns) + autosizeColumns(columns[index]); + } - function setData(newData,scrollToTop) - { - removeAllRows(); - data = newData; - if (scrollToTop) - $divMainScroller.scrollTop(0); - } + function getSelectedRows() { + return selectedRows.concat(); + } - ////////////////////////////////////////////////////////////////////////////////////////////// - // Rendering / Scrolling - - function defaultFormatter(row, cell, value, columnDef, dataContext) { - return (value == null || value == undefined) ? "" : value; - } - - function appendRowHtml(stringArray,row) { - var d = data[row]; - var dataLoading = row < data.length && !d; - var css = "r" + (dataLoading ? " loading" : "") + (selectedRowsLookup[row] ? " selected" : ""); + function setSelectedRows(rows) { + if (Slick.GlobalEditorLock.isEditing() && !Slick.GlobalEditorLock.hasLock(self)) + throw "Grid : setSelectedRows : cannot set selected rows when somebody else has an edit lock"; + + var lookup = {}; + for (var i=0; i<rows.length; i++) + lookup[rows[i]] = true; + + // unselect old rows + for (var i=0; i<selectedRows.length; i++) { + var row = selectedRows[i]; + if (rowsCache[row] && !lookup[row]) + $(rowsCache[row]).removeClass("selected"); + } + + // select new ones + for (var i=0; i<rows.length; i++) { + var row = rows[i]; + if (rowsCache[row] && !selectedRowsLookup[row]) + $(rowsCache[row]).addClass("selected"); + } + + selectedRows = rows.concat(); + selectedRowsLookup = lookup; + } + + function setOptions(args) { + if (currentEditor && !commitCurrentEdit()) + return; + + makeSelectedCellNormal(); + + if (options.enableAddRow != args.enableAddRow) + removeRow(data.length); + + options = $.extend(options,args); + + render(); + } - stringArray.push("<div class='" + css + "' row='" + row + "' style='top:" + (options.rowHeight*row) + "px'>"); + function setData(newData,scrollToTop) + { + removeAllRows(); + data = newData; + if (scrollToTop) + $divMainScroller.scrollTop(0); + } - for (var i=0, cols=columns.length; i<cols; i++) { - var m = columns[i]; - - stringArray.push("<div " + (m.unselectable ? "" : "hideFocus tabIndex=0 ") + "class='c c" + i + (m.cssClass ? " " + m.cssClass : "") + "' cell=" + i + ">"); - - // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet) - if (d && row < data.length) - stringArray.push(m.formatter(row, i, d[m.field], m, d)); - - stringArray.push("</div>"); + ////////////////////////////////////////////////////////////////////////////////////////////// + // Rendering / Scrolling + + function defaultFormatter(row, cell, value, columnDef, dataContext) { + return (value == null || value == undefined) ? "" : value; } - - stringArray.push("</div>"); - } - function getRowHtml(row) { - var html = []; - appendRowHtml(html,row); - return html.join(""); - } + function appendRowHtml(stringArray,row) { + var d = data[row]; + var dataLoading = row < data.length && !d; + var css = "r" + (dataLoading ? " loading" : "") + (selectedRowsLookup[row] ? " selected" : ""); + + stringArray.push("<div class='" + css + "' row='" + row + "' style='top:" + (options.rowHeight*row) + "px'>"); + + for (var i=0, cols=columns.length; i<cols; i++) { + var m = columns[i]; + + stringArray.push("<div " + (m.unselectable ? "" : "hideFocus tabIndex=0 ") + "class='c c" + i + (m.cssClass ? " " + m.cssClass : "") + "' cell=" + i + ">"); - function cleanupRows(visibleFrom,visibleTo) { - var rowsBefore = renderedRows; - var parentNode = $divMain[0]; - for (var i in rowsCache) { - if ((i < visibleFrom || i > visibleTo) && i != currentRow) { - parentNode.removeChild(rowsCache[i]); - delete rowsCache[i]; - delete postProcessedRows[i]; - renderedRows--; - counter_rows_removed++; + // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet) + if (d && row < data.length) + stringArray.push(m.formatter(row, i, d[m.field], m, d)); + + stringArray.push("</div>"); } + + stringArray.push("</div>"); } - } - - function removeAllRows() { - $divMain[0].innerHTML = ""; - rowsCache= {}; - postProcessedRows = {}; - counter_rows_removed += renderedRows; - renderedRows = 0; - } - - function removeRow(row) { - var node = rowsCache[row]; - if (!node) return; - - if (currentEditor && currentRow == row) - throw "Grid : removeRow : Cannot remove a row that is currently in edit mode"; - // if we're removing rows, we're probably not scrolling - scrollDir = 0; + function getRowHtml(row) { + var html = []; + appendRowHtml(html,row); + return html.join(""); + } - node.parentNode.removeChild(node); - node = null; - delete rowsCache[row]; - delete postProcessedRows[row]; - renderedRows--; - counter_rows_removed++; - } - - function removeRows(rows) { - if (!rows || !rows.length) return; - scrollDir = 0; - var nodes = []; - for (var 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"; - - if (rowsCache[rows[i]]) - nodes.push(rows[i]); + function cleanupRows(visibleFrom,visibleTo) { + var rowsBefore = renderedRows; + var parentNode = $divMain[0]; + for (var i in rowsCache) { + if ((i < visibleFrom || i > visibleTo) && i != currentRow) { + parentNode.removeChild(rowsCache[i]); + delete rowsCache[i]; + delete postProcessedRows[i]; + renderedRows--; + counter_rows_removed++; + } + } } - if (renderedRows > 10 && nodes.length == renderedRows) { + function removeAllRows() { $divMain[0].innerHTML = ""; rowsCache= {}; postProcessedRows = {}; counter_rows_removed += renderedRows; - renderedRows = 0; - } else { - for (var i=0, nl=nodes.length; i<nl; i++) { - var node = rowsCache[nodes[i]]; - node.parentNode.removeChild(node); - delete rowsCache[nodes[i]]; - delete postProcessedRows[nodes[i]]; - renderedRows--; - counter_rows_removed++; - } - } - } + renderedRows = 0; + } - function updateCell(row,cell) { - if (!rowsCache[row]) return; - var $cell = $(rowsCache[row]).find(".c[cell=" + cell + "]"); - if ($cell.length == 0) return; - - var m = columns[cell], d = data[row]; - if (currentEditor && currentRow == row && currentCell == cell) - currentEditor.setValue(d[m.field]); - else { - $cell[0].innerHTML = d ? m.formatter(row, cell, d[m.field], m, d) : ""; - invalidatePostProcessingResults(row); + function removeRow(row) { + removeRows([row]); } - } - - function updateRow(row) { - if (!rowsCache[row]) return; - // todo: perf: iterate over direct children? - $(rowsCache[row]).find(".c").each(function(i) { - var m = columns[i]; - if (row == currentRow && i == currentCell && currentEditor) - currentEditor.setValue(data[currentRow][m.field]); - else if (data[row]) - this.innerHTML = m.formatter(row, i, data[row][m.field], m, data[row]); - else - this.innerHTML = ""; - }); - - invalidatePostProcessingResults(row); - } - - function resizeCanvas() { - viewportW = $divMainScroller.innerWidth(); - viewportH = $divMainScroller.innerHeight(); - BUFFER = numVisibleRows = Math.ceil(viewportH / options.rowHeight); - CAPACITY = Math.max(50, numVisibleRows + 2*BUFFER); - - var totalWidth = 0; - for (var i=0; i<columns.length; i++) { - if (columns[i].hidden != true) - totalWidth += columns[i].width; + function removeRows(rows) { + if (!rows || !rows.length) return; + scrollDir = 0; + var nodes = []; + for (var 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"; + + if (rowsCache[rows[i]]) + nodes.push(rows[i]); + } + + if (renderedRows > 10 && nodes.length == renderedRows) { + removeAllRows(); + } else { + for (var i=0, nl=nodes.length; i<nl; i++) { + var node = rowsCache[nodes[i]]; + node.parentNode.removeChild(node); + delete rowsCache[nodes[i]]; + delete postProcessedRows[nodes[i]]; + renderedRows--; + counter_rows_removed++; + } + } } - $divMain.width(totalWidth); - - var newHeight = Math.max(options.rowHeight * (data.length - 1 + (options.leaveSpaceForNewRows?numVisibleRows-1:0)), viewportH - $.getScrollbarWidth()); - $divMainScroller.height( $container.innerHeight() - $divHeadersScroller.outerHeight() ); - // browsers sometimes do not adjust scrollTop/scrollHeight when the height of contained objects changes - if ($divMainScroller.scrollTop() > newHeight - $divMainScroller.height() + $.getScrollbarWidth()) { - $divMainScroller.scrollTop(newHeight - $divMainScroller.height() + $.getScrollbarWidth()); + function updateCell(row,cell) { + if (!rowsCache[row]) return; + var $cell = $(rowsCache[row]).find(".c[cell=" + cell + "]"); + if ($cell.length == 0) return; + + var m = columns[cell], d = data[row]; + if (currentEditor && currentRow == row && currentCell == cell) + currentEditor.setValue(d[m.field]); + else { + $cell[0].innerHTML = d ? m.formatter(row, cell, d[m.field], m, d) : ""; + invalidatePostProcessingResults(row); + } } - $divMain.height(newHeight); - - render(); - } - function updateRowCount() { - // 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 parentNode = $divMain[0]; - var l = options.enableAddRow ? data.length : data.length - 1; - for (var i in rowsCache) { - if (i >= l) { - parentNode.removeChild(rowsCache[i]); - delete rowsCache[i]; - delete postProcessedRows[i]; - renderedRows--; - counter_rows_removed++; - } + function updateRow(row) { + if (!rowsCache[row]) return; + + // todo: perf: iterate over direct children? + $(rowsCache[row]).find(".c").each(function(i) { + var m = columns[i]; + if (row == currentRow && i == currentCell && currentEditor) + currentEditor.setValue(data[currentRow][m.field]); + else if (data[row]) + this.innerHTML = m.formatter(row, i, data[row][m.field], m, data[row]); + else + this.innerHTML = ""; + }); + + invalidatePostProcessingResults(row); } - - var newHeight = Math.max(options.rowHeight * (data.length - 1 + (options.leaveSpaceForNewRows?numVisibleRows-1:0)), viewportH - $.getScrollbarWidth()); - - // browsers sometimes do not adjust scrollTop/scrollHeight when the height of contained objects changes - if ($divMainScroller.scrollTop() > newHeight - $divMainScroller.height() + $.getScrollbarWidth()) - $divMainScroller.scrollTop(newHeight - $divMainScroller.height() + $.getScrollbarWidth()); - $divMain.height(newHeight); - } - function getViewport() - { - return { - top: Math.floor(currentScrollTop / options.rowHeight), - bottom: Math.floor((currentScrollTop + viewportH) / options.rowHeight) - }; - } + function resizeCanvas() { + viewportW = $divMainScroller.innerWidth(); + viewportH = $divMainScroller.innerHeight(); + BUFFER = numVisibleRows = Math.ceil(viewportH / options.rowHeight); + CAPACITY = Math.max(50, numVisibleRows + 2*BUFFER); - function renderRows(from,to) { - var parentNode = $divMain[0]; - var rowsBefore = renderedRows; - var stringArray = [], rows =[]; - var _start = new Date(); + var totalWidth = 0; + for (var i=0; i<columns.length; i++) { + if (columns[i].hidden != true) + totalWidth += columns[i].width; + } + $divMain.width(totalWidth); + + var newHeight = Math.max(options.rowHeight * (data.length - 1 + (options.leaveSpaceForNewRows?numVisibleRows-1:0)), viewportH - $.getScrollbarWidth()); + $divMainScroller.height( $container.innerHeight() - $divHeadersScroller.outerHeight() ); + + // browsers sometimes do not adjust scrollTop/scrollHeight when the height of contained objects changes + if ($divMainScroller.scrollTop() > newHeight - $divMainScroller.height() + $.getScrollbarWidth()) { + $divMainScroller.scrollTop(newHeight - $divMainScroller.height() + $.getScrollbarWidth()); + } + $divMain.height(newHeight); + + render(); + } - for (var i = from; i <= to; i++) { - if (rowsCache[i]) continue; - renderedRows++; - rows.push(i); - appendRowHtml(stringArray,i); - counter_rows_rendered++; + function updateRowCount() { + // 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 parentNode = $divMain[0]; + var l = options.enableAddRow ? data.length : data.length - 1; + for (var i in rowsCache) { + if (i >= l) { + parentNode.removeChild(rowsCache[i]); + delete rowsCache[i]; + delete postProcessedRows[i]; + renderedRows--; + counter_rows_removed++; + } + } + + var newHeight = Math.max(options.rowHeight * (data.length - 1 + (options.leaveSpaceForNewRows?numVisibleRows-1:0)), viewportH - $.getScrollbarWidth()); + + // browsers sometimes do not adjust scrollTop/scrollHeight when the height of contained objects changes + if ($divMainScroller.scrollTop() > newHeight - $divMainScroller.height() + $.getScrollbarWidth()) + $divMainScroller.scrollTop(newHeight - $divMainScroller.height() + $.getScrollbarWidth()); + $divMain.height(newHeight); } - var x = document.createElement("div"); - x.innerHTML = stringArray.join(""); - - for (var i = 0, l = x.childNodes.length; i < l; i++) - rowsCache[rows[i]] = parentNode.appendChild(x.firstChild); + function getViewport() + { + return { + top: Math.floor(currentScrollTop / options.rowHeight), + bottom: Math.floor((currentScrollTop + viewportH) / options.rowHeight) + }; + } - if (renderedRows - rowsBefore > MIN_BUFFER) - avgRowRenderTime = (new Date() - _start) / (renderedRows - rowsBefore); - } - - function startPostProcessing() { - window.clearTimeout(h_postrender); - h_postrender = window.setTimeout(processPostRenderChunk, POSTPROCESSING_DELAY); - } - - function invalidatePostProcessingResults(row) { - delete postProcessedRows[row]; - rowsToPostProcess.unshift(row); - startPostProcessing(); - } + function renderRows(from,to) { + var parentNode = $divMain[0]; + var rowsBefore = renderedRows; + var stringArray = [], rows =[]; + var _start = new Date(); + + for (var i = from; i <= to; i++) { + if (rowsCache[i]) continue; + renderedRows++; + rows.push(i); + appendRowHtml(stringArray,i); + counter_rows_rendered++; + } + + var x = document.createElement("div"); + x.innerHTML = stringArray.join(""); - function render() { - var vp = getViewport(); - var from = Math.max(0, vp.top - (scrollDir >= 0 ? MIN_BUFFER : BUFFER)); - var to = Math.min(options.enableAddRow ? data.length : data.length - 1, vp.bottom + (scrollDir > 0 ? BUFFER : MIN_BUFFER)); + for (var i = 0, l = x.childNodes.length; i < l; i++) + rowsCache[rows[i]] = parentNode.appendChild(x.firstChild); + + if (renderedRows - rowsBefore > MIN_BUFFER) + avgRowRenderTime = (new Date() - _start) / (renderedRows - rowsBefore); + } - if (renderedRows > 10 && Math.abs(lastRenderedScrollTop - currentScrollTop) > options.rowHeight*CAPACITY) - removeAllRows(); - else - cleanupRows(from,to); - - renderRows(from,to); - - rowsToPostProcess = []; - from = Math.max(0,vp.top-MIN_BUFFER); - to = Math.min(options.enableAddRow ? data.length : data.length - 1, vp.bottom+MIN_BUFFER); - for (var i=from; i<=to; i++) { - rowsToPostProcess.push(i); + function startPostProcessing() { + clearTimeout(h_postrender); + h_postrender = setTimeout(processPostRenderChunk, POSTPROCESSING_DELAY); } - startPostProcessing(); - - lastRenderedScrollTop = currentScrollTop; - h_render = null; - } - - function handleScroll() { - currentScrollTop = $divMainScroller[0].scrollTop; - var scrollDistance = Math.abs(lastRenderedScrollTop - currentScrollTop); - var scrollLeft = $divMainScroller[0].scrollLeft; - - if (scrollLeft != currentScrollLeft) - $divHeadersScroller[0].scrollLeft = currentScrollLeft = scrollLeft; - - // min scroll distance = 25% of the viewport or MIN_BUFFER rows (whichever is smaller) - if (scrollDistance < Math.min(viewportH/4, MIN_BUFFER*options.rowHeight)) return; - - if (lastRenderedScrollTop == currentScrollTop) - scrollDir = 0; - else if (lastRenderedScrollTop < currentScrollTop) - scrollDir = 1; - else - scrollDir = -1; + function invalidatePostProcessingResults(row) { + delete postProcessedRows[row]; + rowsToPostProcess.unshift(row); + startPostProcessing(); + } - if (h_render) - window.clearTimeout(h_render); - - if (scrollDistance < numVisibleRows*options.rowHeight) - render(); - else - h_render = window.setTimeout(render, 50); + function render() { + var vp = getViewport(); + var from = Math.max(0, vp.top - (scrollDir >= 0 ? MIN_BUFFER : BUFFER)); + var to = Math.min(options.enableAddRow ? data.length : data.length - 1, vp.bottom + (scrollDir > 0 ? BUFFER : MIN_BUFFER)); - if (self.onViewportChanged) - self.onViewportChanged(); - } - - function processPostRenderChunk() { - if (rowsToPostProcess.length == 0) return; - while (rowsToPostProcess.length > 0) { - var row = rowsToPostProcess.shift(); - if (postProcessedRows[row] || row>=data.length) continue; - var node = rowsCache[row]; - if (!node) continue; + if (renderedRows > 10 && Math.abs(lastRenderedScrollTop - currentScrollTop) > options.rowHeight*CAPACITY) + removeAllRows(); + else + cleanupRows(from,to); - if (self.onPostProcessRowNode) - self.onPostProcessRowNode(node, row, data[row]); - startPostProcessing(); - postProcessedRows[row] = true; - return; - } - } - - - ////////////////////////////////////////////////////////////////////////////////////////////// - // Interactivity - - function handleKeyDown(e) { - // do we have any registered handlers? - if (self.onKeyDown && data[currentRow]) { - // grid must not be in edit mode - if (!currentEditor) { - // handler will return true if the event was handled - if (self.onKeyDown(e, currentRow, currentCell)) { - e.stopPropagation(); - e.preventDefault(); - return false; - } + renderRows(from,to); + + rowsToPostProcess = []; + from = Math.max(0,vp.top-MIN_BUFFER); + to = Math.min(options.enableAddRow ? data.length : data.length - 1, vp.bottom+MIN_BUFFER); + for (var i=from; i<=to; i++) { + rowsToPostProcess.push(i); } + + startPostProcessing(); + + lastRenderedScrollTop = currentScrollTop; + h_render = null; } - - switch (e.which) { - case 27: // esc - if (GlobalEditorLock.isEditing() && GlobalEditorLock.hasLock(self)) - cancelCurrentEdit(self); - - if (currentCellNode) - currentCellNode.focus(); - - break; + + function handleScroll() { + currentScrollTop = $divMainScroller[0].scrollTop; + var scrollDistance = Math.abs(lastRenderedScrollTop - currentScrollTop); + var scrollLeft = $divMainScroller[0].scrollLeft; - case 9: // tab - gotoDir(0, (e.shiftKey) ? -1 : 1, true); - break; - - case 37: // left - gotoDir(0,-1); - break; - - case 39: // right - gotoDir(0,1); - break; - - case 38: // up - gotoDir(-1,0); - break; + if (scrollLeft != currentScrollLeft) + $divHeadersScroller[0].scrollLeft = currentScrollLeft = scrollLeft; + + // min scroll distance = 25% of the viewport or MIN_BUFFER rows (whichever is smaller) + if (scrollDistance < Math.min(viewportH/4, MIN_BUFFER*options.rowHeight)) return; + + if (lastRenderedScrollTop == currentScrollTop) + scrollDir = 0; + else if (lastRenderedScrollTop < currentScrollTop) + scrollDir = 1; + else + scrollDir = -1; + + if (h_render) + clearTimeout(h_render); + + if (scrollDistance < numVisibleRows*options.rowHeight) + render(); + else + h_render = setTimeout(render, 50); - case 40: // down - case 13: // enter - gotoDir(1,0); - break; - - default: - // exit without cancelling the event - return; + if (self.onViewportChanged) + self.onViewportChanged(); } - - e.stopPropagation(); - e.preventDefault(); - return false; - } - - function handleClick(e) { - var $cell = $(e.target).closest(".c"); - if ($cell.length == 0) return; - - // are we editing this cell? - if (currentCellNode == $cell[0] && currentEditor != null) return; - - var row = parseInt($cell.parent().attr("row")); - var cell = parseInt($cell.attr("cell")); - var validated = null; - // do we have any registered handlers? - if (data[row] && self.onClick) { - // grid must not be in edit mode - if (!currentEditor || (validated = commitCurrentEdit())) { - // handler will return true if the event was handled - if (self.onClick(e, row, cell)) { - e.stopPropagation(); - e.preventDefault(); - return false; - } + function processPostRenderChunk() { + if (rowsToPostProcess.length == 0) return; + while (rowsToPostProcess.length > 0) { + var row = rowsToPostProcess.shift(); + if (postProcessedRows[row] || row>=data.length) continue; + var node = rowsCache[row]; + if (!node) continue; + + if (self.onPostProcessRowNode) + self.onPostProcessRowNode(node, row, data[row]); + startPostProcessing(); + postProcessedRows[row] = true; + return; } } - - if (options.enableCellNavigation && !columns[cell].unselectable) { - // commit current edit before proceeding - if (validated == true || (validated == null && commitCurrentEdit())) - setSelectedCellAndRow($cell[0]); - } - } - function handleContextMenu(e) { - var $cell = $(e.target).closest(".c"); - if ($cell.length == 0) return; - - // are we editing this cell? - if (currentCellNode == $cell[0] && currentEditor != null) return; - - var row = parseInt($cell.parent().attr("row")); - var cell = parseInt($cell.attr("cell")); - var validated = null; - // do we have any registered handlers? - if (data[row] && self.onContextMenu) { - // grid must not be in edit mode - if (!currentEditor || (validated = commitCurrentEdit())) { - // handler will return true if the event was handled - if (self.onContextMenu(e, row, cell)) { - e.stopPropagation(); - e.preventDefault(); - return false; + ////////////////////////////////////////////////////////////////////////////////////////////// + // Interactivity + + function handleKeyDown(e) { + // do we have any registered handlers? + if (self.onKeyDown && data[currentRow]) { + // grid must not be in edit mode + if (!currentEditor) { + // handler will return true if the event was handled + if (self.onKeyDown(e, currentRow, currentCell)) { + e.stopPropagation(); + e.preventDefault(); + return false; + } } } - } - } - function handleDblClick(e) { - var $cell = $(e.target).closest(".c"); - if ($cell.length == 0) return; - - // are we editing this cell? - if (currentCellNode == $cell[0] && currentEditor != null) return; + switch (e.which) { + case 27: // esc + if (Slick.GlobalEditorLock.isEditing() && Slick.GlobalEditorLock.hasLock(self)) + cancelCurrentEdit(self); + + if (currentCellNode) + currentCellNode.focus(); + + break; - var row = parseInt($cell.parent().attr("row")); - var cell = parseInt($cell.attr("cell")); - var validated = null; - - // do we have any registered handlers? - if (data[row] && self.onDblClick) { - // grid must not be in edit mode - if (!currentEditor || (validated = commitCurrentEdit())) { - // handler will return true if the event was handled - if (self.onDblClick(e, row, cell)) { - e.stopPropagation(); - e.preventDefault(); - return false; + case 9: // tab + gotoDir(0, (e.shiftKey) ? -1 : 1, true); + break; + + case 37: // left + gotoDir(0,-1); + break; + + case 39: // right + gotoDir(0,1); + break; + + case 38: // up + gotoDir(-1,0); + break; + + case 40: // down + case 13: // enter + gotoDir(1,0); + break; + + default: + // exit without cancelling the event + return; + } + + e.stopPropagation(); + e.preventDefault(); + return false; + } + + function handleClick(e) { + var $cell = $(e.target).closest(".c"); + if ($cell.length == 0) return; + + // are we editing this cell? + if (currentCellNode == $cell[0] && currentEditor != null) return; + + var row = parseInt($cell.parent().attr("row")); + var cell = parseInt($cell.attr("cell")); + var validated = null; + + // do we have any registered handlers? + if (data[row] && self.onClick) { + // grid must not be in edit mode + if (!currentEditor || (validated = commitCurrentEdit())) { + // handler will return true if the event was handled + if (self.onClick(e, row, cell)) { + e.stopPropagation(); + e.preventDefault(); + return false; + } } } - } - - if (options.editOnDoubleClick) - makeSelectedCellEditable(); - } - function handleHeaderContextMenu(e) { - if (self.onHeaderContextMenu && (!currentEditor || (validated = commitCurrentEdit()))) { - e.preventDefault(); - // TODO: figure out which column was acted on and pass it as a param to the handler - self.onHeaderContextMenu(e); + if (options.enableCellNavigation && !columns[cell].unselectable) { + // commit current edit before proceeding + if (validated == true || (validated == null && commitCurrentEdit())) + setSelectedCellAndRow($cell[0]); + } } - } - - function getCellFromPoint(x,y) { - var row = Math.floor(y/options.rowHeight); - var cell = 0; - var w = 0; - for (var i=0; i<columns.length && w<y; i++) { - w += columns[i].width; - cell++; + function handleContextMenu(e) { + var $cell = $(e.target).closest(".c"); + if ($cell.length == 0) return; + + // are we editing this cell? + if (currentCellNode == $cell[0] && currentEditor != null) return; + + var row = parseInt($cell.parent().attr("row")); + var cell = parseInt($cell.attr("cell")); + var validated = null; + + // do we have any registered handlers? + if (data[row] && self.onContextMenu) { + // grid must not be in edit mode + if (!currentEditor || (validated = commitCurrentEdit())) { + // handler will return true if the event was handled + if (self.onContextMenu(e, row, cell)) { + e.stopPropagation(); + e.preventDefault(); + return false; + } + } + } } - return {row:row,cell:cell-1}; - } - - - ////////////////////////////////////////////////////////////////////////////////////////////// - // Cell switching - - function setSelectedCell(newCell,async) { - if (currentCellNode != null) { - makeSelectedCellNormal(); - $(currentCellNode).removeClass("selected"); + function handleDblClick(e) { + var $cell = $(e.target).closest(".c"); + if ($cell.length == 0) return; + + // are we editing this cell? + if (currentCellNode == $cell[0] && currentEditor != null) return; + + var row = parseInt($cell.parent().attr("row")); + var cell = parseInt($cell.attr("cell")); + var validated = null; + + // do we have any registered handlers? + if (data[row] && self.onDblClick) { + // grid must not be in edit mode + if (!currentEditor || (validated = commitCurrentEdit())) { + // handler will return true if the event was handled + if (self.onDblClick(e, row, cell)) { + e.stopPropagation(); + e.preventDefault(); + return false; + } + } + } + + if (options.editOnDoubleClick) + makeSelectedCellEditable(); } - currentCellNode = newCell; - - if (currentCellNode != null) { - currentRow = parseInt($(currentCellNode).parent().attr("row")); - currentCell = parseInt($(currentCellNode).attr("cell")); + function handleHeaderContextMenu(e) { + if (self.onHeaderContextMenu && (!currentEditor || (validated = commitCurrentEdit()))) { + e.preventDefault(); + // TODO: figure out which column was acted on and pass it as a param to the handler + self.onHeaderContextMenu(e); + } + } + + function getCellFromPoint(x,y) { + var row = Math.floor(y/options.rowHeight); + var cell = 0; - $(currentCellNode).addClass("selected"); + var w = 0; + for (var i=0; i<columns.length && w<y; i++) { + w += columns[i].width; + cell++; + } - scrollSelectedCellIntoView(); + return {row:row,cell:cell-1}; + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Cell switching + + function setSelectedCell(newCell,async) { + if (currentCellNode != null) { + makeSelectedCellNormal(); + $(currentCellNode).removeClass("selected"); + } - if (options.editable && !options.editOnDoubleClick && (data[currentRow] || currentRow == data.length)) { - window.clearTimeout(h_editorLoader); + currentCellNode = newCell; + + if (currentCellNode != null) { + currentRow = parseInt($(currentCellNode).parent().attr("row")); + currentCell = parseInt($(currentCellNode).attr("cell")); + + $(currentCellNode).addClass("selected"); + + scrollSelectedCellIntoView(); - if (async) - h_editorLoader = window.setTimeout(makeSelectedCellEditable, 100); - else - makeSelectedCellEditable(); + if (options.editable && !options.editOnDoubleClick && (data[currentRow] || currentRow == data.length)) { + clearTimeout(h_editorLoader); + + if (async) + h_editorLoader = setTimeout(makeSelectedCellEditable, EDITOR_LOAD_DELAY); + else + makeSelectedCellEditable(); + } + } + else { + currentRow = null; + currentCell = null; } } - else { - currentRow = null; - currentCell = null; + + function setSelectedCellAndRow(newCell,async) { + setSelectedCell(newCell,async); + + if (newCell) + setSelectedRows([currentRow]); + else + setSelectedRows([]); + + if (self.onSelectedRowsChanged) + self.onSelectedRowsChanged(); } - } - - function setSelectedCellAndRow(newCell,async) { - setSelectedCell(newCell,async); - if (newCell) - setSelectedRows([currentRow]); - else - setSelectedRows([]); + function clearTextSelection() { + if (document.selection && document.selection.empty) + document.selection.empty(); + else if (window.getSelection) { + var sel = window.getSelection(); + if (sel && sel.removeAllRanges) + sel.removeAllRanges(); + } + } + + function isCellPotentiallyEditable(row,cell) { + // is the data for this row loaded? + if (row < data.length && !data[row]) + return false; - if (self.onSelectedRowsChanged) - self.onSelectedRowsChanged(); - } + // are we in the Add New row? can we create new from this cell? + if (columns[cell].cannotTriggerInsert && row >= data.length) + return false; + + // does this cell have an editor? + if (!columns[cell].editor) + return false; + + return true; + } + + function makeSelectedCellNormal() { + if (!currentEditor) return; + + currentEditor.destroy(); + $(currentCellNode).removeClass("editable invalid"); + + if (data[currentRow]) { + currentCellNode.innerHTML = columns[currentCell].formatter(currentRow, currentCell, data[currentRow][columns[currentCell].field], columns[currentCell], data[currentRow]); + invalidatePostProcessingResults(currentRow); + } + + currentEditor = null; + + // 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 + if ($.browser.msie) clearTextSelection(); - function clearTextSelection() { - if (document.selection && document.selection.empty) - document.selection.empty(); - else if (window.getSelection) { - var sel = window.getSelection(); - if (sel && sel.removeAllRanges) - sel.removeAllRanges(); + Slick.GlobalEditorLock.leaveEditMode(self); } - } - - function isCellPotentiallyEditable(row,cell) { - // is the data for this row loaded? - if (row < data.length && !data[row]) - return false; - - // are we in the Add New row? can we create new from this cell? - if (columns[cell].cannotTriggerInsert && row >= data.length) - return false; + + function makeSelectedCellEditable() { + if (!currentCellNode) return; + if (!options.editable) + throw "Grid : makeSelectedCellEditable : should never get called when options.editable is false"; - // does this cell have an editor? - if (!columns[cell].editor) - return false; + // cancel pending async call if there is one + clearTimeout(h_editorLoader); - return true; - } - - function makeSelectedCellNormal() { - if (!currentEditor) return; - - currentEditor.destroy(); - $(currentCellNode).removeClass("editable invalid"); + if (!isCellPotentiallyEditable(currentRow,currentCell)) + return; + + Slick.GlobalEditorLock.enterEditMode(self); + + $(currentCellNode).addClass("editable"); + + var value = null; - if (data[currentRow]) { - currentCellNode.innerHTML = columns[currentCell].formatter(currentRow, currentCell, data[currentRow][columns[currentCell].field], columns[currentCell], data[currentRow]); - invalidatePostProcessingResults(currentRow); + // if there is a corresponding row + if (data[currentRow]) + value = data[currentRow][columns[currentCell].field]; + + currentCellNode.innerHTML = ""; + + currentEditor = new columns[currentCell].editor($(currentCellNode), columns[currentCell], value, data[currentRow]); } - - currentEditor = null; - - // 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 - if ($.browser.msie) clearTextSelection(); - - GlobalEditorLock.leaveEditMode(self); - } - - function makeSelectedCellEditable() { - if (!currentCellNode) return; - if (!options.editable) - throw "Grid : makeSelectedCellEditable : should never get called when options.editable is false"; - - // cancel pending async call if there is one - window.clearTimeout(h_editorLoader); - - if (!isCellPotentiallyEditable(currentRow,currentCell)) - return; - - GlobalEditorLock.enterEditMode(self); - - $(currentCellNode).addClass("editable"); - - var value = null; - // if there is a corresponding row - if (data[currentRow]) - value = data[currentRow][columns[currentCell].field]; - - currentCellNode.innerHTML = ""; - - currentEditor = new columns[currentCell].editor($(currentCellNode), columns[currentCell], value, data[currentRow]); - } - - function scrollSelectedCellIntoView() { - if (!currentCellNode) return; - var scrollTop = $divMainScroller[0].scrollTop; - - // need to page down? - if ((currentRow + 2) * options.rowHeight > scrollTop + viewportH) { - $divMainScroller[0].scrollTop = (currentRow ) * options.rowHeight; - handleScroll(); + function scrollSelectedCellIntoView() { + if (!currentCellNode) return; + var scrollTop = $divMainScroller[0].scrollTop; + + // need to page down? + if ((currentRow + 2) * options.rowHeight > scrollTop + viewportH) { + $divMainScroller[0].scrollTop = (currentRow ) * options.rowHeight; + handleScroll(); + } + // or page up? + else if (currentRow * options.rowHeight < scrollTop) { + $divMainScroller[0].scrollTop = (currentRow + 2) * options.rowHeight - viewportH; + handleScroll(); + } } - // or page up? - else if (currentRow * options.rowHeight < scrollTop) { - $divMainScroller[0].scrollTop = (currentRow + 2) * options.rowHeight - viewportH; - handleScroll(); - } - } - - function gotoDir(dy, dx, rollover) { - if (!currentCellNode) return; - if (!options.enableCellNavigation) return; - if (!GlobalEditorLock.commitCurrentEdit()) return; - - var nextRow = rowsCache[currentRow + dy]; - var nextCell = nextRow ? $(nextRow).find(".c[cell=" + (currentCell + dx) + "][tabIndex=0]") : null; - - if (rollover && dy == 0 && !(nextRow && nextCell && nextCell.length)) { - if (!nextCell || !nextCell.length) { - if (dx > 0) { - nextRow = rowsCache[currentRow + dy + 1]; - nextCell = nextRow ? $(nextRow).find(".c[cell][tabIndex=0]:first") : null; - } - else { - nextRow = rowsCache[currentRow + dy - 1]; - nextCell = nextRow ? $(nextRow).find(".c[cell][tabIndex=0]:last") : null; + + function gotoDir(dy, dx, rollover) { + if (!currentCellNode) return; + if (!options.enableCellNavigation) return; + if (!Slick.GlobalEditorLock.commitCurrentEdit()) return; + + var nextRow = rowsCache[currentRow + dy]; + var nextCell = nextRow ? $(nextRow).find(".c[cell=" + (currentCell + dx) + "][tabIndex=0]") : null; + + if (rollover && dy == 0 && !(nextRow && nextCell && nextCell.length)) { + if (!nextCell || !nextCell.length) { + if (dx > 0) { + nextRow = rowsCache[currentRow + dy + 1]; + nextCell = nextRow ? $(nextRow).find(".c[cell][tabIndex=0]:first") : null; + } + else { + nextRow = rowsCache[currentRow + dy - 1]; + nextCell = nextRow ? $(nextRow).find(".c[cell][tabIndex=0]:last") : null; + } } } + + + if (nextRow && nextCell && nextCell.length) { + setSelectedCellAndRow(nextCell[0],options.asyncEditorLoading); + + // if no editor was created, set the focus back on the cell + if (!currentEditor) + currentCellNode.focus(); + } + else + currentCellNode.focus(); } - - - if (nextRow && nextCell && nextCell.length) { - setSelectedCellAndRow(nextCell[0],options.asyncEditorLoading); + + function gotoCell(row,cell) { + if (row > data.length || row < 0 || cell >= columns.length || cell < 0) return; + if (!options.enableCellNavigation || columns[cell].unselectable) return; + + if (!Slick.GlobalEditorLock.commitCurrentEdit()) return; + + if (!rowsCache[row]) + renderRows(row,row); + + var cell = $(rowsCache[row]).find(".c[cell=" + cell + "][tabIndex=0]")[0]; + + setSelectedCellAndRow(cell); // if no editor was created, set the focus back on the cell if (!currentEditor) currentCellNode.focus(); } - else - currentCellNode.focus(); - } - - function gotoCell(row,cell) { - if (row > data.length || row < 0 || cell >= columns.length || cell < 0) return; - if (!options.enableCellNavigation || columns[cell].unselectable) return; - - if (!GlobalEditorLock.commitCurrentEdit()) return; - - if (!rowsCache[row]) - renderRows(row,row); - - var cell = $(rowsCache[row]).find(".c[cell=" + cell + "][tabIndex=0]")[0]; - - setSelectedCellAndRow(cell); - - // if no editor was created, set the focus back on the cell - if (!currentEditor) - currentCellNode.focus(); - } - - - ////////////////////////////////////////////////////////////////////////////////////////////// - // IEditor implementation for GlobalEditorLock - function commitCurrentEdit() { - if (currentEditor) { - if (currentEditor.isValueChanged()) { - var validationResults = currentEditor.validate(); - - if (validationResults.valid) { - var value = currentEditor.getValue(); + + ////////////////////////////////////////////////////////////////////////////////////////////// + // IEditor implementation for Slick.GlobalEditorLock + + function commitCurrentEdit() { + if (currentEditor) { + if (currentEditor.isValueChanged()) { + var validationResults = currentEditor.validate(); - if (currentRow < data.length) { - if (columns[currentCell].setValueHandler) { - makeSelectedCellNormal(); - columns[currentCell].setValueHandler(value, columns[currentCell], data[currentRow]); - } - else { - data[currentRow][columns[currentCell].field] = value; - makeSelectedCellNormal(); + if (validationResults.valid) { + var value = currentEditor.getValue(); + + if (currentRow < data.length) { + if (columns[currentCell].setValueHandler) { + makeSelectedCellNormal(); + columns[currentCell].setValueHandler(value, columns[currentCell], data[currentRow]); + } + else { + data[currentRow][columns[currentCell].field] = value; + makeSelectedCellNormal(); + } } + else if (self.onAddNewRow) { + makeSelectedCellNormal(); + self.onAddNewRow(columns[currentCell], value); + } + + return true; + } + else { + $(currentCellNode).addClass("invalid"); + $(currentCellNode).stop(true,true).effect("highlight", {color:"red"}, 300); + + if (self.onValidationError) + self.onValidationError(currentCellNode, validationResults, currentRow, currentCell, columns[currentCell]); + + currentEditor.focus(); + return false; } - else if (self.onAddNewRow) { - makeSelectedCellNormal(); - self.onAddNewRow(columns[currentCell], value); - } - - return true; - } - else { - $(currentCellNode).addClass("invalid"); - $(currentCellNode).stop(true,true).effect("highlight", {color:"red"}, 300); - - if (self.onValidationError) - self.onValidationError(currentCellNode, validationResults, currentRow, currentCell, columns[currentCell]); - - currentEditor.focus(); - return false; } + + makeSelectedCellNormal(); } - makeSelectedCellNormal(); + + return true; } + function cancelCurrentEdit() { + makeSelectedCellNormal(); + } - return true; - } - - function cancelCurrentEdit() { - makeSelectedCellNormal(); - } - - - - ////////////////////////////////////////////////////////////////////////////////////////////// - // Debug - - this.debug = function() { - var s = ""; - - s += ("\n" + "counter_rows_rendered: " + counter_rows_rendered); - s += ("\n" + "counter_rows_removed: " + counter_rows_removed); - s += ("\n" + "renderedRows: " + renderedRows); - s += ("\n" + "numVisibleRows: " + numVisibleRows); - s += ("\n" + "CAPACITY: " + CAPACITY); - s += ("\n" + "BUFFER: " + BUFFER); - s += ("\n" + "avgRowRenderTime: " + avgRowRenderTime); - - alert(s); - }; - - this.benchmark_render_200 = function() { - removeAllRows(); - // render 200 rows in the viewport - renderRows(0, 200); - cleanupRows(); - }; - - this.stressTest = function() { - console.time("benchmark-stress"); - - renderRows(0,500); + ////////////////////////////////////////////////////////////////////////////////////////////// + // Debug - cleanupRows(); + this.debug = function() { + var s = ""; + + s += ("\n" + "counter_rows_rendered: " + counter_rows_rendered); + s += ("\n" + "counter_rows_removed: " + counter_rows_removed); + s += ("\n" + "renderedRows: " + renderedRows); + s += ("\n" + "numVisibleRows: " + numVisibleRows); + s += ("\n" + "CAPACITY: " + CAPACITY); + s += ("\n" + "BUFFER: " + BUFFER); + s += ("\n" + "avgRowRenderTime: " + avgRowRenderTime); + + alert(s); + }; - console.timeEnd("benchmark-stress"); + this.benchmark_render_200 = function() { + removeAllRows(); + + // render 200 rows in the viewport + renderRows(0, 200); + + cleanupRows(); + }; - window.setTimeout(self.stressTest, 50); - }; + this.stressTest = function() { + console.time("benchmark-stress"); - this.benchmarkFn = function(fn) { - var s = new Date(); - - var args = new Array(arguments); - args.splice(0,1); + renderRows(0,500); + + cleanupRows(); + + console.timeEnd("benchmark-stress"); + + setTimeout(self.stressTest, 50); + }; - self[fn].call(this,args); + this.benchmarkFn = function(fn) { + var s = new Date(); + + var args = new Array(arguments); + args.splice(0,1); + + self[fn].call(this,args); + + alert("Grid : benchmarkFn : " + fn + " : " + (new Date() - s) + "ms"); + }; - alert("Grid : benchmarkFn : " + fn + " : " + (new Date() - s) + "ms"); - }; - - - - init(); - ////////////////////////////////////////////////////////////////////////////////////////////// - // Public API + init(); + + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Public API + + $.extend(this, { + // Events + "onSort": null, + "onHeaderContextMenu": null, + "onClick": null, + "onContextMenu": null, + "onKeyDown": null, + "onAddNewRow": null, + "onValidationError": null, + "onViewportChanged": null, + "onSelectedRowsChanged": null, + "onColumnsReordered": null, + "onBeforeMoveRows" : null, + "onMoveRows": null, + "onPostProcessRowNode": null, + + // Methods + "setOptions": setOptions, + "setData": setData, + "destroy": destroy, + "getColumnIndex": getColumnIndex, + "setColumnVisibility": setColumnVisibility, + "autosizeColumns": autosizeColumns, + "updateCell": updateCell, + "updateRow": updateRow, + "removeRow": removeRow, + "removeRows": removeRows, + "removeAllRows": removeAllRows, + "render": render, + "getViewport": getViewport, + "resizeCanvas": resizeCanvas, + "updateRowCount": updateRowCount, + "scroll": scroll, // TODO + "getCellFromPoint": getCellFromPoint, + "gotoCell": gotoCell, + "editCurrentCell": makeSelectedCellEditable, + "getSelectedRows": getSelectedRows, + "setSelectedRows": setSelectedRows, + + // IEditor implementation + "commitCurrentEdit": commitCurrentEdit, + "cancelCurrentEdit": cancelCurrentEdit + }); + } - $.extend(this, { - // Events - "onSort": null, - "onHeaderContextMenu": null, - "onClick": null, - "onContextMenu": null, - "onKeyDown": null, - "onAddNewRow": null, - "onValidationError": null, - "onViewportChanged": null, - "onSelectedRowsChanged": null, - "onColumnsReordered": null, - "onBeforeMoveRows" : null, - "onMoveRows": null, - "onPostProcessRowNode": null, - - // Methods - "setOptions": setOptions, - "setData": setData, - "destroy": destroy, - "getColumnIndex": getColumnIndex, - "setColumnVisibility": setColumnVisibility, - "autosizeColumns": autosizeColumns, - "updateCell": updateCell, - "updateRow": updateRow, - "removeRow": removeRow, - "removeRows": removeRows, - "removeAllRows": removeAllRows, - "render": render, - "getViewport": getViewport, - "resizeCanvas": resizeCanvas, - "updateRowCount": updateRowCount, - "scroll": scroll, // TODO - "getCellFromPoint": getCellFromPoint, - "gotoCell": gotoCell, - "editCurrentCell": makeSelectedCellEditable, - "getSelectedRows": getSelectedRows, - "setSelectedRows": setSelectedRows, - - // IEditor implementation - "commitCurrentEdit": commitCurrentEdit, - "cancelCurrentEdit": cancelCurrentEdit - }); -} + // Slick.Grid + $.extend(true, window, { Slick: { Grid: SlickGrid }}); +})();
\ No newline at end of file diff --git a/slick.model.js b/slick.model.js index ec2be94..76ec45c 100644 --- a/slick.model.js +++ b/slick.model.js @@ -18,214 +18,217 @@ function EventHelper() { } - -/*** - * A sample Model implementation. - * Provides a filtered view of the underlying data. - * - * Relies on the data item having an "id" property uniquely identifying it. - */ -function DataView() { - var self = this; - - // private - var items = []; // data by index - var rows = []; // data by row - var idxById = {}; // indexes by id - var rowsById = null; // rows by id; lazy-calculated - var filter = null; // filter function - var updated = null; // updated item ids - var suspend = false; // suspends the recalculation - - var pagesize = 0; - var pagenum = 0; - var totalRows = 0; - - // events - var onRowCountChanged = new EventHelper(); - var onRowsChanged = new EventHelper(); - var onPagingInfoChanged = new EventHelper(); - - - function beginUpdate() { - suspend = true; - } - - function endUpdate() { - suspend = false; - refresh(); - } - - function setItems(data) { - items = data.concat(); - for (var i=0,l=items.length; i<l; i++) { - var id = items[i].id; - if (id == undefined || idxById[id] != undefined) - throw "Each data element must implement a unique 'id' property"; - idxById[id] = i; - } - refresh(); - } - - function setPagingOptions(args) { - if (args.pageSize != undefined) - pagesize = args.pageSize; - - if (args.pageNum != undefined) - pagenum = Math.min(args.pageNum, Math.ceil(totalRows/pagesize)); - - onPagingInfoChanged.notify(getPagingInfo()); - - refresh(); - } - - function getPagingInfo() { - return {pageSize:pagesize, pageNum:pagenum, totalRows:totalRows}; - } - - function sort(comparer) { - items.sort(comparer); - refresh(); - } - - function setFilter(filterFn) { - filter = filterFn; - refresh(); - } - - function getItemByIdx(i) { - return items[i]; - } - - function getIdxById(id) { - return idxById[id]; - } - - // calculate the lookup table on first call - function getRowById(id) { - if (!rowsById) { - rowsById = {}; - for (var i=0, l=rows.length; i<l; ++i) { - rowsById[rows[i].id] = i; - } +(function() { + /*** + * A sample Model implementation. + * Provides a filtered view of the underlying data. + * + * Relies on the data item having an "id" property uniquely identifying it. + */ + function DataView() { + var self = this; + + // private + var items = []; // data by index + var rows = []; // data by row + var idxById = {}; // indexes by id + var rowsById = null; // rows by id; lazy-calculated + var filter = null; // filter function + var updated = null; // updated item ids + var suspend = false; // suspends the recalculation + + var pagesize = 0; + var pagenum = 0; + var totalRows = 0; + + // events + var onRowCountChanged = new EventHelper(); + var onRowsChanged = new EventHelper(); + var onPagingInfoChanged = new EventHelper(); + + + function beginUpdate() { + suspend = true; } - return rowsById[id]; - } - - function getItemById(id) { - return items[idxById[id]]; - } - - function updateItem(id,item) { - items[idxById[id]] = item; - if (!updated) updated = {}; - updated[id] = true; - refresh(); - } - - function insertItem(insertBefore,item) { - items.splice(insertBefore,0,item); - refresh(); - } - - function addItem(item) { - items.push(item); - refresh(); - } - - function deleteItem(id) { - items.splice(idxById[id],1); - refresh(); - } + function endUpdate() { + suspend = false; + refresh(); + } + + function setItems(data) { + items = data.concat(); + for (var i=0,l=items.length; i<l; i++) { + var id = items[i].id; + if (id == undefined || idxById[id] != undefined) + throw "Each data element must implement a unique 'id' property"; + idxById[id] = i; + } + refresh(); + } + + function setPagingOptions(args) { + if (args.pageSize != undefined) + pagesize = args.pageSize; + + if (args.pageNum != undefined) + pagenum = Math.min(args.pageNum, Math.ceil(totalRows/pagesize)); - function recalc(_items,_rows,_filter,_updated) { - var diff = []; - var items=_items, rows=_rows, filter=_filter, updated=_updated; // cache as local vars - - rowsById = null; - - // go over all items remapping them to rows on the fly - // while keeping track of the differences and updating indexes - var rl = rows.length; - var currentRowIndex = 0; - var currentPageIndex = 0; - var item,id; + onPagingInfoChanged.notify(getPagingInfo()); - for (var i = 0, il = items.length; i < il; ++i) { - item = items[i]; - id = item.id; - - if (!filter || filter(item)) { - if (!pagesize || (currentRowIndex >= pagesize * pagenum && currentRowIndex < pagesize * (pagenum + 1))) { - if (currentPageIndex >= rl || id != rows[currentPageIndex].id || (updated && updated[id])) - diff[diff.length] = currentPageIndex; - - rows[currentPageIndex] = item; - currentPageIndex++; - } - - currentRowIndex++; + refresh(); + } + + function getPagingInfo() { + return {pageSize:pagesize, pageNum:pagenum, totalRows:totalRows}; + } + + function sort(comparer) { + items.sort(comparer); + refresh(); + } + + function setFilter(filterFn) { + filter = filterFn; + refresh(); + } + + function getItemByIdx(i) { + return items[i]; + } + + function getIdxById(id) { + return idxById[id]; + } + + // calculate the lookup table on first call + function getRowById(id) { + if (!rowsById) { + rowsById = {}; + for (var i=0, l=rows.length; i<l; ++i) { + rowsById[rows[i].id] = i; + } } + + return rowsById[id]; } - if (rl > currentPageIndex) - rows.splice(currentPageIndex, rl - currentPageIndex); + function getItemById(id) { + return items[idxById[id]]; + } - totalRows = currentRowIndex; + function updateItem(id,item) { + items[idxById[id]] = item; + if (!updated) updated = {}; + updated[id] = true; + refresh(); + } - return diff; - } - - function refresh() { - if (suspend) return; + function insertItem(insertBefore,item) { + items.splice(insertBefore,0,item); + refresh(); + } + + function addItem(item) { + items.push(item); + refresh(); + } - var countBefore = rows.length; - var totalRowsBefore = totalRows; + function deleteItem(id) { + items.splice(idxById[id],1); + refresh(); + } - var diff = recalc(items,rows,filter,updated); // pass as direct refs to avoid closure perf hit + function recalc(_items,_rows,_filter,_updated) { + var diff = []; + var items=_items, rows=_rows, filter=_filter, updated=_updated; // cache as local vars + + rowsById = null; + + // go over all items remapping them to rows on the fly + // while keeping track of the differences and updating indexes + var rl = rows.length; + var currentRowIndex = 0; + var currentPageIndex = 0; + var item,id; - // if the current page is no longer valid, go to last page and recalc - // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized - if (pagesize && totalRows < pagenum*pagesize) { - pagenum = Math.floor(totalRows/pagesize); - diff = recalc(items,rows,filter,updated); + for (var i = 0, il = items.length; i < il; ++i) { + item = items[i]; + id = item.id; + + if (!filter || filter(item)) { + if (!pagesize || (currentRowIndex >= pagesize * pagenum && currentRowIndex < pagesize * (pagenum + 1))) { + if (currentPageIndex >= rl || id != rows[currentPageIndex].id || (updated && updated[id])) + diff[diff.length] = currentPageIndex; + + rows[currentPageIndex] = item; + currentPageIndex++; + } + + currentRowIndex++; + } + } + + if (rl > currentPageIndex) + rows.splice(currentPageIndex, rl - currentPageIndex); + + totalRows = currentRowIndex; + + return diff; } - - updated = null; - if (totalRowsBefore != totalRows) onPagingInfoChanged.notify(getPagingInfo()); - if (countBefore != rows.length) onRowCountChanged.notify({previous:countBefore, current:rows.length}); - if (diff.length > 0 || countBefore != rows.length) onRowsChanged.notify(diff); - } + function refresh() { + if (suspend) return; + + var countBefore = rows.length; + var totalRowsBefore = totalRows; + + var diff = recalc(items,rows,filter,updated); // pass as direct refs to avoid closure perf hit + + // if the current page is no longer valid, go to last page and recalc + // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized + if (pagesize && totalRows < pagenum*pagesize) { + pagenum = Math.floor(totalRows/pagesize); + diff = recalc(items,rows,filter,updated); + } - + updated = null; + + if (totalRowsBefore != totalRows) onPagingInfoChanged.notify(getPagingInfo()); + if (countBefore != rows.length) onRowCountChanged.notify({previous:countBefore, current:rows.length}); + if (diff.length > 0 || countBefore != rows.length) onRowsChanged.notify(diff); + } + - return { - // properties - "rows": rows, // note: neither the array or the data in it should be modified directly - - // methods - "beginUpdate": beginUpdate, - "endUpdate": endUpdate, - "setPagingOptions": setPagingOptions, - "getPagingInfo": getPagingInfo, - "setItems": setItems, - "setFilter": setFilter, - "sort": sort, - "getIdxById": getIdxById, - "getRowById": getRowById, - "getItemById": getItemById, - "getItemByIdx": getItemByIdx, - "refresh": refresh, - "updateItem": updateItem, - "insertItem": insertItem, - "addItem": addItem, - "deleteItem": deleteItem, - // events - "onRowCountChanged": onRowCountChanged, - "onRowsChanged": onRowsChanged, - "onPagingInfoChanged": onPagingInfoChanged - }; -} + return { + // properties + "rows": rows, // note: neither the array or the data in it should be modified directly + + // methods + "beginUpdate": beginUpdate, + "endUpdate": endUpdate, + "setPagingOptions": setPagingOptions, + "getPagingInfo": getPagingInfo, + "setItems": setItems, + "setFilter": setFilter, + "sort": sort, + "getIdxById": getIdxById, + "getRowById": getRowById, + "getItemById": getItemById, + "getItemByIdx": getItemByIdx, + "refresh": refresh, + "updateItem": updateItem, + "insertItem": insertItem, + "addItem": addItem, + "deleteItem": deleteItem, + + // events + "onRowCountChanged": onRowCountChanged, + "onRowsChanged": onRowsChanged, + "onPagingInfoChanged": onPagingInfoChanged + }; + } + // Slick.Data.DataView + $.extend(true, window, { Slick: { Data: { DataView: DataView }}}); +})();
\ No newline at end of file diff --git a/slick.pager.js b/slick.pager.js index c9c45bb..9d0bb25 100644 --- a/slick.pager.js +++ b/slick.pager.js @@ -1,138 +1,144 @@ -function SlickGridPager(dataView, grid, $container) -{ - var $status, $contextMenu; - - function init() - { - dataView.onPagingInfoChanged.subscribe(function(pagingInfo) { - updatePager(pagingInfo); - }); - - constructPagerUI(); - updatePager(dataView.getPagingInfo()); - } - - function getNavState() +(function() { + function SlickGridPager(dataView, grid, $container) { - var cannotLeaveEditMode = !(!GlobalEditorLock.isEditing() || GlobalEditorLock.commitCurrentEdit()) - var pagingInfo = dataView.getPagingInfo(); - var lastPage = Math.floor(pagingInfo.totalRows/pagingInfo.pageSize); + var $status, $contextMenu; - return { - canGotoFirst: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0, - canGotoLast: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum != lastPage, - canGotoPrev: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0, - canGotoNext: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum < lastPage, - pagingInfo: pagingInfo, - lastPage: lastPage + function init() + { + dataView.onPagingInfoChanged.subscribe(function(pagingInfo) { + updatePager(pagingInfo); + }); + + constructPagerUI(); + updatePager(dataView.getPagingInfo()); } - } - - function setPageSize(n) - { - dataView.setPagingOptions({pageSize:n}); - } - - function gotoFirst() - { - if (getNavState().canGotoFirst) - dataView.setPagingOptions({pageNum: 0}); - } - - function gotoLast() - { - var state = getNavState(); - if (state.canGotoLast) - dataView.setPagingOptions({pageNum: state.lastPage}); - } - - function gotoPrev() - { - var state = getNavState(); - if (state.canGotoPrev) - dataView.setPagingOptions({pageNum: state.pagingInfo.pageNum-1}); - } - - function gotoNext() - { - var state = getNavState(); - if (state.canGotoNext) - dataView.setPagingOptions({pageNum: state.pagingInfo.pageNum+1}); - } - - function constructPagerUI() - { - $container.empty(); - - $status = $("<span class='slick-pager-status' />").appendTo($container); - - var $nav = $("<span class='slick-pager-nav' />").appendTo($container); - var $settings = $("<span class='slick-pager-settings' />").appendTo($container); - - $settings - .append("<span class='slick-pager-settings-expanded' style='display:none'>Show: <a data=0>All</a><a data='-1'>Auto</a><a data=25>25</a><a data=50>50</a><a data=100>100</a></span>"); - $settings.find("a[data]").click(function(e) { - var pagesize = $(e.target).attr("data"); - if (pagesize != undefined) - { - if (pagesize == -1) - { - var vp = grid.getViewport(); - setPageSize(vp.bottom-vp.top); - } - else - setPageSize(parseInt(pagesize)); - } - }); + function getNavState() + { + var cannotLeaveEditMode = !(!Slick.GlobalEditorLock.isEditing() || Slick.GlobalEditorLock.commitCurrentEdit()) + var pagingInfo = dataView.getPagingInfo(); + var lastPage = Math.floor(pagingInfo.totalRows/pagingInfo.pageSize); - $("<span class='ui-icon ui-icon-lightbulb' />") - .click(function() { $(".slick-pager-settings-expanded").toggle() }) - .appendTo($settings); + return { + canGotoFirst: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0, + canGotoLast: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum != lastPage, + canGotoPrev: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0, + canGotoNext: !cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum < lastPage, + pagingInfo: pagingInfo, + lastPage: lastPage + } + } - $("<span class='ui-icon ui-icon-seek-first' />") - .click(gotoFirst) - .appendTo($nav); + function setPageSize(n) + { + dataView.setPagingOptions({pageSize:n}); + } - $("<span class='ui-icon ui-icon-seek-prev' />") - .click(gotoPrev) - .appendTo($nav); - - $("<span class='ui-icon ui-icon-seek-next' />") - .click(gotoNext) - .appendTo($nav); + function gotoFirst() + { + if (getNavState().canGotoFirst) + dataView.setPagingOptions({pageNum: 0}); + } - $("<span class='ui-icon ui-icon-seek-end' />") - .click(gotoLast) - .appendTo($nav); + function gotoLast() + { + var state = getNavState(); + if (state.canGotoLast) + dataView.setPagingOptions({pageNum: state.lastPage}); + } - $container.find(".ui-icon") - .addClass("ui-state-default ui-corner-all") - .mouseover(function(e) { $(e.target).addClass("ui-state-hover") }) - .mouseout(function(e) { $(e.target).removeClass("ui-state-hover") }) + function gotoPrev() + { + var state = getNavState(); + if (state.canGotoPrev) + dataView.setPagingOptions({pageNum: state.pagingInfo.pageNum-1}); + } - $container.children().wrapAll("<div class='slick-pager zui-state-default' />"); - } + function gotoNext() + { + var state = getNavState(); + if (state.canGotoNext) + dataView.setPagingOptions({pageNum: state.pagingInfo.pageNum+1}); + } + + function constructPagerUI() + { + $container.empty(); + + $status = $("<span class='slick-pager-status' />").appendTo($container); + var $nav = $("<span class='slick-pager-nav' />").appendTo($container); + var $settings = $("<span class='slick-pager-settings' />").appendTo($container); - function updatePager(pagingInfo) - { - var state = getNavState(); + $settings + .append("<span class='slick-pager-settings-expanded' style='display:none'>Show: <a data=0>All</a><a data='-1'>Auto</a><a data=25>25</a><a data=50>50</a><a data=100>100</a></span>"); + + $settings.find("a[data]").click(function(e) { + var pagesize = $(e.target).attr("data"); + if (pagesize != undefined) + { + if (pagesize == -1) + { + var vp = grid.getViewport(); + setPageSize(vp.bottom-vp.top); + } + else + setPageSize(parseInt(pagesize)); + } + }); + + $("<span class='ui-icon ui-icon-lightbulb' />") + .click(function() { $(".slick-pager-settings-expanded").toggle() }) + .appendTo($settings); + + $("<span class='ui-icon ui-icon-seek-first' />") + .click(gotoFirst) + .appendTo($nav); + + $("<span class='ui-icon ui-icon-seek-prev' />") + .click(gotoPrev) + .appendTo($nav); + + $("<span class='ui-icon ui-icon-seek-next' />") + .click(gotoNext) + .appendTo($nav); + + $("<span class='ui-icon ui-icon-seek-end' />") + .click(gotoLast) + .appendTo($nav); + + $container.find(".ui-icon") + .addClass("ui-state-default ui-corner-all") + .mouseover(function(e) { $(e.target).addClass("ui-state-hover") }) + .mouseout(function(e) { $(e.target).removeClass("ui-state-hover") }) + + $container.children().wrapAll("<div class='slick-pager zui-state-default' />"); + } + + + function updatePager(pagingInfo) + { + var state = getNavState(); + + $container.find(".slick-pager-nav span").removeClass("ui-state-disabled"); + if (!state.canGotoFirst) $container.find(".ui-icon-seek-first").addClass("ui-state-disabled"); + if (!state.canGotoLast) $container.find(".ui-icon-seek-end").addClass("ui-state-disabled"); + if (!state.canGotoNext) $container.find(".ui-icon-seek-next").addClass("ui-state-disabled"); + if (!state.canGotoPrev) $container.find(".ui-icon-seek-prev").addClass("ui-state-disabled"); + + + if (pagingInfo.pageSize == 0) + $status.text("Showing all " + pagingInfo.totalRows + " rows"); + else + $status.text("Showing page " + (pagingInfo.pageNum+1) + " of " + (Math.floor(pagingInfo.totalRows/pagingInfo.pageSize)+1)); + } - $container.find(".slick-pager-nav span").removeClass("ui-state-disabled"); - if (!state.canGotoFirst) $container.find(".ui-icon-seek-first").addClass("ui-state-disabled"); - if (!state.canGotoLast) $container.find(".ui-icon-seek-end").addClass("ui-state-disabled"); - if (!state.canGotoNext) $container.find(".ui-icon-seek-next").addClass("ui-state-disabled"); - if (!state.canGotoPrev) $container.find(".ui-icon-seek-prev").addClass("ui-state-disabled"); - if (pagingInfo.pageSize == 0) - $status.text("Showing all " + pagingInfo.totalRows + " rows"); - else - $status.text("Showing page " + (pagingInfo.pageNum+1) + " of " + (Math.floor(pagingInfo.totalRows/pagingInfo.pageSize)+1)); + init(); } - - - init(); -} + // Slick.Controls.Pager + $.extend(true, window, { Slick: { Controls: { Pager: SlickGridPager }}}); +})(); + diff --git a/slick.remotemodel.js b/slick.remotemodel.js index 4dce1b8..0ffa555 100644 --- a/slick.remotemodel.js +++ b/slick.remotemodel.js @@ -18,166 +18,170 @@ function EventHelper() { } - -/*** - * A sample AJAX data store implementation. - * Right now, it's hooked up to load all Apple-related Digg stories, but can - * easily be extended to support and JSONP-compatible backend that accepts paging parameters. - */ -function RemoteModel() { - // private - var PAGESIZE = 50; - var data = {length:0}; - var searchstr = "apple"; - var sortcol = null; - var sortdir = 1; - var h_request = null; - var req = null; // ajax request - var req_page; - - // events - var onDataLoading = new EventHelper(); - var onDataLoaded = new EventHelper(); - - - function init() { - } - - - function isDataLoaded(from,to) { - for (var i=from; i<=to; i++) { - if (data[i] == undefined || data[i] == null) - return false; - } +(function() { + /*** + * A sample AJAX data store implementation. + * Right now, it's hooked up to load all Apple-related Digg stories, but can + * easily be extended to support and JSONP-compatible backend that accepts paging parameters. + */ + function RemoteModel() { + // private + var PAGESIZE = 50; + var data = {length:0}; + var searchstr = "apple"; + var sortcol = null; + var sortdir = 1; + var h_request = null; + var req = null; // ajax request + var req_page; - return true; - } - - - function clear() { - for (var key in data) { - delete data[key]; + // events + var onDataLoading = new EventHelper(); + var onDataLoaded = new EventHelper(); + + + function init() { } - data.length = 0; - } - - - function ensureData(from,to) { - if (from < 0) - from = 0; - var fromPage = Math.floor(from / PAGESIZE); - var toPage = Math.floor(to / PAGESIZE); - while (data[fromPage * PAGESIZE] !== undefined && fromPage < toPage) - fromPage++; + function isDataLoaded(from,to) { + for (var i=from; i<=to; i++) { + if (data[i] == undefined || data[i] == null) + return false; + } + + return true; + } + - while (data[toPage * PAGESIZE] !== undefined && fromPage < toPage) - toPage--; + function clear() { + for (var key in data) { + delete data[key]; + } + data.length = 0; + } + + + function ensureData(from,to) { + if (from < 0) + from = 0; - if (fromPage > toPage || ((fromPage == toPage) && data[fromPage*PAGESIZE] !== undefined)) { - // TODO: lookeahead + var fromPage = Math.floor(from / PAGESIZE); + var toPage = Math.floor(to / PAGESIZE); - //if () + while (data[fromPage * PAGESIZE] !== undefined && fromPage < toPage) + fromPage++; - return; - } + while (data[toPage * PAGESIZE] !== undefined && fromPage < toPage) + toPage--; + + if (fromPage > toPage || ((fromPage == toPage) && data[fromPage*PAGESIZE] !== undefined)) { + // TODO: lookeahead + + //if () + + return; + } + + + var url = "http://services.digg.com/search/stories?query=" + searchstr + "&offset=" + (fromPage * PAGESIZE) + "&count=" + (((toPage-fromPage)*PAGESIZE)+PAGESIZE) + "&appkey=http://slickgrid.googlecode.com&type=javascript" + + switch (sortcol) { + case "diggs": + url += ("&sort=" + ((sortdir>0)?"digg_count-asc":"digg_count-desc")); + break; + } - var url = "http://services.digg.com/search/stories?query=" + searchstr + "&offset=" + (fromPage * PAGESIZE) + "&count=" + (((toPage-fromPage)*PAGESIZE)+PAGESIZE) + "&appkey=http://slickgrid.googlecode.com&type=javascript" - - - switch (sortcol) { - case "diggs": - url += ("&sort=" + ((sortdir>0)?"digg_count-asc":"digg_count-desc")); - break; + + if (req) { + req.abort(); + data[req_page*PAGESIZE] = undefined; + } + + 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' + + req_page = fromPage; + + onDataLoading.notify({from:from, to:to}); + + req = $.jsonp({ + url: url, + callbackParameter: "callback", + cache: true, // Digg doesn't accept the autogenerated cachebuster param + success: onSuccess, + error: function(){ + onError(fromPage, toPage) + } + }); + }, 50); } - if (req) { - req.abort(); - data[req_page*PAGESIZE] = undefined; + function onError(fromPage,toPage) { + alert("error loading pages " + fromPage + " to " + toPage); } - if (h_request != null) - window.clearTimeout(h_request); - - h_request = window.setTimeout(function() { - for (var i=fromPage; i<=toPage; i++) - data[i*PAGESIZE] = null; // null indicates a 'requested but not available yet' + function onSuccess(resp) { + var from = resp.offset, to = resp.offset + resp.count; + data.length = parseInt(resp.total); - req_page = fromPage; + for (var i = 0; i < resp.stories.length; i++) { + data[from + i] = resp.stories[i]; + data[from + i].index = from + i; + } - onDataLoading.notify({from:from, to:to}); + req = null; - req = $.jsonp({ - url: url, - callbackParameter: "callback", - cache: true, // Digg doesn't accept the autogenerated cachebuster param - success: onSuccess, - error: function(){ - onError(fromPage, toPage) - } - }); - }, 50); - } - - - function onError(fromPage,toPage) { - alert("error loading pages " + fromPage + " to " + toPage); - } - - function onSuccess(resp) { - var from = resp.offset, to = resp.offset + resp.count; - data.length = parseInt(resp.total); + onDataLoaded.notify({from:from, to:to}); + } - for (var i = 0; i < resp.stories.length; i++) { - data[from + i] = resp.stories[i]; - data[from + i].index = from + i; + + function reloadData(from,to) { + for (var i=from; i<=to; i++) + delete data[i]; + + ensureData(from,to); } - req = null; - onDataLoaded.notify({from:from, to:to}); - } + function setSort(column,dir) { + sortcol = column; + sortdir = dir; + clear(); + } + + function setSearch(str) { + searchstr = str; + clear(); + } - function reloadData(from,to) { - for (var i=from; i<=to; i++) - delete data[i]; + init(); + + return { + // properties + "data": data, - ensureData(from,to); + // methods + "clear": clear, + "isDataLoaded": isDataLoaded, + "ensureData": ensureData, + "reloadData": reloadData, + "setSort": setSort, + "setSearch": setSearch, + + // events + "onDataLoading": onDataLoading, + "onDataLoaded": onDataLoaded + }; } - - - 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 }}}); +})(); diff --git a/tests/model benchmarks.html b/tests/model benchmarks.html index 4881c38..46a29f1 100644 --- a/tests/model benchmarks.html +++ b/tests/model benchmarks.html @@ -6,8 +6,8 @@ </head> <body> <script language="JavaScript" src="../lib/firebugx.js"></script> - <script language="JavaScript" src="../slick.model.js"></script> <script language="JavaScript" src="../lib/jquery-1.3.2.min.js"></script> + <script language="JavaScript" src="../slick.model.js"></script> <script> @@ -34,7 +34,7 @@ } - var dv = new DataView(); + var dv = new Slick.Data.DataView(); dv.beginUpdate(); dv.setItems(data); dv.setFilter(filter); diff --git a/tests/scrolling benchmarks.html b/tests/scrolling benchmarks.html index 6a0aa98..f5b35a4 100644 --- a/tests/scrolling benchmarks.html +++ b/tests/scrolling benchmarks.html @@ -119,7 +119,7 @@ // initialize the model - dataView = new DataView(); + dataView = new Slick.Data.DataView(); dataView.beginUpdate(); dataView.setItems(data); dataView.setFilter(myFilter); @@ -127,7 +127,7 @@ // initialize the grid - grid = new SlickGrid($("#myGrid"), dataView.rows, columns, options); + grid = new Slick.Grid($("#myGrid"), dataView.rows, columns, options); grid.onPostProcessRowNode = function(rowNode) { |