browser: only add to the grid scope data that's needed; not the whole grid object
authorBill Erickson <berick@esilibrary.com>
Tue, 29 Apr 2014 16:33:31 +0000 (12:33 -0400)
committerBill Erickson <berick@esilibrary.com>
Tue, 29 Apr 2014 16:33:31 +0000 (12:33 -0400)
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Open-ILS/src/templates/staff/parts/t_autogrid.tt2
Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
Open-ILS/web/js/ui/default/staff/circ/patron/app.js
Open-ILS/web/js/ui/default/staff/services/grid.js

index 98aec72..26d3eb6 100644 (file)
@@ -7,16 +7,16 @@
 <div class="eg-grid-row eg-grid-action-row">
 
   <div style="flex:1">
-    <div class="eg-grid-primary-label">{{grid.mainLabel}}</div>
+    <div class="eg-grid-primary-label">{{mainLabel}}</div>
   </div>
 
-  <div class="btn-group" ng-if="grid.menuLabel" style="margin-right: 10px">
+  <div class="btn-group" ng-if="menuLabel" style="margin-right: 10px">
     <button type="button" 
       class="btn btn-default dropdown-toggle" data-toggle="dropdown">
-      {{grid.menuLabel}}<span class="caret"></span>
+      {{menuLabel}}<span class="caret"></span>
     </button>
     <ul class="dropdown-menu">
-      <li ng-repeat="item in grid.menuItems" ng-class="{divider: item.divider}">
+      <li ng-repeat="item in menuItems" ng-class="{divider: item.divider}">
         <a ng-if="!item.divider" href='' 
           ng-click="item.handler(item, item.handlerData)">{{item.label}}</a>
       </li>
 
     <!-- first page -->
     <button type="button" class="btn btn-default" 
-      ng-class="{disabled : grid.onFirstPage()}" 
-      ng-click="grid.offset = 0;grid.collect()"
+      ng-class="{disabled : onFirstPage()}" 
+      ng-click="offset(0);collect()"
       title="[% l('Start') %]">
         <span class="glyphicon glyphicon-fast-backward"></span>
     </button>
 
     <!-- previous page -->
     <button type="button" class="btn btn-default" 
-      ng-class="{disabled : grid.onFirstPage()}"
-      ng-click="grid.decrementPage()"
+      ng-class="{disabled : onFirstPage()}"
+      ng-click="decrementPage()"
       title="[% l('Previous Page') %]">
         <span class="glyphicon glyphicon-backward"></span>
     </button>
     <!-- next page -->
     <!-- todo: paging needs a total count value to be fully functional -->
     <button type="button" class="btn btn-default" 
-      ng-class="{disabled : !grid.hasNextPage()}"
-      ng-click="grid.incrementPage()"
+      ng-class="{disabled : !hasNextPage()}"
+      ng-click="incrementPage()"
       title="[% l('Next Page') %]">
         <span class="glyphicon glyphicon-forward"></span>
     </button>
 
     <!-- actions drop-down menu -->
-    <div class="btn-group" ng-if="grid.actions.length">                                                  
+    <div class="btn-group" ng-if="actions.length">                                                  
       <button type="button" class="btn btn-default dropdown-toggle"          
           ng-class="{disabled : false}" data-toggle="dropdown">     
         [% l('Actions') %] <span class="caret"></span>                       
       </button>                                                              
       <ul class="dropdown-menu pull-right">                                  
-        <li ng-class="{disabled : false}" ng-repeat="action in grid.actions">
-          <a href="" ng-click="grid.actionLauncher(action)">{{action.label}}</a>
+        <li ng-class="{disabled : false}" ng-repeat="action in actions">
+          <a href="" ng-click="actionLauncher(action)">{{action.label}}</a>
         </li>                                                                
       </ul>
     </div>
     <div class="btn-group">
       <button type="button" title="[% ('Select Row Count') %]"
         class="btn btn-default dropdown-toggle" data-toggle="dropdown">
-        [% l('Rows [_1]', '{{grid.limit}}') %]
+        [% l('Rows [_1]', '{{limit()}}') %]
         <span class="caret"></span>
       </button>
       <ul class="dropdown-menu">
         <li ng-repeat="t in [5,10,25,50,100]">
-          <a href='' ng-click='grid.offset=0;grid.limit=t;grid.collect()'>
+          <a href='' ng-click='offset(0);limit(t);collect()'>
             {{t}}
           </a>
         </li>
@@ -82,7 +82,7 @@
     <div class="btn-group">
       <button type="button" title="[% ('Select Page') %]"
         class="btn btn-default dropdown-toggle" data-toggle="dropdown">
