<div class="eg-grid-primary-label">{{mainLabel}}</div>
- <div class="btn-group eg-grid-menuiitem" ng-if="menuLabel" dropdown>
+ <div class="btn-group eg-grid-menuiitem"
+ is-open="gridMenuIsOpen" ng-if="menuLabel" dropdown>
<button type="button" class="btn btn-default dropdown-toggle">
{{menuLabel}}<span class="caret"></span>
</button>
</button>
<!-- actions drop-down menu -->
- <div class="btn-group" ng-if="actions.length" dropdown>
+ <div class="btn-group" ng-if="actions.length"
+ is-open="gridActionsIsOpen" dropdown>
<button type="button" class="btn btn-default dropdown-toggle"
ng-class="{disabled : false}">
[% l('Actions') %] <span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right">
<li ng-class="{disabled : false}" ng-repeat="action in actions">
- <a href="" ng-click="actionLauncher(action)">{{action.label}}</a>
+ <a href ng-click="actionLauncher(action)">{{action.label}}</a>
</li>
</ul>
</div>
- <div class="btn-group" dropdown>
+ <div class="btn-group" dropdown is-open="gridRowCountIsOpen">
<button type="button" title="[% ('Select Row Count') %]"
class="btn btn-default dropdown-toggle">
[% l('Rows [_1]', '{{limit()}}') %]
</button>
<ul class="dropdown-menu">
<li ng-repeat="t in [5,10,25,50,100]">
- <a href dropdown-toggle ng-click='offset(0);limit(t);collect()'>
+ <a href ng-click='offset(0);limit(t);collect()'>
{{t}}
</a>
</li>
</ul>
</div>
- <div class="btn-group" dropdown>
+ <div class="btn-group" dropdown is-open="gridPageSelectIsOpen">
<button type="button" title="[% ('Select Page') %]"
class="btn btn-default dropdown-toggle">
[% l('Page [_1]', '{{page()}}') %]
ng-click="$event.stopPropagation()"/>
<span class="input-group-btn">
<button class="btn btn-default" type="button"
- ng-click="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 dropdown-toggle ng-click='goToPage(t)'>{{t}}</a>
+ <a href ng-click='goToPage(t);gridPageSelectIsOpen=false;'>{{t}}</a>
</li>
</ul>
</div>
- <div class="btn-group" dropdown>
- <button type="button"
- class="btn btn-default dropdown-toggle">
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu pull-right">
- <li><a href dropdown-toggle ng-click="toggleConfDisplay()">
- <span class="glyphicon glyphicon-wrench"></span>
- [% l('Configure Columns') %]
- </a></li>
- <li><a href dropdown-toggle ng-click="saveConfig()">
- <span class="glyphicon glyphicon-floppy-save"></span>
- [% l('Save Columns') %]
- </a></li>
- <li><a href dropdown-toggle ng-click="showAllColumns()">
- <span class="glyphicon glyphicon-resize-full"></span>
- [% l('Show All Columns') %]
- </a></li>
- <li><a href dropdown-toggle ng-click="hideAllColumns()">
- <span class="glyphicon glyphicon-resize-small"></span>
- [% l('Hide All Columns') %]
- </a></li>
- <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 dropdown-toggle 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 columns">
- <a href dropdown-toggle ng-click="col.visible = !col.visible">
- <span ng-if="col.visible"
- class="label label-success">✓</span>
- <span ng-if="!col.visible"
- class="label label-warning">✗</span>
- <span>{{col.label}}</span>
- </a>
- </li>
- </ul>
+ <div class="btn-group" dropdown is-open="gridColumnPickerIsOpen">
+ <button type="button"
+ class="btn btn-default dropdown-toggle">
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu pull-right eg-grid-column-picker">
+ <li><a href ng-click="toggleConfDisplay()">
+ <span class="glyphicon glyphicon-wrench"></span>
+ [% l('Configure Columns') %]
+ </a></li>
+ <li><a href ng-click="saveConfig()">
+ <span class="glyphicon glyphicon-floppy-save"></span>
+ [% l('Save Columns') %]
+ </a></li>
+ <li><a href ng-click="showAllColumns()">
+ <span class="glyphicon glyphicon-resize-full"></span>
+ [% l('Show All Columns') %]
+ </a></li>
+ <li><a href ng-click="hideAllColumns()">
+ <span class="glyphicon glyphicon-resize-small"></span>
+ [% l('Hide All Columns') %]
+ </a></li>
+ <li><a href ng-click="resetColumns()">
+ <span class="glyphicon glyphicon-refresh"></span>
+ [% l('Reset Columns') %]
+ </a></li>
+ <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="printCSV()">
+ <span class="glyphicon glyphicon-print"></span>
+ [% l('Print CSV') %]
+ </a></li>
+ <li role="presentation" class="divider"></li>
+ <li ng-repeat="col in columns">
+ <a href ng-click="toggleColumnVisibility(col)">
+ <span ng-if="col.visible"
+ class="label label-success">✓</span>
+ <span ng-if="!col.visible"
+ class="label label-warning">✗</span>
+ <span>{{col.label}}</span>
+ </a>
+ </li>
+ </ul>
</div>
</div>
</div>
},
controller : [
- '$scope','$q','egCore','egGridFlatDataProvider',
+ '$scope','$q','egCore','egGridFlatDataProvider','$location',
'egGridColumnsProvider','$filter','$window','$sce',
- function($scope, $q , egCore, egGridFlatDataProvider,
+ function($scope, $q , egCore, egGridFlatDataProvider , $location,
egGridColumnsProvider , $filter , $window , $sce) {
var grid = this;
grid.addMenuItem = function(item) {
$scope.menuItems.push(item);
var handler = item.handler;
- if (handler) {
- item.handler = function() {
- handler(item, item.handlerData,
- grid.getSelectedItems());
+ item.handler = function() {
+ $scope.gridMenuIsOpen = false; // close menu
+ if (handler) {
+ handler(item,
+ item.handlerData, grid.getSelectedItems());
}
}
}
$scope.showAllColumns = function() {
grid.columnsProvider.showAllColumns();
}
+
$scope.hideAllColumns = function() {
grid.columnsProvider.hideAllColumns();
}
+ $scope.toggleColumnVisibility = function(col) {
+ $scope.gridColumnPickerIsOpen = false;
+ col.visible = !col.visible;
+
+ // egGridFlatDataProvider only retrieves data to be
+ // displayed. When column visibility changes, it's
+ // necessary to fetch the newly visible column data.
+ if (grid.selfManagedData) grid.collect();
+ }
+
if (!grid.indexField && grid.idlClass)
grid.indexField = egCore.idl.classes[grid.idlClass].pkey;
});
}
+ // remove the stored column configuration preferenc, then recover
+ // the column visibility information from the initial page load.
+ $scope.resetColumns = function() {
+ $scope.gridColumnPickerIsOpen = false;
+ egCore.hatch.removeItem('eg.grid.' + grid.persistKey)
+ .then(function() {
+ grid.columnsProvider.reset();
+ if (grid.selfManagedData) grid.collect();
+ });
+ }
+
+
// save the columns configuration (position, sort, width) to
// eg.grid.<persist-key>
$scope.saveConfig = function() {
+ $scope.gridColumnPickerIsOpen = false;
+
if (!grid.persistKey) {
console.warn(
"Cannot save settings without a grid persist-key");
// fires the action handler function
$scope.actionLauncher = function(action) {
+ $scope.gridActionsIsOpen = false;
action.handler(grid.getSelectedItems());
}
} else {
$scope.showGridConf = true;
}
+
+ $scope.gridColumnPickerIsOpen = false;
}
// called when a dragged column is dropped onto itself
// sets the download file name and inserts the current CSV
// into a Blob URL for browser download.
$scope.generateCSVExportURL = function() {
+ $scope.gridColumnPickerIsOpen = false;
// let the file name describe the grid
$scope.csvExportFileName =
}
$scope.printCSV = function() {
+ $scope.gridColumnPickerIsOpen = false;
egCore.hatch.print('text/plain', grid.generateCSV())
.then(function() { console.debug('print complete') });
}
// asks the dataProvider for a page of data
grid.collect = function() {
if (grid.collecting) return; // avoid parallel collects()
+
+ // ensure all of our dropdowns are closed
+ $scope.gridColumnPickerIsOpen = false;
+ $scope.gridRowCountIsOpen = false;
+ $scope.gridPageSelectIsOpen = false;
+
$scope.items = [];
$scope.selected = {};
grid.collecting = true;
if (tmpl && !tmpl.match(/^\s*$/))
scope.template = tmpl
- 'datatype', // for non-IDL fields, can be used to apply filters
egGridCtrl.columnsProvider.add(scope);
scope.$destroy();
}
function ColumnsProvider(args) {
var cols = this;
cols.columns = [];
+ cols.stockVisible = [];
cols.idlClass = args.idlClass;
cols.defaultToHidden = args.defaultToHidden;
cols.defaultToNoSort = args.defaultToNoSort;
cols.defaultToNoMultiSort = args.defaultToNoMultiSort;
+ // resets column width, visibility, and sort behavior
+ // Visibility resets to the visibility settings defined in the
+ // template (i.e. the original egGridField values).
+ cols.reset = function() {
+ angular.forEach(cols.columns, function(col) {
+ col.flex = 2;
+ col.sort = 0;
+ if (cols.stockVisible.indexOf(col.name) > -1) {
+ col.visible = true;
+ } else {
+ col.visible = false;
+ }
+ });
+ }
+
// returns true if any columns are sortable
cols.hasSortableColumn = function() {
return cols.columns.filter(
}
cols.showAllColumns = function() {
+ $scope.gridColumnPickerIsOpen = false;
angular.forEach(cols.columns, function(column) {
column.visible = true;
});
+ if (grid.selfManagedData) grid.collect();
}
cols.hideAllColumns = function() {
+ $scope.gridColumnPickerIsOpen = false;
angular.forEach(cols.columns, function(col) {
delete col.visible;
});
+ // note: no need to fetch new data if no columns are visible
}
cols.indexOf = function(name) {
);
}
- // Add a column to the columns collection.
- // Columns may come from a slim eg-columns-field or
- // directly from the IDL.
- cols.add = function(colSpec, fromIDL) {
+ // if a column definition has a path with a wildcard, create
+ // columns for all non-virtual fields at the specified
+ // position in the path.
+ cols.expandPath = function(colSpec) {
+
+ var dotpath = colSpec.path.replace(/\.\*$/,'');
+ var class_obj = egCore.idl.classes[cols.idlClass];
+ var path_parts = dotpath.split(/\./);
+
+ // find the IDL class definition for the last element in the
+ // path before the .*
+ var class_obj;
+ for (var path_idx in path_parts) {
+ var part = path_parts[path_idx];
+ var idl_field = class_obj.field_map[part];
- var column = {
+ // unless we're at the end of the list, this field should
+ // link to another class.
+ if (idl_field && idl_field['class'] && (
+ idl_field.datatype == 'link' ||
+ idl_field.datatype == 'org_unit')) {
+ class_obj = egCore.idl.classes[idl_field['class']];
+ } else {
+ if (path_idx < (path_parts.length - 1)) {
+ // we ran out of classes to hop through before
+ // we ran out of path components
+ console.error("egGrid: invalid IDL path: " + path);
+ }
+ }
+ }
+
+ if (class_obj) {
+ angular.forEach(class_obj.fields, function(field) {
+
+ // Only show wildcard fields where we have data to show
+ // Virtual and un-fleshed links will not have any data.
+ if (field.virtual || (
+ field.datatype == 'link' || field.datatype == 'org_unit'))
+ return;
+
+ var col = cols.cloneFromScope(colSpec);
+ col.path = dotpath + '.' + field.name;
+ cols.add(col, null, true);
+ });
+
+ } else {
+ console.error(
+ "egGrid: wildcard path does not resolve to an object: "
+ + dotpath);
+ }
+ }
+
+ // angular.clone(scopeObject) is not permittable. Manually copy
+ // the fields over that we need (so the scope object can go away).
+ cols.cloneFromScope = function(colSpec) {
+ return {
name : colSpec.name,
label : colSpec.label,
path : colSpec.path,
multisortable : colSpec.multisortable,
nonmultisortable : colSpec.nonmultisortable
};
+ }
+
+
+ // Add a column to the columns collection.
+ // Columns may come from a slim eg-columns-field or
+ // directly from the IDL.
+ cols.add = function(colSpec, fromIDL, fromExpand) {
+
+ // First added column with the specified path takes precedence.
+ // This allows for specific definitions followed by wildcard
+ // definitions. If a match is found, back out.
+ if (cols.columns.filter(function(c) {
+ return (c.path == colSpec.path) })[0]) {
+ console.debug('skipping column ' + colSpec.path);
+ return;
+ }
+
+ var column = fromExpand ? colSpec : cols.cloneFromScope(colSpec);
+
+ if (column.path && column.path.match(/\*/))
+ return cols.expandPath(colSpec);
if (!column.name) column.name = column.path;
if (!column.path) column.path = column.name;
cols.columns.push(column);
+ // Track which columns are visible by default in case we
+ // need to reset column visibility
+ if (column.visible)
+ cols.stockVisible.push(column.name);
+
if (fromIDL) return;
if (!cols.idlClass) return; // ad-hoc object
// lookup the matching IDL field
- var idl_field = cols.idlFieldFromPath(column.path);
+ var idl_info = cols.idlFieldFromPath(column.path);
- if (!idl_field) {
+ if (!idl_info) {
// column is not represented within the IDL
column.adhoc = true;
return;
}
- column.datatype = idl_field.datatype;
+ column.datatype = idl_info.idl_field.datatype;
if (!column.label) {
- column.label = idl_field.label || column.name;
+ column.label = idl_info.idl_field.label || column.name;
+ if (fromExpand) {
+ var label =
+ idl_info.idl_class.label || idl_info.idl_class.name;
+ column.label = label + '::' + column.label;
+ }
}
},
var class_obj = egCore.idl.classes[cols.idlClass];
var path_parts = dotpath.split(/\./);
- // for() == early exit
var idl_field;
for (var path_idx in path_parts) {
var part = path_parts[path_idx];
-
- // find the field object matching the path component
- for (var field_idx in class_obj.fields) {
- if (class_obj.fields[field_idx].name == part) {
- idl_field = class_obj.fields[field_idx];
- break;
- }
- }
-
- // unless we're at the end of the list, this field should
- // link to another class.
+ idl_field = class_obj.field_map[part];
if (idl_field && idl_field['class'] && (
idl_field.datatype == 'link' ||
}
}
- return idl_field;
+ if (!idl_field) return null;
+
+ return {
+ idl_field :idl_field,
+ idl_class : class_obj
+ };
}
}