r1014 - in branches/dev/grid: tests/visual ui

r1014 - in branches/dev/grid: tests/visual ui


Author: paul.bakaus
Date: Wed Nov 26 08:21:04 2008
New Revision: 1014
Added:
branches/dev/grid/tests/visual/infiniteScrolling.html
branches/dev/grid/ui/ui.infiniteScrolling.js
Modified:
branches/dev/grid/tests/visual/grid.html
branches/dev/grid/ui/ui.grid.js
Log:
infiniteScrolling: initial implementation of a utility that solves infinite
table scrolling and provides callbacks to retrieve and fill rows
grid: supports and implements infinite scrolling (by disabling
this.options.pagination, see visual test for working prototype)
Modified: branches/dev/grid/tests/visual/grid.html
==============================================================================
--- branches/dev/grid/tests/visual/grid.html    (original)
+++ branches/dev/grid/tests/visual/grid.html    Wed Nov 26 08:21:04 2008
@@ -9,8 +9,9 @@
        <script type="text/javascript" src="../../ui/ui.core.js"></script>
        <script type="text/javascript" src="../../ui/ui.grid.js"></script>
        <script type="text/javascript" src="../../ui/ui.gridmodel.js"></script>
+        <script type="text/javascript"
src="../../ui/ui.infiniteScrolling.js"></script>
        <script type="text/javascript"
src="../../dev-selectable-ui/ui.selectable.js"></script>
-        <script type="text/javascript" src="../../ui/ui.resizable.js"></script>
+
        
@@ -67,7 +68,8 @@
                    url: "../data/employees-json.php",
                    limit: 20,
                    height: 200,
-                    toolbar: false
+                    toolbar: false,
+                    pagination: false
                });
            });
Added: branches/dev/grid/tests/visual/infiniteScrolling.html
==============================================================================
--- (empty file)
+++ branches/dev/grid/tests/visual/infiniteScrolling.html    Wed Nov 26
08:21:04 2008
@@ -0,0 +1,94 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+        <title>grid</title>
+        <link rel="stylesheet" href="../../themes/default/ui.all.css"
type="text/css" media="screen" title="no title" charset="utf-8">
+        
+        <script type="text/javascript" src="../../jquery-1.2.6.js"></script>
+        <script type="text/javascript" src="../../ui/ui.core.js"></script>
+        <script type="text/javascript"
src="../../ui/ui.infiniteScrolling.js"></script>
+        
+
+
+        <style type="text/css" media="screen">
+
+            body, html {
+                font-size: 10px;
+                font-family: Arial;
+            }
+            
+            td {
+                padding: 3px;
+                border: 2px solid #bbb;
+            }
+
+
+        </style>
+
+        <script type="text/javascript">
+
+            $(document).ready(function() {
+
+                $('.scrollme').infiniteScrolling({
+                    total: 1000,
+                    template: '<tr class="{$1}"><td>{$2}</td><td>{$3}</td></tr>',
+                    scroll: function(e, ui) {
+                        
+                        var data = [];
+                        for (var i=0; i < 20; i++) {
+                            data.push({ 1: 'test', 2: 'Row '+(ui.start+i), 3: 'Entry' });
+                        };
+                        
+                        window.setTimeout(function() {
+
+                            ui.fill({
+                                block: ui.block,
+                                data: data
+                            });
+
+                        }, 1000);
+                        
+
+                        
+                    }
+                });
+
+            });
+
+
+
+        </script>
+
+    </head>
+    <body>
+
+        <div class='scrollme' style='width: 500px; height: 500px; overflow:
auto; background: #eee; border: 5px solid black;'>
+        <table cellpadding='0' cellspacing='0' width='100%'>
+            <tbody>
+                <tr><td>Row 1</td><td>Entry (init)</td></tr>
+                <tr><td>Row 2</td><td>Entry (init)</td></tr>
+                <tr><td>Row 3</td><td>Entry (init)</td></tr>
+                <tr><td>Row 4</td><td>Entry (init)</td></tr>
+                <tr><td>Row 5</td><td>Entry (init)</td></tr>
+                <tr><td>Row 6</td><td>Entry (init)</td></tr>
+                <tr><td>Row 7</td><td>Entry (init)</td></tr>
+                <tr><td>Row 8</td><td>Entry (init)</td></tr>
+                <tr><td>Row 9</td><td>Entry (init)</td></tr>
+                <tr><td>Row 10</td><td>Entry (init)</td></tr>
+                <tr><td>Row 11</td><td>Entry (init)</td></tr>
+                <tr><td>Row 12</td><td>Entry (init)</td></tr>
+                <tr><td>Row 13</td><td>Entry (init)</td></tr>
+                <tr><td>Row 14</td><td>Entry (init)</td></tr>
+                <tr><td>Row 15</td><td>Entry (init)</td></tr>
+                <tr><td>Row 16</td><td>Entry (init)</td></tr>
+                <tr><td>Row 17</td><td>Entry (init)</td></tr>
+                <tr><td>Row 18</td><td>Entry (init)</td></tr>
+                <tr><td>Row 19</td><td>Entry (init)</td></tr>
+                <tr><td>Row 20</td><td>Entry (init)</td></tr>
+            </tbody>
+        </table>
+        </div>
+
+    </body>
+</html>
\ No newline at end of file
Modified: branches/dev/grid/ui/ui.grid.js
==============================================================================
--- branches/dev/grid/ui/ui.grid.js    (original)
+++ branches/dev/grid/ui/ui.grid.js    Wed Nov 26 08:21:04 2008
@@ -179,22 +179,59 @@
                self._updatePagination(response);
            }