-        [% l('Page [_1]', '{{grid.page()}}') %]
+        [% l('Page [_1]', '{{page()}}') %]
         <span class="caret"></span>
       </button>
       <ul class="dropdown-menu">
@@ -93,7 +93,7 @@
               ng-click="$event.stopPropagation()"/>
             <span class="input-group-btn">
               <button class="btn btn-default" type="button"
-                ng-click="grid.goToPage(pageFromUI);pageFromUI=''">
+                ng-click="goToPage(pageFromUI);pageFromUI=''">
                 [% l('Go To...') %]
               </button>
             </span>
         </li>
         <li role="presentation" class="divider"></li>
         <li ng-repeat="t in [1,2,3,4,5,10,25,50,100]">
-          <a href='' ng-click='grid.goToPage(t)'>{{t}}</a>
+          <a href='' ng-click='goToPage(t)'>{{t}}</a>
         </li>
       </ul>
     </div>
       data-toggle="dropdown"><span class="caret"></span>
     </button>
     <ul class="dropdown-menu pull-right">
-      <li><a href='' ng-click="grid.toggleConfDisplay()">
+      <li><a href='' ng-click="toggleConfDisplay()">
         <span class="glyphicon glyphicon-wrench"></span>
         [% l('Configure Columns') %]
       </a></li>
-      <li><a href='' ng-click="grid.columnsProvider.showAllColumns()">
+      <li><a href='' ng-click="showAllColumns()">
         <span class="glyphicon glyphicon-resize-full"></span>
         [% l('Show All Columns') %]
       </a></li>
-      <li><a href='' ng-click="grid.columnsProvider.hideAllColumns()">
+      <li><a href='' ng-click="hideAllColumns()">
         <span class="glyphicon glyphicon-resize-small"></span>
         [% l('Hide All Columns') %]
       </a></li>
-      <li><a ng-click="grid.generateCSVExportURL()" 
-        download="{{grid.csvExportFileName}}.csv" ng-href="{{grid.csvExportURL}}">
+      <li><a ng-click="generateCSVExportURL()" 
+        download="{{csvExportFileName}}.csv" ng-href="{{csvExportURL}}">
         <span class="glyphicon glyphicon-download"></span>
         [% l('Download CSV') %]
       </a></li>
-      <li><a href='' ng-click="grid.printCSV()">
+      <li><a href='' ng-click="printCSV()">
         <span class="glyphicon glyphicon-print"></span>
         [% l('Print CSV') %]
       </a></li>
       <li role="presentation" class="divider"></li>
-      <li ng-repeat="col in grid.columnsProvider.columns">
-        <a href='' ng-click="grid.columnsProvider.visible[col.name] = 
-            !grid.columnsProvider.visible[col.name]">
-            <span ng-if="grid.columnsProvider.visible[col.name]" 
+      <li ng-repeat="col in columns">
+        <a href='' ng-click="col.visible = !col.visible">
+            <span ng-if="col.visible" 
               class="label label-success">&#x2713;</span>
-            <span ng-if="!grid.columnsProvider.visible[col.name]
+            <span ng-if="!col.visible
               class="label label-warning">&#x2717;</span>
             <span>{{col.label}}</span>
         </a>
 </div>
 
 <!-- Grid -->
-<div class="eg-grid" ng-class="{'eg-grid-as-conf' : grid.showGridConf}">
+<div class="eg-grid" ng-class="{'eg-grid-as-conf' : showGridConf}">
 
   <!-- import our eg-grid-field defs -->
   <div ng-transclude></div>
 
   <div class="eg-grid-row eg-grid-header-row">
-    <div class="eg-grid-cell eg-grid-cell-stock" style="flex:{{grid.indexFlex}}">
+    <div class="eg-grid-cell eg-grid-cell-stock" style="flex:{{indexFlex}}">
       <div>[% l('#') %]</div>
     </div>
-    <div class="eg-grid-cell eg-grid-cell-stock" style="flex:{{grid.selectorFlex}}">
+    <div class="eg-grid-cell eg-grid-cell-stock" style="flex:{{selectorFlex}}">
       <div>
-        <input type='checkbox' ng-click="grid.toggleSelectAllItems()"/>
+        <input type='checkbox' ng-click="toggleSelectAllItems()"/>
       </div>
     </div>
     <div class="eg-grid-cell"
         eg-grid-column-drag-dest
         column="{{col.name}}"
-        eg-right-click="grid.onContextMenu($event)"
-        ng-repeat="col in grid.columnsProvider.columns"
+        eg-right-click="onContextMenu($event)"
+        ng-repeat="col in columns"
         style="flex:{{col.flex}}"
