+++ /dev/null
-/**
- * Service for generating list management objects.
- * Each object tracks common list attributes like limit, offset, etc.,
- * A ListManager is not responsible for collecting data, it's only
- * there to allow controllers to have a known consistent API
- * for manage list-related information.
- *
- * The service exports a single attribute, which instantiates
- * a new ListManager object. Controllers using ListManagers
- * are responsible for providing their own route persistence.
- *
- * var list = egList.create();
- * if (list.hasNextPage()) { ... }
- *
- */
-
-angular.module('egListMod', ['egCoreMod'])
-
-.factory('egList', ['$filter', 'egIDL', function($filter, egIDL) {
-
- function ListManager(args) {
- var self = this;
- this.limit = 25;
- this.offset = 0;
- this.sort = null;
- this.totalCount = 0;
-
- // attribute on each item in our items list which
- // refers to its unique identifier value
- this.indexField = 'id';
-
- // true if the index field name refers to a
- // function instead of an object attribute
- this.indexFieldAsFunction = false;
-
- // per-page list of items
- this.items = [];
-
- // collect any defaults passed in
- if (args) angular.forEach(args,
- function(val, key) {self[key] = val});
-
- // sorted list of all available display columns
- // a column takes form of (at minimum) {name : name, label : label}
- this.allColumns = [];
-
- // {name => true} map of visible columns
- this.displayColumns = {};
-
- // {index => true} map of selected rows
- this.selected = {};
-
- this.indexValue = function(item) {
- if (this.indexFieldAsFunction) {
- return item[this.indexField]();
- } else {
- return item[this.indexField];
- }
- }
-
- // returns item objects
- this.selectedItems = function() {
- var items = [];
- angular.forEach(
- this.items,
- function(item) {
- if (self.selected[self.indexValue(item)])
- items.push(item);
- }
- );
- return items;
- }
-
- // remove an item from the items list and return the deleted item
- this.removeItem = function(index) {
- var deleted;
- angular.forEach(this.items, function(item, idx) {
- if (self.indexValue(item) == index) {
- self.items.splice(idx, 1);
- deleted = item;
- }
- });
- delete this.selected[index];
- return deleted;
- }
-
- // get item by index value
- this.getItem = function(index) {
- return this.items.filter(
- function(item) { return self.indexValue(item) == index }
- )[0];
- }
-
- this.count = function() { return this.items.length }
-
- this.reset = function() {
- this.offset = 0;
- this.totalCount = 0;
- this.items = [];
- this.selected = {};
- }
-
- // prepare to draw a new page of data
- this.resetPageData = function() {
- this.items = [];
- this.selected = {};
- }
-
- this.showAllColumns = function() {
- angular.forEach(this.allColumns, function(field) {
- self.displayColumns[field.name] = true;
- });
- }
-
- this.hideAllColumns = function() {
- angular.forEach(this.allColumns, function(field) {
- delete self.displayColumns[field.name]
- });
- }
-
- // selects one row after deselecting all of the others
- this.selectOne = function(index) {
- this.deselectAll();
- this.selected[index] = true;
- }
-
- // selects or deselects a row, without affecting the others
- this.toggleOneSelection = function(index) {
- if (this.selected[index]) {
- delete this.selected[index];
- } else {
- this.selected[index] = true;
- }
- }
-
- // selects all visible rows
- this.selectAll = function() {
- angular.forEach(this.items, function(item) {
- self.selected[self.indexValue(item)] = true
- });
- }
-
- // if all are selected, deselect all, otherwise select all
- this.toggleSelectAll = function() {
- if (Object.keys(this.selected).length == this.items.length) {
- this.deselectAll();
- } else {
- this.selectAll();
- }
- }
-
- // deselects all visible rows
- this.deselectAll = function() {
- this.selected = {};
- }
-
- this.addColumn = function(col) {
- this.allColumns.push(col);
- if (col.display)
- this.displayColumns[col.name] = true;
- }
-
- this.defaultColumns = function(list) {
- // set the display=true value for the selected columns
- angular.forEach(list, function(name) {
- self.displayColumns[name] = true
- });
-
- // default columns may be provided before we
- // know what our columns are. Save them for later.
- this._defaultColumns = list;
-
- // setColumns we rearrange the allCollums
- // list based on the content of this._defaultColums
- if (this.allColumns.length)
- this.setColumns(this.allColumns);
- }
-
- this.setColumns = function(list) {
- if (this._defaultColumns) {
- this.allColumns = [];
-
- // append the default columns to the front of
- // our allColumnst list. Any remaining columns
- // are plopped onto the end.
- angular.forEach(
- this._defaultColumns,
- function(name) {
- var foundIndex;
- angular.forEach(list, function(f, idx) {
- if (f.name == name) {
- self.allColumns.push(f);
- foundIndex = idx;
- }
- });
- list.splice(foundIndex, 1);
- }
- );
- this.allColumns = this.allColumns.concat(list);
- delete this._defaultColumns;
-
- } else {
- this.allColumns = list;
- angular.forEach(this.allColumns, function(col) {
- if (col.display)
- self.displayColumns[col.name] = true;
- });
- }
- }
-
- /**
- * Adds all IDL fields for the specified class to the set of
- * display columns. If a field is already in the display
- * columns, it's not added again.
- *
- * 'base' is the base dotpath for finding values within each
- * object in the list. E.g. I have objects that look like
- * foo.bar().baz() and I want to present columns for my 'bar'
- * objects, my 'base' is 'foo.bar'. When access values via
- * fieldValue(), the list will look up foo.bar()[field_name]().
- * 'base' is only required when using fieldValue().
- *
- * To avoid ambiguity, automatically added column labels are
- * prefixed with the class label.
- */
- this.addColumnsForClass = function(cls, base) {
- var idlClass = egIDL.classes[cls];
- angular.forEach(idlClass.fields, function(field) {
- if (!field.label) return;
-
- // only add virtual fields if the class is virtual
- if (field.virtual && !idlClass.virtual) return;
-
- var name = field.name;
- if (base) name = base + '.' + name;
-
- // avoid adding the field if it's already represented
- if (self.allColumns.filter(
- function(f) {return f.name == name })[0])
- return;
-
- self.allColumns.push({
- name : name,
- label : idlClass.label + ':' + field.label
- });
- });
- }
-
- this.onFirstPage = function() {
- return this.offset == 0;
- }
-
- this.hasNextPage = function() {
- // we have less data than requested, there must
- // not be any more pages
- if (this.items.length < this.limit) return false;
-
- // if the total count is not known, assume that a full
- // page of data implies more pages are available.
- if (!this.totalCount) return true;
-
- // we have a full page of data, but is there more?
- return this.totalCount > (this.offset + this.items.length);
- }
-
- this.incrementPage = function() {
- this.offset += this.limit;
- }
-
- this.decrementPage = function() {
- if (this.offset < this.limit) {
- this.offset = 0;
- } else {
- this.offset -= this.limit;
- }
- }
-
- // given an object and a dot-separated path to a field,
- // extract the value of the field. The path can refer
- // to function names or object attributes. If the final
- // value is an IDL field, run the value through its
- // corresponding output filter.
- // TODO: support modifying field values --
- // useful for inline table editing
- this.fieldValue = function(obj, dotpath) {
- if (!obj) return '';
- if (!dotpath) return obj;
-
- var idlField;
- var parts = dotpath.split('.');
- var cls, clsobj;
-
- angular.forEach(parts, function(step, idx) {
- // object is not fleshed to the expected extent
- if (!obj || typeof obj != 'object') {
- obj = '';
- return;
- }
-
- cls = obj.classname;
- if (cls && (clsobj = egIDL.classes[cls])) {
- idlField = clsobj.fields.filter(
- function(f) { return f.name == step })[0];
- obj = obj[step]();
- } else {
- if (typeof obj[step] == 'function') {
- obj = obj[step]();
- } else {
- obj = obj[step];
- }
- }
- });
-
- if (obj === null || obj === undefined || obj === '')
- return '';
-
- if (!idlField) return obj;
-
- switch(idlField.datatype) {
- case 'timestamp':
- return $filter('date')(obj, 'shortDate');
- case 'bool':
- // let the browser translate true / false for us
- return Boolean(obj == 't');
- default:
- if (typeof obj == 'object' && obj.classname) {
- // if we have a path to an object, display the
- // selector value or pkey instead of the
- // stringified object
- var pkey = egIDL.classes[obj.classname].pkey
- var pkey_field = egIDL.classes[obj.classname].fields.filter(
- function(f) { return f.name == pkey })[0];
- if (pkey_field) {
- var dfield = pkey_field.selector || pkey;
- return obj[dfield]();
- } else {
- // if we get here that means the object has no
- // pkey / selector field. that should not happen.
- return obj;
- }
-
- } else {
- return obj;
- }
- }
- }
- }
-
- return {
- create : function(args) {
- return new ListManager(args)
- }
- };
-}]);
-