-            if(o && o.columns) {
+
+            if(!self.infiniteScrolling)
                self.content.empty();
+
+            if(o && o.columns) {
                self.columnsContainer.empty();
                self._addColumns(response.columns);
-            } else {
-                self.content.empty();
            }
-            for (var i=0; i < response.records.length; i++) {
-                self._addRow(response.records[i]);
-            };
-            self._syncColumnWidth();
+            if(self.infiniteScrolling) {
+
+                var data = [];
+                for (var i=0; i < response.records.length; i++) {
+                    data.push(self._addRow(response.records[i]));
+                };
+
+                o.fill({
+                    block: o.block,
+                    data: data
+                });                
+
+            } else {
-            $('div.ui-grid-limits', self.footer)
-                .html('Result ' + options.start + '-' + (options.start +
options.limit) + ' of ' + response.totalRecords);
+                for (var i=0; i < response.records.length; i++) {
+                    self._addRow(response.records[i]);
+                };
+    
+                self._syncColumnWidth();
+
+            }
+            
+            //Initiate infinite scrolling if we don't use pagination and total
records exceed the displayed records
+            if(!self.infiniteScrolling && !self.options.pagination &&
self.options.limit < response.totalRecords) {
+                
+                self.infiniteScrolling = true;
+                self.infiniteScrollingJustInitiated = true;
+                $('div.ui-grid-content', self.grid).infiniteScrolling({
+                    total: response.totalRecords,
+                    block: 10,
+                    scroll: function(e, ui) {
+                        self.offset = ui.start;
+                        self._update({ fill: ui.fill, block: ui.block });
+                    },
+                    update: function(e, ui) {
+                        $('div.ui-grid-limits', self.footer).html('Result ' + ui.firstItem
+ '-' + ui.lastItem + ' of ' +
$(this).infiniteScrolling('option', 'total'));
+                    }
+                });
+                
+            }
+
+            if(!self.infiniteScrolling)
+                $('div.ui-grid-limits', self.footer).html('Result ' + options.start
+ '-' + (options.start + options.limit) + ' of ' + response.totalRecords);
        });