-        ng-show="grid.columnsProvider.visible[col.name]">
+        ng-show="col.visible">
 
         <div style="display:flex">
           <div style="flex:1" class="eg-grid-column-move-handle">
             <div ng-if="col.sortable">
               <a column="{{col.name}}" href='' 
                 eg-grid-column-drag-source
-                ng-click="grid.quickSort(col.name)">{{col.label}}</a>
+                ng-click="quickSort(col.name)">{{col.label}}</a>
             </div>
             <div ng-if="!col.sortable">
               <div column="{{col.name}}" eg-grid-column-drag-source>{{col.label}}</div>
   </div>
 
   <!-- Inline grid configuration row -->
-  <div class="eg-grid-row eg-grid-conf-row" ng-show="grid.showGridConf">
+  <div class="eg-grid-row eg-grid-conf-row" ng-show="showGridConf">
     <div class="eg-grid-cell eg-grid-cell-conf-header" 
-        style="flex:{{grid.indexFlex + grid.selectorFlex}}">
+        style="flex:{{indexFlex + selectorFlex}}">
       <div class="eg-grid-conf-cell-entry">[% l('Expand') %]</div>
       <div class="eg-grid-conf-cell-entry">[% l('Shrink') %]</div>
-      <div class="eg-grid-conf-cell-entry" ng-if="!grid.disableMultiSort">[% l('Sort') %]</div>
+      <div class="eg-grid-conf-cell-entry" ng-if="!disableMultiSort">[% l('Sort') %]</div>
     </div>
     <div class="eg-grid-cell"
-      ng-repeat="col in grid.columnsProvider.columns"
+      ng-repeat="col in columns"
       style="flex:{{col.flex}}"
-      ng-show="grid.columnsProvider.visible[col.name]">
+      ng-show="col.visible">
       <div class="eg-grid-conf-cell-entry">
         <a href="" title="[% l('Make column wider') %]"
-          ng-click="grid.modifyColumnFlex(col,1)">
+          ng-click="modifyColumnFlex(col,1)">
           <span class="glyphicon glyphicon-fast-forward"></span>
         </a>
       </div>
       <div class="eg-grid-conf-cell-entry">
         <a href="" title="[% l('Make column narrower') %]"
-          ng-click="grid.modifyColumnFlex(col,-1)">
+          ng-click="modifyColumnFlex(col,-1)">
           <span class="glyphicon glyphicon-fast-backward"></span>
         </a>
       </div>
-      <div class="eg-grid-conf-cell-entry" ng-if="!grid.disableMultiSort">
+      <div class="eg-grid-conf-cell-entry" ng-if="!disableMultiSort">
         <div ng-if="col.multisortable">
           <input type='number' ng-model="col.sort"
             title="[% l('Sort Priority / Direction') %]" style='width:2.3em'/>
   </div>
 
   <div class="eg-grid-content-body">
-    <div ng-show="grid.count() == 0" 
+    <div ng-show="items.length == 0" 
       class="alert alert-info">[% l('No Items To Display') %]</div>
 
     <div class="eg-grid-row" 
         id="eg-grid-row-{{$index + 1}}"
-        ng-repeat="item in grid.items"
-        ng-show="grid.count() > 0"
-        ng-class="{'eg-grid-row-selected' : grid.selected[grid.indexValue(item)]}">
-      <div class="eg-grid-cell eg-grid-cell-stock" style="flex:{{grid.indexFlex}}"
-        ng-click="grid.handleRowClick($event, item)">
-        {{$index + grid.offset + 1}}
+        ng-repeat="item in items"
+        ng-show="items.length > 0"
+        ng-class="{'eg-grid-row-selected' : selected[indexValue(item)]}">
+      <div class="eg-grid-cell eg-grid-cell-stock" style="flex:{{indexFlex}}"
+        ng-click="handleRowClick($event, item)">
+        {{$index + offset() + 1}}
       </div>
-      <div class="eg-grid-cell eg-grid-cell-stock" style="flex:{{grid.selectorFlex}}">
+      <div class="eg-grid-cell eg-grid-cell-stock" style="flex:{{selectorFlex}}">
         <!-- ng-click=handleRowClick here has unintended 
              consequences and is unnecessary, avoid it -->
         <div>
           <input type='checkbox'  
-            ng-model="grid.selected[grid.indexValue(item)]"/>
+            ng-model="selected[indexValue(item)]"/>
         </div>
       </div>
       <div class="eg-grid-cell eg-grid-cell-content"
