// column
features : '@',
+ // optional: object containing function to conditionally apply
+ // class to each row.
+ rowClass : '=',
+
+ // optional: object that enables status icon field and contains
+ // function to handle what status icons should exist and why.
+ statusColumn : '=',
+
// optional primary grid label
mainLabel : '@',
menuLabel : '@',
dateformat : '@', // optional: passed down to egGridValueFilter
+ datecontext: '@', // optional: passed down to egGridValueFilter to choose TZ
+ datefilter: '@', // optional: passed down to egGridValueFilter to choose specialized date filters
+ dateonlyinterval: '@', // optional: passed down to egGridValueFilter to choose a "better" format
// Hash of control functions.
//
templateUrl : '/eg/staff/share/t_autogrid',
link : function(scope, element, attrs) {
- // 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.
-
- // load auto fields after eg-grid-field's so they are not clobbered
- scope.handleAutoFields();
- scope.collect();
-
- scope.grid_element = element;
- $(element)
- .find('.eg-grid-content-body')
- .bind('contextmenu', scope.showActionContextMenu);
+
+ // Give the grid config loading steps time to fetch the
+ // workstation setting and apply columns before loading data.
+ var loadPromise = scope.configLoadPromise || $q.when();
+ loadPromise.then(function() {
+
+ // load auto fields after eg-grid-field's so they are not clobbered
+ scope.handleAutoFields();
+ scope.collect();
+
+ scope.grid_element = element;
+
+ if(!attrs.id){
+ $(element).attr('id', attrs.persistKey);
+ }
+
+ $(element)
+ .find('.eg-grid-content-body')
+ .bind('contextmenu', scope.showActionContextMenu);
+ });
},
controller : [
'$scope','$q','egCore','egGridFlatDataProvider','$location',
'egGridColumnsProvider','$filter','$window','$sce','$timeout',
+ 'egProgressDialog','$uibModal','egConfirmDialog','egStrings',
function($scope, $q , egCore, egGridFlatDataProvider , $location,
- egGridColumnsProvider , $filter , $window , $sce , $timeout) {
+ egGridColumnsProvider , $filter , $window , $sce , $timeout,
+ egProgressDialog, $uibModal , egConfirmDialog , egStrings) {
var grid = this;
var stored_limit = 0;
if ($scope.showPagination) {
+ // localStorage of grid limits is deprecated. Limits
+ // are now stored along with the columns configuration.
+ // Values found in localStorage will be migrated upon
+ // config save.
if (grid.persistKey) {
var stored_limit = Number(
egCore.hatch.getLocalItem('eg.grid.' + grid.persistKey + '.limit')
grid.columnsProvider = egGridColumnsProvider.instance({
idlClass : grid.idlClass,
+ clientSort : (features.indexOf('clientsort') > -1 && features.indexOf('-clientsort') == -1),
defaultToHidden : (features.indexOf('-display') > -1),
defaultToNoSort : (features.indexOf('-sort') > -1),
defaultToNoMultiSort : (features.indexOf('-multisort') > -1),
- defaultDateFormat : $scope.dateformat
+ defaultDateFormat : $scope.dateformat,
+ defaultDateContext : $scope.datecontext,
+ defaultDateFilter : $scope.datefilter,
+ defaultDateOnlyInterval : $scope.dateonlyinterval
});
$scope.canMultiSelect = (features.indexOf('-multiselect') == -1);
});
}
+ // make grid ref available in get() to set totalCount, if known.
+ // this allows us disable the 'next' paging button correctly
+ grid.dataProvider.grid = grid;
+
+ grid.dataProvider.columnsProvider = grid.columnsProvider;
+
$scope.itemFieldValue = grid.dataProvider.itemFieldValue;
$scope.indexValue = function(item) {
return grid.indexValue(item)
grid.applyControlFunctions();
- grid.loadConfig().then(function() {
+ $scope.configLoadPromise = grid.loadConfig().then(function() {
// link columns to scope after loadConfig(), since it
// replaces the columns array.
$scope.columns = grid.columnsProvider.columns;
return grid.getSelectedItems()
}
+ controls.selectItemsByValue = function(c,v) {
+ return grid.selectItemsByValue(c,v)
+ }
+
controls.allItems = function() {
return $scope.items;
}
grid.collect();
}
+ controls.prepend = function(limit) {
+ grid.prepend(limit);
+ }
+
controls.setLimit = function(limit,forget) {
- if (!forget && grid.persistKey)
- egCore.hatch.setLocalItem('eg.grid.' + grid.persistKey + '.limit', limit);
grid.limit = limit;
+ if (!forget && grid.persistKey) {
+ $scope.saveConfig();
+ }
}
controls.getLimit = function() {
return grid.limit;
}
grid.dataProvider.refresh = controls.refresh;
+ grid.dataProvider.prepend = controls.prepend;
grid.controls = controls;
}
}
// only store information about visible columns.
- var conf = grid.columnsProvider.columns.filter(
+ var cols = grid.columnsProvider.columns.filter(
function(col) {return Boolean(col.visible) });
// now scrunch the data down to just the needed info
- conf = conf.map(function(col) {
+ cols = cols.map(function(col) {
var c = {name : col.name}
// Apart from the name, only store non-default values.
// No need to store col.visible, since that's implicit
return c;
});
+ var conf = {
+ version: 2,
+ limit: grid.limit,
+ columns: cols
+ };
+
egCore.hatch.setItem('eg.grid.' + grid.persistKey, conf)
.then(function() {
// Save operation performed from the grid configuration UI.
// Hide the configuration UI and re-draw w/ sort applied
if ($scope.showGridConf)
$scope.toggleConfDisplay();
+
+ // Once a version-2 grid config is saved (with limit
+ // included) we can remove the local limit pref.
+ egCore.hatch.removeLocalItem(
+ 'eg.grid.' + grid.persistKey + '.limit');
});
}
var columns = grid.columnsProvider.columns;
var new_cols = [];
- angular.forEach(conf, function(col) {
+ if (Array.isArray(conf)) {
+ console.debug(
+ 'upgrading version 1 grid config to version 2');
+ conf = {
+ version : 2,
+ columns : conf
+ };
+ }
+
+ if (conf.limit) {
+ grid.limit = Number(conf.limit);
+ }
+
+ angular.forEach(conf.columns, function(col) {
var grid_col = columns.filter(
function(c) {return c.name == col.name})[0];
// configuration are marked as non-visible and
// appended to the end of the new list of columns.
angular.forEach(columns, function(col) {
- var found = conf.filter(
+ var found = conf.columns.filter(
function(c) {return (c.name == col.name)})[0];
if (!found) {
col.visible = false;
$scope.limit = function(l) {
if (angular.isNumber(l)) {
- if (grid.persistKey)
- egCore.hatch.setLocalItem('eg.grid.' + grid.persistKey + '.limit', l);
grid.limit = l;
+ if (grid.persistKey) {
+ $scope.saveConfig();
+ }
}
return grid.limit
}
if (!$scope.menu_dom) $scope.menu_dom = $($scope.grid_element).find('.grid-action-dropdown')[0];
if (!$scope.action_context_parent) $scope.action_context_parent = $($scope.menu_dom).parent();
- if (!grid.getSelectedItems().length) // Nothing selected, fire the click event
- $event.target.click();
+ // we need the the row that got right-clicked...
+ var e = $event.target; // the DOM element
+ var s = undefined; // the angular scope for that element
+ while(e){ // searching for the row
+ // abort & use the browser default context menu for links (lp1669856):
+ if(e.tagName.toLowerCase() === 'a' && e.href){ return true; }
+ s = angular.element(e).scope();
+ if(s.hasOwnProperty('item')){ break; }
+ e = e.parentElement;
+ }
+ // select the right-clicked row if it is not already selected (lp1776557):
+ if(!$scope.selected[grid.indexValue(s.item)]){ $event.target.click(); }
if (!$scope.action_context_showing) {
$scope.action_context_width = $($scope.menu_dom).css('width');
$scope.selected[index] = true;
}
+ // selects items by a column value, first clearing selected list.
+ // we overwrite the object so that we can watch $scope.selected
+ grid.selectItemsByValue = function(column, value) {
+ $scope.selected = {};
+ angular.forEach($scope.items, function(item) {
+ var col_value;
+ if (angular.isFunction(item[column]))
+ col_value = item[column]();
+ else
+ col_value = item[column];
+
+ if (value == col_value) $scope.selected[grid.indexValue(item)] = true
+ });
+ }
+
// selects or deselects an item, without affecting the others.
// returns true if the item is selected; false if de-selected.
// we overwrite the object so that we can watch $scope.selected
column.flex = 1;
}
$scope.modifyColumnFlex = function(col, val) {
- $scope.lastModColumn = col.name;
+ $scope.lastModColumn = col;
grid.modifyColumnFlex(col, val);
}
+ $scope.isLastModifiedColumn = function(col) {
+ if ($scope.lastModColumn)
+ return $scope.lastModColumn === col;
+ return false;
+ }
+
grid.modifyColumnPos = function(col, diff) {
var srcIdx, targetIdx;
angular.forEach(grid.columnsProvider.columns,
if (column.visible) lastVisible = idx;
}
);
- targetIdx = lastVisible + 1;
+
+ // When moving a column (down) causes one or more
+ // visible columns to shuffle forward, our column
+ // moves into the slot of the last visible column.
+ // Otherwise, put it into the slot directly following
+ // the last visible column.
+ targetIdx =
+ srcIdx <= lastVisible ? lastVisible : lastVisible + 1;
}
// Splice column out of old position, insert at new position.
}
$scope.modifyColumnPos = function(col, diff) {
- $scope.lastModColumn = col.name;
+ $scope.lastModColumn = col;
return grid.modifyColumnPos(col, diff);
}
return str;
}
- // sets the download file name and inserts the current CSV
- // into a Blob URL for browser download.
- $scope.generateCSVExportURL = function() {
+ /** Export the full data set as CSV.
+ * Flow of events:
+ * 1. User clicks the 'download csv' link
+ * 2. All grid data is retrieved asychronously
+ * 3. Once all data is all present and CSV-ized, the download
+ * attributes are linked to the href.
+ * 4. The href .click() action is prgrammatically fired again,
+ * telling the browser to download the data, now that the
+ * data is available for download.
+ * 5 Once downloaded, the href attributes are reset.
+ */
+ grid.csvExportInProgress = false;
+ $scope.generateCSVExportURL = function($event) {
+
+ if (grid.csvExportInProgress) {
+ // This is secondary href click handler. Give the
+ // browser a moment to start the download, then reset
+ // the CSV download attributes / state.
+ $timeout(
+ function() {
+ $scope.csvExportURL = '';
+ $scope.csvExportFileName = '';
+ grid.csvExportInProgress = false;
+ }, 500
+ );
+ return;
+ }
+
+ grid.csvExportInProgress = true;
$scope.gridColumnPickerIsOpen = false;
// let the file name describe the grid
.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'});
- $scope.csvExportURL =
- ($window.URL || $window.webkitURL).createObjectURL(blob);
+ grid.generateCSV().then(function(csv) {
+ var blob = new Blob([csv], {type : 'text/plain'});
+ $scope.csvExportURL =
+ ($window.URL || $window.webkitURL).createObjectURL(blob);
+
+ // Fire the 2nd click event now that the browser has
+ // information on how to download the CSV file.
+ $timeout(function() {$event.target.click()});
+ });
}
+ /*
+ * TODO: does this serve any purpose given we can
+ * print formatted HTML? If so, generateCSV() now
+ * returns a promise, needs light refactoring...
$scope.printCSV = function() {
$scope.gridColumnPickerIsOpen = false;
egCore.print.print({
content_type : 'text/plain'
});
}
+ */
+
+ // Given a row item and column definition, extract the
+ // text content for printing. Templated columns must be
+ // processed and parsed as HTML, then boiled down to their
+ // text content.
+ grid.getItemTextContent = function(item, col) {
+ var val;
+ if (col.template) {
+ val = $scope.translateCellTemplate(col, item);
+ if (val) {
+ var node = new DOMParser()
+ .parseFromString(val, 'text/html');
+ val = $(node).text();
+ }
+ } else {
+ val = grid.dataProvider.itemFieldValue(item, col);
+ val = $filter('egGridValueFilter')(val, col, item);
+ }
+ return val;
+ }
+
+ $scope.getHtmlTooltip = function(col, item) {
+ return grid.getItemTextContent(item, col);
+ }
+
+ /**
+ * Fetches all grid data and transates each item into a simple
+ * key-value pair of column name => text-value.
+ * Included in the response for convenience is the list of
+ * currently visible column definitions.
+ * TODO: currently fetches a maximum of 10k rows. Does this
+ * need to be configurable?
+ */
+ grid.getAllItemsAsText = function() {
+ var text_items = [];
+
+ // we don't know the total number of rows we're about
+ // to retrieve, but we can indicate the number retrieved
+ // so far as each item arrives.
+ egProgressDialog.open({value : 0});
+
+ var visible_cols = grid.columnsProvider.columns.filter(
+ function(c) { return c.visible });
+
+ return grid.dataProvider.get(0, 10000).then(
+ function() {
+ return {items : text_items, columns : visible_cols};
+ },
+ null,
+ function(item) {
+ egProgressDialog.increment();
+ var text_item = {};
+ angular.forEach(visible_cols, function(col) {
+ text_item[col.name] =
+ grid.getItemTextContent(item, col);
+ });
+ text_items.push(text_item);
+ }
+ ).finally(egProgressDialog.close);
+ }
+
+ // Fetch "all" of the grid data, translate it into print-friendly
+ // text, and send it to the printer service.
+ $scope.printHTML = function() {
+ $scope.gridColumnPickerIsOpen = false;
+ return grid.getAllItemsAsText().then(function(text_items) {
+ return egCore.print.print({
+ template : 'grid_html',
+ scope : text_items
+ });
+ });
+ }
+
+ $scope.showColumnDialog = function() {
+ return $uibModal.open({
+ templateUrl: './share/t_grid_columns',
+ backdrop: 'static',
+ size : 'lg',
+ controller: ['$scope', '$uibModalInstance',
+ function($dialogScope, $uibModalInstance) {
+ $dialogScope.modifyColumnPos = $scope.modifyColumnPos;
+ $dialogScope.disableMultiSort = $scope.disableMultiSort;
+ $dialogScope.columns = $scope.columns;
+
+ // Push visible columns to the top of the list
+ $dialogScope.elevateVisible = function() {
+ var new_cols = [];
+ angular.forEach($dialogScope.columns, function(col) {
+ if (col.visible) new_cols.push(col);
+ });
+ angular.forEach($dialogScope.columns, function(col) {
+ if (!col.visible) new_cols.push(col);
+ });
+
+ // Update all references to the list of columns
+ $dialogScope.columns =
+ $scope.columns =
+ grid.columnsProvider.columns =
+ new_cols;
+ }
+
+ $dialogScope.toggle = function(col) {
+ col.visible = !Boolean(col.visible);
+ }
+ $dialogScope.ok = $dialogScope.cancel = function() {
+ delete $scope.lastModColumn;
+ $uibModalInstance.close()
+ }
+ }
+ ]
+ });
+ },
// generates CSV for the currently visible grid contents
grid.generateCSV = function() {
- var csvStr = '';
- var colCount = grid.columnsProvider.columns.length;
+ return grid.getAllItemsAsText().then(function(text_items) {
+ var columns = text_items.columns;
+ var items = text_items.items;
+ var csvStr = '';
- // columns
- angular.forEach(grid.columnsProvider.columns,
- function(col) {
- if (!col.visible) return;
+ // column headers
+ angular.forEach(columns, function(col) {
csvStr += grid.csvDatum(col.label);
csvStr += ',';
- }
- );
+ });
- csvStr = csvStr.replace(/,$/,'\n');
+ csvStr = csvStr.replace(/,$/,'\n');
- // items
- angular.forEach($scope.items, function(item) {
- angular.forEach(grid.columnsProvider.columns,
- function(col) {
- if (!col.visible) return;
- // bare value
- var val = grid.dataProvider.itemFieldValue(item, col);
- // filtered value (dates, etc.)
- val = $filter('egGridValueFilter')(val, col);
- csvStr += grid.csvDatum(val);
+ // items
+ angular.forEach(items, function(item) {
+ angular.forEach(columns, function(col) {
+ csvStr += grid.csvDatum(item[col.name]);
csvStr += ',';
- }
- );
- csvStr = csvStr.replace(/,$/,'\n');
- });
+ });
+ csvStr = csvStr.replace(/,$/,'\n');
+ });
- return csvStr;
+ return csvStr;
+ });
}
// Interpolate the value for column.linkpath within the context
$scope.collect = function() { grid.collect() }
+
+ $scope.confirmAllowAllAndCollect = function(){
+ egConfirmDialog.open(egStrings.CONFIRM_LONG_RUNNING_ACTION_ALL_ROWS_TITLE,
+ egStrings.CONFIRM_LONG_RUNNING_ACTION_MSG)
+ .result
+ .then(function(){
+ $scope.offset(0);
+ $scope.limit(10000);
+ grid.collect();
+ });
+ }
+
// asks the dataProvider for a page of data
grid.collect = function() {
});
}
+ grid.prepend = function(limit) {
+ var ran_into_duplicate = false;
+ var sort = grid.dataProvider.sort;
+ if (sort && sort.length) {
+ // If sorting is in effect, we have no way
+ // of knowing that the new item should be
+ // visible _if the sort order is retained_.
+ // However, since the grids that do prepending in
+ // the first place are ones where we always
+ // want the new row to show up on top, we'll
+ // remove the current sort options.
+ grid.dataProvider.sort = [];
+ }
+ if (grid.offset > 0) {
+ // if we're prepending, we're forcing the
+ // offset back to zero to display the top
+ // of the list
+ grid.offset = 0;
+ grid.collect();
+ return;
+ }
+ if (grid.collecting) return; // avoid parallel collect() or prepend()
+ grid.collecting = true;
+ console.debug('egGrid.prepend() starting');
+ // Note that we can count on the most-recently added
+ // item being at offset 0 in the data provider only
+ // for arrayNotifier data sources that do not have
+ // sort options currently set.
+ grid.dataProvider.get(0, 1).then(
+ null,
+ null,
+ function(item) {
+ if (item) {
+ var newIdx = grid.indexValue(item);
+ angular.forEach($scope.items, function(existing) {
+ if (grid.indexValue(existing) == newIdx) {
+ console.debug('egGrid.prepend(): refusing to add duplicate item ' + newIdx);
+ ran_into_duplicate = true;
+ return;
+ }
+ });
+ $scope.items.unshift(item);
+ if (limit && $scope.items.length > limit) {
+ // this accommodates the checkin grid that
+ // allows the user to set a definite limit
+ // without requiring that entire collect()
+ $scope.items.length = limit;
+ }
+ if ($scope.items.length > grid.limit) {
+ $scope.items.length = grid.limit;
+ }
+ if (grid.controls.itemRetrieved)
+ grid.controls.itemRetrieved(item);
+ if ($scope.selectAll)
+ $scope.selected[grid.indexValue(item)] = true
+ }
+ }).finally(function() {
+ console.debug('egGrid.prepend() complete');
+ grid.collecting = false;
+ $scope.selected = angular.copy($scope.selected);
+ if (ran_into_duplicate) {
+ grid.collect();
+ }
+ });
+ }
+
grid.init();
}]
};
restrict : 'AE',
scope : {
flesher: '=', // optional; function that can flesh a linked field, given the value
+ comparator: '=', // optional; function that can sort the thing at the end of 'path'
name : '@', // required; unique name
path : '@', // optional; flesh path
ignore: '@', // optional; fields to ignore when path is a wildcard
flex : '@', // optional; default flex width
align : '@', // optional; default alignment, left/center/right
dateformat : '@', // optional: passed down to egGridValueFilter
+ datecontext: '@', // optional: passed down to egGridValueFilter to choose TZ
+ datefilter: '@', // optional: passed down to egGridValueFilter to choose specialized date filters
+ dateonlyinterval: '@', // optional: passed down to egGridValueFilter to choose a "better" format
// if a field is part of an IDL object, but we are unable to
// determine the class, because it's nested within a hash
// optional: for non-IDL columns, specifying a datatype
// lets the caller control which display filter is used.
// datatype should match the standard IDL datatypes.
- datatype : '@'
+ datatype : '@',
+
+ // optional hash of functions that can be imported into
+ // the directive's scope; meant for cases where the "compiled"
+ // attribute is set
+ handlers : '=',
+
+ // optional: CSS class name that we want to have for this field.
+ // Auto generated from path if nothing is passed in via eg-grid-field declaration
+ cssSelector : "@"
},
link : function(scope, element, attrs, egGridCtrl) {
angular.forEach(
[
'visible',
+ 'compiled',
'hidden',
'sortable',
'nonsortable',
}
);
+ scope.cssSelector = attrs['cssSelector'] ? attrs['cssSelector'] : "";
+
+ // auto-generate CSS selector name for field if none declared in tt2 and there's a path
+ if (scope.path && !scope.cssSelector){
+ var cssClass = 'grid' + "." + scope.path;
+ cssClass = cssClass.replace(/\./g,'-');
+ element.addClass(cssClass);
+ scope.cssSelector = cssClass;
+ }
+
// any HTML content within the field is its custom template
var tmpl = element.html();
if (tmpl && !tmpl.match(/^\s*$/))
cols.columns = [];
cols.stockVisible = [];
cols.idlClass = args.idlClass;
+ cols.clientSort = args.clientSort;
cols.defaultToHidden = args.defaultToHidden;
cols.defaultToNoSort = args.defaultToNoSort;
cols.defaultToNoMultiSort = args.defaultToNoMultiSort;
cols.defaultDateFormat = args.defaultDateFormat;
+ cols.defaultDateContext = args.defaultDateContext;
// resets column width, visibility, and sort behavior
// Visibility resets to the visibility settings defined in the
if (!class_obj) return;
- console.debug('egGrid: auto dotpath is: ' + dotpath);
+ //console.debug('egGrid: auto dotpath is: ' + dotpath);
var path_parts = dotpath.split(/\./);
// find the IDL class definition for the last element in the
cols.cloneFromScope = function(colSpec) {
return {
flesher : colSpec.flesher,
+ comparator : colSpec.comparator,
name : colSpec.name,
label : colSpec.label,
path : colSpec.path,
linkpath : colSpec.linkpath,
template : colSpec.template,
visible : colSpec.visible,
+ compiled : colSpec.compiled,
+ handlers : colSpec.handlers,
hidden : colSpec.hidden,
datatype : colSpec.datatype,
sortable : colSpec.sortable,
multisortable : colSpec.multisortable,
nonmultisortable : colSpec.nonmultisortable,
dateformat : colSpec.dateformat,
- parentIdlClass : colSpec.parentIdlClass
+ datecontext : colSpec.datecontext,
+ datefilter : colSpec.datefilter,
+ dateonlyinterval : colSpec.dateonlyinterval,
+ parentIdlClass : colSpec.parentIdlClass,
+ cssSelector : colSpec.cssSelector
};
}
column.dateformat = cols.defaultDateFormat;
}
+ if (cols.defaultDateOnlyInterval && ! column.dateonlyinterval) {
+ column.dateonlyinterval = cols.defaultDateOnlyInterval;
+ }
+
+ if (cols.defaultDateContext && ! column.datecontext) {
+ column.datecontext = cols.defaultDateContext;
+ }
+
+ if (cols.defaultDateFilter && ! column.datefilter) {
+ column.datefilter = cols.defaultDateFilter;
+ }
+
cols.columns.push(column);
// Track which columns are visible by default in case we
// idlClass as the base.
cols.idlFieldFromPath = function(dotpath) {
var class_obj = egCore.idl.classes[cols.idlClass];
+ if (!dotpath) return null;
+
var path_parts = dotpath.split(/\./);
var idl_parent;
// the range defined by count and offset
gridData.arrayNotifier = function(arr, offset, count) {
if (!arr || arr.length == 0) return $q.when();
+
+ if (gridData.columnsProvider.clientSort
+ && gridData.sort
+ && gridData.sort.length > 0
+ ) {
+ var sorter_cache = [];
+ arr.sort(function(a,b) {
+ for (var si = 0; si < gridData.sort.length; si++) {
+ if (!sorter_cache[si]) { // Build sort structure on first comparison, reuse thereafter
+ var field = gridData.sort[si];
+ var dir = 'asc';
+
+ if (angular.isObject(field)) {
+ dir = Object.values(field)[0];
+ field = Object.keys(field)[0];
+ }
+
+ var path = gridData.columnsProvider.findColumn(field).path || field;
+ var comparator = gridData.columnsProvider.findColumn(field).comparator ||
+ function (x,y) { if (x < y) return -1; if (x > y) return 1; return 0 };
+
+ sorter_cache[si] = {
+ field : path,
+ dir : dir,
+ comparator : comparator
+ };
+ }
+
+ var sc = sorter_cache[si];
+
+ var af,bf;
+
+ if (a._isfieldmapper || angular.isFunction(a[sc.field])) {
+ try {af = a[sc.field](); bf = b[sc.field]() } catch (e) {};
+ } else {
+ af = a[sc.field]; bf = b[sc.field];
+ }
+ if (af === undefined && sc.field.indexOf('.') > -1) { // assume an object, not flat path
+ var parts = sc.field.split('.');
+ af = a;
+ bf = b;
+ angular.forEach(parts, function (p) {
+ if (af) {
+ if (af._isfieldmapper || angular.isFunction(af[p])) af = af[p]();
+ else af = af[p];
+ }
+ if (bf) {
+ if (bf._isfieldmapper || angular.isFunction(bf[p])) bf = bf[p]();
+ else bf = bf[p];
+ }
+ });
+ }
+
+ if (af === undefined) af = null;
+ if (bf === undefined) bf = null;
+
+ if (af === null && bf !== null) return 1;
+ if (bf === null && af !== null) return -1;
+
+ if (!(bf === null && af === null)) {
+ var partial = sc.comparator(af,bf);
+ if (partial) {
+ if (sc.dir == 'desc') {
+ if (partial > 0) return -1;
+ return 1;
+ }
+ return partial;
+ }
+ }
+ }
+
+ return 0;
+ });
+ }
+
if (count) arr = arr.slice(offset, offset + count);
var def = $q.defer();
// promise notifications are only witnessed when delivered
// Calls the grid refresh function. Once instantiated, the
// grid will replace this function with it's own refresh()
gridData.refresh = function(noReset) { }
+ gridData.prepend = function(limit) { }
if (!gridData.get) {
// returns a promise whose notify() delivers items
angular.forEach(provider.columnsProvider.columns,
function(col) {
// only query IDL-tracked columns
- if (!col.adhoc && (col.required || col.visible))
+ if (!col.adhoc && col.name && col.path && (col.required || col.visible))
queryFields[col.name] = col.path;
}
);
};
})
+/* https://stackoverflow.com/questions/17343696/adding-an-ng-click-event-inside-a-filter/17344875#17344875 */
+.directive('compile', ['$compile', function ($compile) {
+ return function(scope, element, attrs) {
+ // pass through column defs from grid cell's scope
+ scope.col = scope.$parent.col;
+ scope.$watch(
+ function(scope) {
+ // watch the 'compile' expression for changes
+ return scope.$eval(attrs.compile);
+ },
+ function(value) {
+ // when the 'compile' expression changes
+ // assign it into the current DOM
+ element.html(value);
+
+ // compile the new DOM and link it to the current
+ // scope.
+ // NOTE: we only compile .childNodes so that
+ // we don't get into infinite loop compiling ourselves
+ $compile(element.contents())(scope);
+ }
+ );
+ };
+}])
+
/**
* Translates bare IDL object values into display values.
* 1. Passes dates through the angular date filter
- * 2. Translates bools to Booleans so the browser can display translated
- * value. (Though we could manually translate instead..)
+ * 2. Converts bools to translated Yes/No strings
* Others likely to follow...
*/
-.filter('egGridValueFilter', ['$filter', function($filter) {
- return function(value, column) {
- switch(column.datatype) {
- case 'bool':
+.filter('egGridValueFilter', ['$filter','egCore', 'egStrings', function($filter,egCore,egStrings) {
+ function traversePath(obj,path) {
+ var list = path.split('.');
+ for (var part in path) {
+ if (obj[path]) obj = obj[path]
+ else return null;
+ }
+ return obj;
+ }
+
+ var GVF = function(value, column, item) {
+ switch(column.datatype) {
+ case 'bool':
switch(value) {
- // Browser will translate true/false for us
+ // Browser will translate true/false for us
case 't' :
case '1' : // legacy
case true:
- return ''+true;
+ return egStrings.YES;
case 'f' :
case '0' : // legacy
case false:
- return ''+false;
+ return egStrings.NO;
// value may be null, '', etc.
default : return '';
}
- case 'timestamp':
- // canned angular date filter FTW
- if (!column.dateformat)
- column.dateformat = 'shortDate';
- return $filter('date')(value, column.dateformat);
- case 'money':
+ case 'timestamp':
+ var interval = angular.isFunction(item[column.dateonlyinterval])
+ ? item[column.dateonlyinterval]()
+ : item[column.dateonlyinterval];
+
+ if (column.dateonlyinterval && !interval) // try it as a dotted path
+ interval = traversePath(item, column.dateonlyinterval);
+
+ var context = angular.isFunction(item[column.datecontext])
+ ? item[column.datecontext]()
+ : item[column.datecontext];
+
+ if (column.datecontext && !context) // try it as a dotted path
+ context = traversePath(item, column.datecontext);
+
+ var date_filter = column.datefilter || 'egOrgDateInContext';
+
+ return $filter(date_filter)(value, column.dateformat, context, interval);
+ case 'money':
return $filter('currency')(value);
- default:
- return value;
- }
- }
+ default:
+ return value;
+ }
+ };
+
+ GVF.$stateful = true;
+ return GVF;
}]);