-angular.module('egGridMod', ['egCoreMod', 'egListMod'])
+angular.module('egGridMod', ['egCoreMod', 'egListMod', 'egUiMod'])
.directive('egGrid', function() {
return {
- restrict : 'A',
+ restrict : 'AE',
transclude : true,
scope : {
idlClass : '@',
+
+ // points to a structure in the calling scope which defines
+ // a PCRUD-compliant query.
query : '=',
- sort : '@',
- autoFields : '='
+
+ // if true, grid columns are derived from all non-virtual
+ // fields on the base idlClass
+ autoFields : '=',
+
+ // optional, custom data retrieval function
+ dataFetcher : '=',
+
+ // grid preferences will be stored / retrieved with this key
+ persistKey : '@',
+
+ // if true, use the scroll CSS to force a vertical height
+ // and scroll bar
+ isScroll : '='
+ },
+
+ 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.
+ scope.fetchData();
},
- templateUrl : '/eg/staff/parts/t_autogrid', // TODO: abs url
+
+ templateUrl : '/eg/staff/parts/t_autogrid', // TODO: avoid abs url
+
controller : function($scope, $timeout, egIDL, egAuth, egNet, egList) { // TODO: reqs list
var self = this;
$scope.dataList = egList.create();
- this.addField = function(fieldScope) {
+ // column-header click quick sort
+ $scope.sortOn = function(col_name) {
+ if ($scope.sort && $scope.sort.length &&
+ $scope.sort[0] == col_name) {
+ var blob = {};
+ blob[col_name] = 'desc';
+ $scope.sort = [blob];
+ } else {
+ $scope.sort = [col_name];
+ }
+ $scope.fetchData();
+ }
+
+ /**
+ * Adds a column from an eg-grid-field or directly from
+ * an IDL field via compileAutoFields.
+ */
+ this.addColumn = function(fieldSpec) {
var field = {
- name : fieldScope.name,
- label : fieldScope.label,
- path : fieldScope.path,
- display : (fieldScope.display === false) ? false : true
+ name : fieldSpec.name,
+ label : fieldSpec.label,
+ path : fieldSpec.path,
+ datatype : fieldSpec.datatype,
+ display : (fieldSpec.display === false) ? false : true
};
- self.applyFieldLabel(field);
+ if (!field.path) field.path = field.name;
+ field = self.absorbField(field);
$scope.dataList.addColumn(field);
}
}
}
}
+
+ // once we exceeed the number of display fields,
+ // the others are added as hidden fields, accessible
+ // via the column picker.
if ($scope.dataList.allColumns.length >= self.maxFieldCount) {
console.log('setting to false ' + field.name);
field.display = false;
}
- self.addField(field);
+ self.addColumn(field);
}
);
}
- this.applyFieldLabel = function(field) {
- if (field.label) return; // label already applied
-
- var class_obj = egIDL.classes[$scope.idlClass];
- if (!field.path) field.path = field.name;
- var path_parts = field.path.split(/\./);
+ // given a base class and a dotpath, find the IDL field
+ this.getIDLFieldFromPath = function(idlClass, path) {
+ var class_obj = egIDL.classes[idlClass];
+ var path_parts = path.split(/\./);
// note: use of for() is intentional for early exit
- var field_obj;
+ 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) {
- field_obj = class_obj.fields[field_idx];
+ idl_field = class_obj.fields[field_idx];
break;
}
}
// unless we're at the end of the list, this field should
// link to another class.
- if (field_obj && field_obj['class'] && (
- field_obj.datatype == 'link' ||
- field_obj.datatype == 'org_unit')) {
- class_obj = egIDL.classes[field_obj['class']];
+ if (idl_field && idl_field['class'] && (
+ idl_field.datatype == 'link' ||
+ idl_field.datatype == 'org_unit')) {
+ class_obj = egIDL.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: " + field.path);
+ console.error("egGrid: invalid IDL path: " + path);
}
}
}
- field.label = field_obj ? field_obj.label : field.name;
+ return idl_field;
+ }
+
+ /**
+ * Looks for the matching IDL field to extract the label
+ * and datattype as needed.
+ * Creates a local copy of the field for our internal
+ * machinations.
+ */
+ this.absorbField = function(field) {
+
+ // start by cloning the field so we can flesh it out.
+ // note: aungular.copy won't work, because 'field' may
+ // be a $scope object.
+ var new_field = {
+ name : field.name,
+ label : field.label,
+ path : field.path || field.name,
+ display : (field.display === false) ? false : true
+ };
+
+ // lookup the matching IDL field
+ var idl_field = field.datatype ? field :
+ self.getIDLFieldFromPath($scope.idlClass, field.path);
+
+ // No matching IDL field. Caller has gone commando.
+ // Nothing left to do.
+ if (!idl_field) return new_field;
+
+ new_field.datatype = idl_field.datatype;
+
+ if (field.label) {
+ // caller-provided label
+ new_field.label = field.label;
+ } else {
+ if (idl_field.label) {
+ new_field.label = idl_field.label;
+ } else {
+ new_field.label = new_field.name;
+ }
+ }
+
+ return new_field;
}
- this.fetchData = function() {
+ /**
+ * For stock grids, makes a flattened_search call to retrieve
+ * the requested values.
+ * For non-stock grids, calls the external data fetcher
+ */
+ $scope.fetchData = function() {
+ $scope.dataList.resetPageData();
+
+ if (self.dataFetcher)
+ return self.dataFetcher();
if (!$scope.query) {
console.error("egGrid requires a query");
egAuth.token(), $scope.idlClass, queryFields,
$scope.query,
{ sort : $scope.sort,
- limit : 10, // TODO
+ limit : 20, // TODO
offset : 0 // TODO
}
- ).then(null, null, function(item) {
- $scope.dataList.items.push(item);
- }
- );
- }
-
- // Don't call fetchData until we know what the fields are
- // TODO: this is a hack which polls to see if our eg-grid-field's
- // have been processed. There has to be a better way...
- readycheck = 0;
- this.checkReadyForDraw = function() {
- if ($scope.autoFields || $scope.dataList.allColumns.length) {
- self.fetchData();
- } else {
- if (++readycheck > 10000) return; // failsafe, no fields defined
- // check again after the next $digest loop
- $scope.$evalAsync(function() {self.checkReadyForDraw()});
- }
+ ).then(null, null, function(item) {
+ $scope.dataList.items.push(item);
+ });
}
- this.checkReadyForDraw();
}
};
})
.directive('egGridField', function() {
return {
require : '^egGrid',
- restrict : 'A',
+ restrict : 'AE',
transclude : true,
scope : {name : '@', path : '@', label : '@'},
template : '<div></div>', // NOOP template
link : function(scope, element, attrs, egGridCtrl) {
- egGridCtrl.addField(scope);
+ egGridCtrl.addColumn(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..)
+ * Others likely to follow...
+ */
+.filter('egGridvalueFilter', ['$filter', function($filter) {
+ return function(value, item) {
+ switch(item.datatype) {
+ case 'bool':
+ // Browser will translate true/false for us
+ return Boolean(value == 't');
+ case 'timestamp':
+ // canned angular date filter FTW
+ return $filter('date')(value);
+ default:
+ return value;
+ }
+ }
+}]);