* FlattenerGrid in their own right */
"columnReordering": true,
"columnPersistKey": null,
+ "autoCoreFields": false,
+ "autoFieldFields": null,
"showLoadFilter": false, /* use FlattenerFilterDialog */
"fetchLock": false,
/* These are the fields defined in thead -> tr -> [th,th,...].
* For purposes of building the map, where each field has
* three boolean attributes "display", "sort" and "filter",
- * assume "display" and "sort" are always true for these.
+ * assume "display" is always true for these.
* That doesn't mean that at the UI level we can't hide a
* column later.
*
* If you need extra fields in the map for which display
- * or sort should *not* be true, use mapExtras.
+ * should *not* be true, use mapExtras.
*/
dojo.forEach(
fields, function(field) {
map[field.field] = {
"display": true,
"filter": (field.ffilter || false),
- "sort": true,
+ "sort": field.fsort,
"path": field.fpath || field.field
};
/* The following attribute is not for the flattener
return clean;
},
- /* The FlattenerStore doesn't need this, but it has at least two
- * uses: 1) FlattenerFilterDialog, 2) setting column header labels
- * to IDL defaults.
- *
- * To call these 'Terminii' can be misleading. In certain
- * (actually probably common) cases, they won't really be the last
- * field in a path, but the next-to-last. Read on. */
- "_calculateMapTerminii": function() {
- function _fm_is_selector_for_class(hint, field) {
- var cl = fieldmapper.IDL.fmclasses[hint];
+ /* Given the hint of a class to start at, follow path to the end
+ * and return information on the last field. */
+ "_followPathToEnd": function(hint, path, allow_selector_backoff) {
+ function _fm_is_selector_for_class(h, field) {
+ var cl = fieldmapper.IDL.fmclasses[h];
return (cl.field_map[cl.pkey].selector == field);
}
- function _follow_to_end(hint, path) {
- var last_field, last_hint;
- var orig_path = dojo.clone(path);
- var field;
-
- while (field = path.shift()) {
- /* XXX this assumes we have the whole IDL loaded. I
- * guess we could teach this to work by loading classes
- * on demand when we don't have the whole IDL loaded. */
- var field_def =
- fieldmapper.IDL.fmclasses[hint].field_map[field];
-
- if (!field_def) {
- throw new Error(
- "Lost our way in IDL at hint " + hint +
- ", field " + field
- );
- }
-
- if (field_def["class"] && path.length) {
- last_field = field;
- last_hint = hint;
-
- hint = field_def["class"];
- } else if (path.length) {
- /* There are more fields left but we can't follow
- * the chain via IDL any further. */
- throw new Error(
- "_calculateMapTerminii can't parse path " +
- orig_path + " (at " + field + ")"
- );
- } else {
- break; /* keeps field defined after loop */
- }
+ var last_field, last_hint;
+ var orig_path = dojo.clone(path);
+ var field, field_def;
+
+ while (field = path.shift()) {
+ /* XXX this assumes we have the whole IDL loaded. I
+ * guess we could teach this to work by loading classes
+ * on demand when we don't have the whole IDL loaded. */
+ field_def =
+ fieldmapper.IDL.fmclasses[hint].field_map[field];
+
+ if (!field_def) {
+ /* This can be ok in some cases. Columns following
+ * IDL paths involving links with a nonempty "map"
+ * attribute can be used for display only (no
+ * sort, no filter). */
+ console.info(
+ "Lost our way in IDL at hint " + hint +
+ ", field " + field + "; may be ok"
+ );
+ return null;
}
- var datatype = field_def.datatype;
- var indirect = false;
- /* Back off the last field in the path if it's a selector
- * for its class, because the preceding field will be
- * a better thing to hand to AutoFieldWidget.
- */
- if (orig_path.length > 1 &&
- _fm_is_selector_for_class(hint, field)) {
- hint = last_hint;
- field = last_field;
- datatype = "link";
- indirect = true;
+ if (field_def["class"] && path.length) {
+ last_field = field;
+ last_hint = hint;
+
+ hint = field_def["class"];
+ } else if (path.length) {
+ /* There are more fields left but we can't follow
+ * the chain via IDL any further. */
+ throw new Error(
+ "_calculateMapTerminii can't parse path " +
+ orig_path + " (at " + field + ")"
+ );
+ } else {
+ break; /* keeps field defined after loop */
}
+ }
- return {
- "fmClass": hint,
- "name": field,
- "label": field_def.label,
- "datatype": datatype,
- "indirect": indirect
- };
+ var datatype = field_def.datatype;
+ var indirect = false;
+ /* If allowed, back off the last field in the path if it's a
+ * selector for its class, because the preceding field will be
+ * a better thing to hand to AutoFieldWidget.
+ */
+ if (orig_path.length > 1 && allow_selector_backoff &&
+ _fm_is_selector_for_class(hint, field)) {
+ hint = last_hint;
+ field = last_field;
+ datatype = "link";
+ indirect = true;
}
+ return {
+ "fmClass": hint,
+ "name": field,
+ "label": field_def.label,
+ "datatype": datatype,
+ "indirect": indirect
+ };
+ },
+
+ /* The FlattenerStore doesn't need this, but it has at least two
+ * uses: 1) FlattenerFilterDialog, 2) setting column header labels
+ * to IDL defaults.
+ *
+ * To call these 'Terminii' can be misleading. In certain
+ * (actually probably common) cases, they won't really be the last
+ * field in a path, but the next-to-last. Read on. */
+ "_calculateMapTerminii": function() {
this.mapTerminii = [];
for (var column in this.mapClause) {
+ var end = this._followPathToEnd(
+ this.fmClass,
+ this.mapClause[column].path.split(/\./),
+ true /* allow selector backoff */
+ );
+ if (!end)
+ continue;
var terminus = dojo.mixin(
- _follow_to_end(
- this.fmClass,
- this.mapClause[column].path.split(/\./)
- ), {
+ end, {
"simple_name": column,
"isfilter": this.mapClause[column].filter
}
},
"_supplementHeaderNames": function() {
- /* You'd be surprised how rarely this make sense in Flattener
- * use cases, but if we didn't give a particular header cell
+ /* If we didn't give a particular header cell
* (<th>) a display name (the innerHTML of that <th>), then
* use the IDL to provide the label of the terminus of the
* flattener path for that column. It may be better than using
return {"labels": labels, "columns": columns};
},
+ "_getAutoFieldFields": function(fmclass) {
+ return fieldmapper.IDL.fmclasses[fmclass].fields.filter(
+ function(field) {
+ return !field.virtual && field.datatype != "link";
+ }
+ );
+ },
+
+ /* Take our core class (this.fmClass) and add table columns for
+ * any field we don't already have covered by actual hard-coded
+ * <th> columns. */
+ "_addAutoCoreFields": function() {
+ var cell_list = this.structure[0].cells[0];
+
+ dojo.forEach(
+ fieldmapper.IDL.fmclasses[this.fmClass].fields,
+ function(f) {
+ if (f.datatype == "link" || f.virtual)
+ return;
+
+ if (cell_list.filter(
+ function(c) {
+ if (!c.fpath) return false;
+ return c.fpath.split(/\./)[0] == f.name;
+ }
+ ).length)
+ return;
+
+ cell_list.push({
+ "field": f.name,
+ "name": f.label,
+ "fsort": true,
+ "_visible": false
+ });
+ }
+ );
+ },
+
+ "_addAutoFieldFields": function(paths) {
+ var self = this;
+ var n = 0;
+
+ dojo.forEach(
+ paths, function(path) {
+ /* The beginning is the end. */
+ var beginning = self._followPathToEnd(
+ self.fmClass, path.split(/\./), false
+ );
+ if (!beginning) {
+ return;
+ } else {
+ console.log(dojo.toJson(beginning));
+ dojo.forEach(
+ self._getAutoFieldFields(beginning.fmClass),
+ function(field) {
+ var would_be_path =
+ path + "." + field.name;
+ var wbp_re =
+ new RegExp("^" + would_be_path);
+ if (!self.structure[0].cells[0].filter(
+ function(c) {
+ return c.fpath.match(wbp_re);
+ }
+ ).length) {
+ self.structure[0].cells[0].push({
+ "field": "AUTO_" + beginning.name +
+ "_" + field.name,
+ "name": beginning.label + " - " +
+ field.label,
+ "fsort": true,
+ "fpath": would_be_path,
+ "_visible": false
+ });
+ }
+ }
+ );
+ }
+ }
+ );
+ },
+
+ "_addAutoFields": function() {
+ if (this.autoCoreFields)
+ this._addAutoCoreFields();
+
+ if (dojo.isArray(this.autoFieldFields))
+ this._addAutoFieldFields(this.autoFieldFields);
+
+ this.setStructure(this.structure);
+ },
+
"constructor": function(args) {
dojo.mixin(this, args);
},
"startup": function() {
-
/* Save original query for further filtering later */
this._baseQuery = dojo.clone(this.query);
+
+ this._addAutoFields();
+
this._startupGridHelperColumns();
if (!this.columnPicker) {
this.inherited(arguments);
},
+ "canSort": function(idx, skip_structure /* API abuse */) {
+ var initial = this.inherited(arguments);
+
+ /* idx is one-based instead of zero-based for a reason. */
+ var view_idx = Math.abs(idx) - 1;
+ return initial && (
+ skip_structure ||
+ this.views.views[0].structure.cells[0][view_idx].fsort
+ );
+ },
+
/* Maps ColumnPicker sort fields to the correct format.
If no sort fields specified, falls back to defaultSort */
"_mapCPSortFields": function(sortFields) {
}), this.query
);
- if (!this.fetchLock)
- this._refresh(true);
-
// pick up any column label changes
this.columnPicker.reloadStructure();
+ if (!this.fetchLock)
+ this._refresh(true);
+
this._showing_create_pane = false;
this.overrideEditWidgets = {};
cellDef[a] = value;
}
);
+
+ /* fsort is special. Assume true unless defined. */
+ var fsort = dojo.attr(node, "fsort");
+ cellDef.fsort = (typeof fsort == "undefined" || fsort === null) ?
+ true : dojo.fromJson(fsort);
};
})();