@@ -218,7 +255,8 @@
    _addColumns: function(item) {
        this.columns = item;
-        var totalWidth = 0;
+        var totalWidth = 25;
+        
        for (var i=0; i < item.length; i++) {
            var column = $('<td class="ui-grid-column-header
ui-default-state"><div>'+item[i].label+'</div></td>')
                .width(item[i].width)
@@ -227,15 +265,19 @@
            totalWidth += item[i].width;
        };
        
+        //This column is the last and only used to serve as placeholder for a
non-existant scrollbar
+        $('<td class="ui-grid-column-header
ui-default-state"><div></div></td>').width(25).appendTo(this.columnsContainer);
+        
+        //Update the total width of the wrapper of the column headers
        this.columnsContainer.parent().parent().width(totalWidth);
    },
-    _addRow: function(item) {
+    _addRow: function(item, dontAdd) {
-        var row = $('<tr class="ui-grid-row"></tr>')
-            .appendTo(this.content)
-            .hover(function() {
+        var row = $('<tr class="ui-grid-row"></tr>');
+        if(!dontAdd) row.appendTo(this.content);
+        row.hover(function() {
                $(this).addClass('ui-grid-row-hover');
            }, function() {
                $(this).removeClass('ui-grid-row-hover');
@@ -245,6 +287,8 @@
            $('<td class="ui-grid-column
ui-active-state"><div>'+item[this.columns[i].id]+'</div></td>')
                .appendTo(row);
        };
+        
+        return row;
    }
Added: branches/dev/grid/ui/ui.infiniteScrolling.js
==============================================================================
--- (empty file)
+++ branches/dev/grid/ui/ui.infiniteScrolling.js    Wed Nov 26 08:21:04 2008
@@ -0,0 +1,130 @@
+/*
+ * jQuery UI Dialog @VERSION
+ *
+ * Copyright (c) 2008 Richard D. Worth (rdworth.org)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Dialog
+ *
+ * Depends:
+ *    ui.core.js
+ *    ui.draggable.js
+ *    ui.resizable.js
+ */
+(function($) {
+
+$.widget("ui.infiniteScrolling", {
+    
+    _init: function() {
+        
+        var self = this;
+        this.tbody = $('> table > tbody', this.element);
+        this.height = this.element.height();
+        this.rowHeight = $('tr', this.tbody).height();
+        
+        //Prepare the cache that tells us what already has been loaded and what
not
+        this._prepareCache();
+        
+        //Alocate all rows in this.options.total to change the scrollbar size
+        this._allocateRows();
+        
+        //Call update the first time to retrieve the first set of rows
+        this._update();
+        
+        this.element.bind('scroll', function(event) {
+            self._update(event);
+        });
+        
+    },
+    
+    _prepareCache: function() {
+        
+        this.cache = new Array(Math.ceil(this.options.total /
this.options.block));
+        var alreadyCached = Math.floor($('tr', this.tbody).length /
this.options.block);
+        for (var i=0; i < alreadyCached; i++) {
+            this.cache[i] = (new Date()).getTime();
+        };
+        
+    },
+    
+    _allocateRows: function() {
+        
+        var num = this.options.total - $('tr', this.tbody).length;
+        var colspan = $('tr:eq(0) > td', this.tbody).length;
+        var newHTML = '';
+        
+        for (var i=0; i < num; i++) {
+            newHTML += '<tr><td
style="height:'+this.rowHeight+'px;border:0;padding:0;margin:0;"
colspan="'+colspan+'"></td></tr>';
+        };
+        
+        //Appending this whole block to innerHTML is drastically faster than
individual appends in the loop above
+        this.tbody[0].innerHTML += newHTML;
+    
+    },
+    
+    _update: function(event) {
+        
+        var self = this;
+        var start = this.element[0].scrollTop;
+        var stop = start + this.height;
+        
+        var firstItem = Math.floor(start / this.rowHeight);
+        var lastItem = Math.ceil(stop / this.rowHeight);
+        
+        var firstBlock = Math.round(firstItem / this.options.block);
+        var lastBlock = Math.round(lastItem / this.options.block);
+
+        for (var i=firstBlock - this.options.preload; i <= lastBlock +
this.options.preload; i++) {
+            
+            if(i < 0 || i >= this.cache.length) continue;
+            if(this.cache[i]) continue;
+            
+            this.cache[i] = (new Date()).getTime(); //TODO: Revalidation option
+            this._trigger('scroll', event, { block: i, start: i *
this.options.block, fill: function() { return self.fill.apply(self,
arguments); } });
+
+        };
+        
+        this._trigger('update', event, { firstBlock: firstBlock, lastBlock:
lastBlock, firstItem: firstItem, lastItem: lastItem });
+        
+    },
+    
+    fill: function(o) {
+        
+        var rows = this.tbody[0].rows;
+        
+        for (var i=0; i < o.data.length; i++) {
+
+            if(o.data[i].jquery || o.data[i].nodeType || o.data[i].constructor ==
String) {
+                var template = o.data[i];
+            } else {
+                var template = this.options.template;
+                for(var r in o.data[i]) {
+                    template = template.replace(new RegExp('\\{\\$'+r+'\\}', 'g'),
o.data[i][r]);
+                }
+            }
+
+            
+            //I'm sure there is a better way to do this..
+            this.tbody[0].replaceChild($(template)[0], rows[(o.block *
this.options.block)+i]);
+            
+        };
+        
+    }
+    
+});
+
+$.extend($.ui.infiniteScrolling, {
+    version: "@VERSION",
+    defaults: {
+        total: 1000,
+        block: 20,
+        preload: 1
+    }
+    
+});
+
+
+
+
+})(jQuery);