-          ng-click="grid.handleRowClick($event, item)"
-          ng-repeat="col in grid.columnsProvider.columns"
+          ng-click="handleRowClick($event, item)"
+          ng-repeat="col in columns"
           style="flex:{{col.flex}}"
-          ng-show="grid.columnsProvider.visible[col.name]">
-        {{grid.dataProvider.itemFieldValue(item, col) | egGridValueFilter:col}}
+          ng-show="col.visible">
+        {{itemFieldValue(item, col)}}
       </div>
     </div>
   </div>
index 9e0b6fa..0da8fbe 100644 (file)
@@ -486,23 +486,26 @@ function($scope,  $q , $routeParams,  bucketSvc) {
     $scope.setTab('view');
     $scope.bucketId = $routeParams.id;
 
-    // idQuery contents will change with each bucket loaded
-    // as the query changes, the grid will notice and refresh itself
-    $scope.gridQuery = function() { 
-        if (bucketSvc.currentBucket) {
-            var ids = bucketSvc.currentBucket.items().map(
-                function(i){return i.target_biblio_record_entry()}
-            );
-            if (ids.length) return {id : ids};
-        }
-        return {};
-    }
+    // when no items are linked to a bucket, use noQuery, since the 
+    // watchers will see it's the same value (and not a new ref to 
+    // a new {} each time)
+    var noQuery = {};
+    var myQuery = {};
+    $scope.gridQuery = function() { return myQuery }
 
     function drawBucket() {
-        if (!$scope.bucketId ||
-            bucketSvc.bucketRefreshLevel($scope.bucketId) == 2) 
-            return $q.when();
-        return bucketSvc.fetchBucket($scope.bucketId);
+        return bucketSvc.fetchBucket($scope.bucketId).then(
+            function(bucket) {
+                var ids = bucket.items().map(
+                    function(i){return i.target_biblio_record_entry()}
+                );
+                if (ids.length) {
+                    myQuery = {id : ids};
+                } else {
+                    myQuery = noQuery;
+                }
+            }
+        );
     }
 
     $scope.detachRecords = function(records) {
index acb7203..e2ebb30 100644 (file)
@@ -231,6 +231,10 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egEnv,
     $scope.focusMe = true;
     $scope.searchArgs = {};
 
+    // cache of object field values; speeds up grid rendering for
+    // nested objects.  TODO: move into grid?
+    var fieldValueCache = {};
+
     if (patronSvc.lastSearch) {
         // populate the search form with our cached search info
         angular.forEach(patronSvc.lastSearch.search, function(val, key) {
@@ -308,6 +312,7 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egEnv,
             return deferred.promise;
         }
 
+        fieldValueCache = {};
         patronSvc.patrons = [];
         egNet.request(
             'open-ils.actor',
@@ -335,7 +340,19 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egEnv,
     };
 
     provider.itemFieldValue = function(item, column) {
-        return provider.nestedItemFieldValue(item, column);
+
+        if (fieldValueCache[item.id()]) {
+            if (fieldValueCache[item.id()][column.path] !== undefined) {
+                return fieldValueCache[item.id()][column.path];
+            }
+        } else {
+            fieldValueCache[item.id()] = {};
+        }
+
+        fieldValueCache[item.id()][column.path] =
+            provider.nestedItemFieldValue(item, column);
+
+        return fieldValueCache[item.id()][column.path];
     };
 
     $scope.patronSearchGridProvider = provider;
@@ -395,7 +412,7 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egEnv,
 
     function compileSort() {
 
-        if (!$scope.patronSearchGridProvider.sort.length) {
+        if (!provider.sort.length) {
             return [ // default
                 "family_name ASC",
                 "first_given_name ASC",
@@ -406,7 +423,7 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egEnv,
 
         var sort = [];
         angular.forEach(
-            $scope.patronSearchGridProvider.sort,
+            provider.sort,
             function(sortdef) {
                 if (angular.isObject(sortdef)) {
                     var name = Object.keys(sortdef)[0];
@@ -425,7 +442,7 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egEnv,
     // refresh itself.
     $scope.search = function(args) { // args === $scope.searchArgs
         if (args && Object.keys(args).length) 
-            $scope.patronSearchGridProvider.increment();
+            provider.increment();
     }
 
     // TODO: move this into the (forthcoming) grid row activate action
index 76b74db..5848110 100644 (file)
@@ -38,21 +38,19 @@ angular.module('egGridMod',
             //  -multisort : sort priorities config disabled by default
             features : '@',
 
-            initialOffset : '=',
-
             // optional primary grid label
             mainLabel : '@',
 
             // if true, use the IDL class label as the mainLabel
             autoLabel : '=', 
 
+            // optional context menu label
+            menuLabel : '@',
+
             // called on each item retrieved in collect() with the item
             // as the argument.  Useful for modiying objects before they
             // are absorbed by the grid.
-            onItemRetrieved : '=', 
-
-            // optional context menu label
-            menuLabel : '@'
+            onItemRetrieved : '='
         },
 
         // TODO: avoid hard-coded url
@@ -62,7 +60,7 @@ angular.module('egGridMod',
             // link() is called after page compilation, which means our
             // eg-grid-field's have been parsed and loaded.  Now it's 
             // safe to perform our initial page load.
-            scope.grid.collect();
+            scope.collect();
         },
 
         controller : [
@@ -76,41 +74,68 @@ angular.module('egGridMod',
             grid.init = function() {
                 grid.offset = 0;
                 grid.limit = 25;
-                grid.items = [];
-                grid.selected = {}; // idField-based
-                grid.actions = [];
+                $scope.items = [];
+                $scope.showGridConf = false;
                 grid.totalCount = -1;
-                grid.idlClass = $scope.idlClass;
-                grid.mainLabel = $scope.mainLabel;
-                grid.indexField = $scope.idField;
-                grid.showGridConf = false;
-                grid.dataProvider = $scope.itemsProvider;
-                grid.menuLabel = $scope.menuLabel;
-                grid.onItemRetrieved = $scope.onItemRetrieved;
 
-                grid.menuItems = [];
+                // default flex values for the index and selector columns
+                $scope.indexFlex = 1;
+                $scope.selectorFlex = 1;
+
+                $scope.actions = [];
+                grid.addAction = function(act) {
+                    $scope.actions.push(act);
+                }
+
+                $scope.selected = {};
+
+                $scope.menuItems = [];
                 grid.addMenuItem = function(item) {
-                    grid.menuItems.push(item);
+                    $scope.menuItems.push(item);
                 }
 
-                // default flex values for the index and selector columns
-                grid.indexFlex = 1;
-                grid.selectorFlex = 1;
-                grid.features = ($scope.features) ? 
+                // items needed only by the grid; remove from scope
+                angular.forEach( 
+                    ['idlClass', 'onItemRetrieved','persistKey'], 
+                    function(field) {
+                        grid[field] = $scope[field];
+                        delete $scope[field];
+                    }
+                );
+
+                grid.indexField = $scope.idField;
+                delete $scope.idField;
+
+                grid.dataProvider = $scope.itemsProvider;
+                delete $scope.itemsProvider;
+
+                var features = ($scope.features) ? 
                     $scope.features.split(',') : [];
+                delete $scope.features;
+
+                //$scope.disableMultiSort = features.indexOf('-multisort') > -1;
 
                 grid.columnsProvider = egGridColumnsProvider.instance({
                     idlClass : grid.idlClass,
-                    defaultToHidden : (grid.features.indexOf('-display') > -1),
-                    defaultToNoSort : (grid.features.indexOf('-sort') > -1),
-                    defaultToNoMultiSort : (grid.features.indexOf('-multisort') > -1)
+                    defaultToHidden : (features.indexOf('-display') > -1),
+                    defaultToNoSort : (features.indexOf('-sort') > -1),
+                    defaultToNoMultiSort : (features.indexOf('-multisort') > -1)
                 });
 
+                $scope.columns = grid.columnsProvider.columns;
+                $scope.showAllColumns = function() {
+                    grid.columnsProvider.showAllColumns();
+                }
+                $scope.hideAllColumns = function() {
+                    grid.columnsProvider.hideAllColumns();
+                }
+
                 if ($scope.autoFields) {
                     grid.indexField = egIDL.classes[grid.idlClass].pkey;
                     if (grid.autoLabel)
-                        grid.mainLabel = egIDL.classes[grid.idlClass].label;
+                        $scope.mainLabel = egIDL.classes[grid.idlClass].label;
                     grid.columnsProvider.compileAutoColumns();
+                    delete $scope.autoFields;
                 }
 
                 if (grid.dataProvider) {
@@ -120,8 +145,7 @@ angular.module('egGridMod',
                     $scope.$watch(
                         function() { return grid.dataProvider.revision() },
                         function(newVal, oldVal) { 
-                            // hmm, why is this check necessary?
-                            if (newVal != oldVal) grid.collect();
+                            if (newVal !== oldVal) grid.collect();
                         }
                     );
 
@@ -135,16 +159,21 @@ angular.module('egGridMod',
                     });
 
                     $scope.$watch(
-                        function() { return $scope.query() }, 
+                        function() { return grid.dataProvider.query() },
                         function(newVal, oldVal) { 
-                            // hmm, why is this check necessary?
-                            if (!angular.equals(newVal, oldVal))
+                            if (newVal !== oldVal)
                                 grid.collect() 
                         },
                         true // object comparison
                     );
+                    delete $scope.query;
                 }
 
+                $scope.itemFieldValue = grid.dataProvider.itemFieldValue;
+                $scope.indexValue = function(item) {
+                    return grid.indexValue(item)
+                };
+
                 // this allows the caller to pass in initializtion 
                 // values, like offset, for cases when the caller may
                 // be caching grid data between route loads.
@@ -158,18 +187,18 @@ angular.module('egGridMod',
                 }
     
                 grid.compileSort();
-                $scope.grid = grid;
             }
 
-            grid.onContextMenu = function($event) {
+            $scope.onContextMenu = function($event) {
                 var col = angular.element($event.target).attr('column');
+                console.log('selected column ' + col);
             }
 
-            grid.page = function() {
+            $scope.page = function() {
                 return (grid.offset / grid.limit) + 1;
             }
 
-            grid.goToPage = function(page) {
+            $scope.goToPage = function(page) {
                 page = Number(page);
                 if (angular.isNumber(page) && page > 0) {
                     grid.offset = (page - 1) * grid.limit;
@@ -177,11 +206,23 @@ angular.module('egGridMod',
                 }
             }
 
-            grid.onFirstPage = function() {
+            $scope.offset = function(o) {
+                if (angular.isNumber(o))
+                    grid.offset = o;
+                return grid.offset 
+            }
+
+            $scope.limit = function(l) { 
+                if (angular.isNumber(l))
+                    grid.limit = l;
+                return grid.limit 
+            }
+
+            $scope.onFirstPage = function() {
                 return grid.offset == 0;
             }
 
-            grid.hasNextPage = function() {
+            $scope.hasNextPage = function() {
                 // we have less data than requested, there must
                 // not be any more pages
                 if (grid.count() < grid.limit) return false;
@@ -194,12 +235,12 @@ angular.module('egGridMod',
                 return grid.totalCount > (grid.offset + grid.count());
             }
 
-            grid.incrementPage = function() {
+            $scope.incrementPage = function() {
                 grid.offset += grid.limit;
                 grid.collect();
             }
 
-            grid.decrementPage = function() {
+            $scope.decrementPage = function() {
                 if (grid.offset < grid.limit) {
                     grid.offset = 0;
                 } else {
@@ -210,7 +251,7 @@ angular.module('egGridMod',
 
             // number of items loaded for the current page of results
             grid.count = function() {
-                return grid.items.length;
+                return $scope.items.length;
             }
 
             // returns the unique identifier value for the provided item
@@ -227,62 +268,62 @@ angular.module('egGridMod',
             }
 
             // fires the action handler function
-            grid.actionLauncher = function(action) {
+            $scope.actionLauncher = function(action) {
                 action.handler(grid.getSelectedItems());
             }
 
             // returns the list of selected item objects
             grid.getSelectedItems = function() {
-                return grid.items.filter(
+                return $scope.items.filter(
                     function(item) {
-                        return Boolean(grid.selected[grid.indexValue(item)]);
+                        return Boolean($scope.selected[grid.indexValue(item)]);
                     }
                 );
             }
 
             // selects one row after deselecting all of the others
             grid.selectOneItem = function(index) {
-                grid.selected = {};
-                grid.selected[index] = true;
+                $scope.selected = {};
+                $scope.selected[index] = true;
             }
 
             // selects or deselects an item, without affecting the others.
             // returns true if the item is selected; false if de-selected.
             grid.toggleSelectOneItem = function(index) {
-                if (grid.selected[index]) {
-                    delete grid.selected[index];
+                if ($scope.selected[index]) {
+                    delete $scope.selected[index];
                     return false;
                 } else {
-                    return grid.selected[index] = true;
+                    return $scope.selected[index] = true;
                 }
             }
 
             grid.selectAllItems = function() {
-                angular.forEach(grid.items, function(item) {
-                    grid.selected[grid.indexValue(item)] = true
+                angular.forEach($scope.items, function(item) {
+                    $scope.selected[grid.indexValue(item)] = true
                 });
             }
 
             // if all are selected, deselect all, otherwise select all
-            grid.toggleSelectAllItems = function() {
-                if (Object.keys(grid.selected).length == grid.items.length) {
-                    grid.selected = {};
+            $scope.toggleSelectAllItems = function() {
+                if (Object.keys($scope.selected).length == $scope.items.length) {
+                    $scope.selected = {};
                 } else {
                     grid.selectAllItems();
                 }
             }
 
-            // inform the data provider that a selection has been made
             grid.informSelection = function() {
                 var items = [];
-                angular.forEach(Object.keys(grid.selected),
+                angular.forEach(Object.keys($scope.selected),
                     function(index) {
-                        items.push(grid.items[grid.indexOf(index)]);
+                        items.push($scope.items[grid.indexOf(index)]);
                     }
                 );
                 grid.dataProvider.select(items);
             }
 
+
             // returns true if item1 appears in the list before item2;
             // false otherwise.  this is slightly more efficient that
             // finding the position of each then comparing them.
@@ -292,8 +333,8 @@ angular.module('egGridMod',
                 var idx2 = grid.indexValue(itemOrIndex2);
 
                 // use for() for early exit
-                for (var i = 0; i < grid.items.length; i++) {
-                    var idx = grid.indexValue(grid.items[i]);
+                for (var i = 0; i < $scope.items.length; i++) {
+                    var idx = grid.indexValue($scope.items[i]);
                     if (idx == idx1) return true;
                     if (idx == idx2) return false;
                 }
@@ -303,8 +344,8 @@ angular.module('egGridMod',
             // 0-based position of item in the current data set
             grid.indexOf = function(item) {
                 var idx = grid.indexValue(item);
-                for (var i = 0; i < grid.items.length; i++) {
-                    if (grid.indexValue(grid.items[i]) == idx)
+                for (var i = 0; i < $scope.items.length; i++) {
+                    if (grid.indexValue($scope.items[i]) == idx)
                         return i;
                 }
                 return -1;
@@ -316,9 +357,12 @@ angular.module('egGridMod',
                 if (column.flex < 1)
                     column.flex = 1;
             }
+            $scope.modifyColumnFlex = function(col, val) {
+                grid.modifyColumnFlex(col, val);
+            }
 
             // handles click, control-click, and shift-click
-            grid.handleRowClick = function($event, item) {
+            $scope.handleRowClick = function($event, item) {
                 var index = grid.indexValue(item);
 
                 if ($event.ctrlKey || $event.metaKey /* mac command */) {
@@ -349,10 +393,10 @@ angular.module('egGridMod',
                         // currently selected items
                         while (true) {
                             startPos += ascending ? 1 : -1;
-                            var curItem = grid.items[startPos];
+                            var curItem = $scope.items[startPos];
                             if (!curItem) break;
                             var curIdx = grid.indexValue(curItem);
-                            grid.selected[curIdx] = true;
+                            $scope.selected[curIdx] = true;
                             if (curIdx == index) break; // all done
                         }
                     }
@@ -389,7 +433,7 @@ angular.module('egGridMod',
 
             // builds a sort expression using a single column, 
             // toggling between ascending and descending sort.
-            grid.quickSort = function(col_name) {
+            $scope.quickSort = function(col_name) {
                 var sort = grid.dataProvider.sort;
                 if (sort && sort.length &&
                     sort[0] == col_name) {
@@ -405,9 +449,9 @@ angular.module('egGridMod',
             }
 
             // show / hide the grid configuration row
-            grid.toggleConfDisplay = function() {
-                if (grid.showGridConf) {
-                    grid.showGridConf = false;
+            $scope.toggleConfDisplay = function() {
+                if ($scope.showGridConf) {
+                    $scope.showGridConf = false;
                     if (grid.columnsProvider.hasSortableColumn()) {
                         // only refresh the grid if the user has the
                         // ability to modify the sort priorities.
@@ -416,7 +460,7 @@ angular.module('egGridMod',
                         grid.collect();
                     }
                 } else {
-                    grid.showGridConf = true;
+                    $scope.showGridConf = true;
                 }
             }
 
@@ -461,21 +505,21 @@ angular.module('egGridMod',
 
             // sets the download file name and inserts the current CSV
             // into a Blob URL for browser download.
-            grid.generateCSVExportURL = function() {
+            $scope.generateCSVExportURL = function() {
 
                 // let the file name describe the grid
-                grid.csvExportFileName = 
-                    (grid.mainLabel || grid.persistKey || 'eg_grid_data')
-                    .replace(/\s+/g, '_') + '_' + grid.page();
+                $scope.csvExportFileName = 
+                    ($scope.mainLabel || grid.persistKey || 'eg_grid_data')
+                    .replace(/\s+/g, '_') + '_' + $scope.page();
 
                 // toss the CSV into a Blob and update the export URL
                 var csv = grid.generateCSV();
                 var blob = new Blob([csv], {type : 'text/plain'});
-                grid.csvExportURL = 
+                $scope.csvExportURL = 
                     ($window.URL || $window.webkitURL).createObjectURL(blob);
             }
 
-            grid.printCSV = function() {
+            $scope.printCSV = function() {
                 egPrintStore.print('text/plain', grid.generateCSV())
                 .then(function() { console.debug('print complete') });
             }
@@ -488,7 +532,7 @@ angular.module('egGridMod',
                 // columns
                 angular.forEach(grid.columnsProvider.columns,
                     function(col) {
-                        if (!grid.columnsProvider.visible[col.name]) return;
+                        if (!col.visible) return;
                         csvStr += grid.csvDatum(col.label);
                         csvStr += ',';
                     }
@@ -497,10 +541,10 @@ angular.module('egGridMod',
                 csvStr = csvStr.replace(/,$/,'\n');
 
                 // items
-                angular.forEach(grid.items, function(item) {
+                angular.forEach($scope.items, function(item) {
                     angular.forEach(grid.columnsProvider.columns, 
                         function(col) {
-                            if (!grid.columnsProvider.visible[col.name]) return;
+                            if (!col.visible) return;
                             // bare value
                             var val = grid.dataProvider.itemFieldValue(item, col);
                             // filtered value (dates, etc.)
@@ -515,16 +559,18 @@ angular.module('egGridMod',
                 return csvStr;
             }
 
+            $scope.collect = function() { grid.collect() }
+
             // asks the dataProvider for a page of data
             grid.collect = function() {
-                grid.items = [];
-                grid.selected = {};
+                $scope.items = [];
+                $scope.selected = {};
                 grid.dataProvider.get(grid.offset, grid.limit)
                 .then(null, null, function(item) {
                     if (item) {
                         if (grid.onItemRetrieved)
                             grid.onItemRetrieved(item);
-                        grid.items.push(item)
+                        $scope.items.push(item)
                     }
                 });
             }
@@ -570,6 +616,7 @@ angular.module('egGridMod',
                 }
             );
             egGridCtrl.columnsProvider.add(scope);
+            scope.$destroy();
         }
     };
 })
@@ -589,10 +636,11 @@ angular.module('egGridMod',
         },
         template : '<div></div>', // NOOP template
         link : function(scope, element, attrs, egGridCtrl) {
-            egGridCtrl.actions.push({
+            egGridCtrl.addAction({
                 label : scope.label,
                 handler : scope.handler
             });
+            scope.$destroy();
         }
     };
 })
@@ -602,7 +650,6 @@ angular.module('egGridMod',
     function ColumnsProvider(args) {
         var cols = this;
         cols.columns = [];
-        cols.visible = {};
         cols.idlClass = args.idlClass;
         cols.defaultToHidden = args.defaultToHidden;
         cols.defaultToNoSort = args.defaultToNoSort;
@@ -619,12 +666,14 @@ angular.module('egGridMod',
 
         cols.showAllColumns = function() {
             angular.forEach(cols.columns, function(column) {
-                cols.visible[column.name] = true;
+                column.visible = true;
             });
         }
 
         cols.hideAllColumns = function() {
-            cols.visible = {};
+            angular.forEach(cols.columns, function(col) {
+                delete col.visible;
+            });
         }
 
         cols.indexOf = function(name) {
@@ -691,7 +740,7 @@ angular.module('egGridMod',
             if (!column.path) column.path = column.name;
 
             if (column.visible || (!cols.defaultToHidden && !column.hidden))
-                cols.visible[column.name] = true;
+                column.visible = true;
 
             if (column.sortable || (!cols.defaultToNoSort && !column.nonsortable))
                 column.sortable = true;
@@ -894,8 +943,7 @@ angular.module('egGridMod',
                     var queryFields = {}
                     angular.forEach(provider.columnsProvider.columns, 
                         function(col) {
-                            if (col.required ||
-                                    provider.columnsProvider.visible[col.name]) 
+                            if (col.required || col.visible)
                                 queryFields[col.name] = col.path;
                         }
                     );
@@ -1051,6 +1099,7 @@ angular.module('egGridMod',
                 divider : scope.divider,
                 handlerData : scope.handlerData
             });
+            scope.$destroy();
         }
     };
 })
@@ -1069,7 +1118,7 @@ angular.module('egGridMod',
         switch(column.datatype) {                                                
             case 'bool':                                                       
                 // Browser will translate true/false for us                    
-                return Boolean(value == 't');                                  
+                return ''+Boolean(value == 't');                                  
             case 'timestamp':                                                  
                 // canned angular date filter FTW                              
                 return $filter('date')(value, 'shortDate');