do not lose
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Wed, 18 Apr 2012 23:26:30 +0000 (19:26 -0400)
committerLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Wed, 18 Apr 2012 23:26:30 +0000 (19:26 -0400)
Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Open-ILS/src/perlmods/lib/OpenILS/Utils/Fieldmapper.pm
Open-ILS/src/templates/circ/hold_pull_list.tt2
Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js
Open-ILS/web/js/dojo/openils/widget/GridColumnPicker.js

index df00277..eddb944 100644 (file)
@@ -139,11 +139,13 @@ sub load_links {
                        my $reltype = get_attribute( $attribute_list, 'reltype' );
                        my $key     = get_attribute( $attribute_list, 'key' );
                        my $class   = get_attribute( $attribute_list, 'class' );
+                       my $map     = get_attribute( $attribute_list, 'map' );
 
                        $$fieldmap{$fm}{links}{ $field } =
                                { class   => $class,
                                  reltype => $reltype,
                                  key     => $key,
+                                 map     => $map
                                };
                }
        }
index db470bc..ce8d338 100644 (file)
@@ -66,6 +66,8 @@
         autoHeight="10"
         editOnEnter="false"
         hideSelector="true"
+        autoCoreFields="true"
+        autoFieldFields="['current_copy']"
         editStyle="pane"
         showLoadFilter="true"
         fmClass="'ahopl'"
         query="{}">
         <thead>
             <tr>
-                <th field="barcode" fpath="current_copy.barcode"></th>
-                <th field="title" fpath="current_copy.call_number.record.simple_record.title">Title</th>
-                <th field="author" fpath="current_copy.call_number.record.simple_record.author">Author</th>
-                <th field="call_number_label" fpath="call_number_label"></th>
                 <th field="shelving_loc" fpath="current_copy.location.name" ffilter="true">Shelving Location</th>
-                <th field="usr_display_name" fpath="usr_display_name">Patron Name or Alias</th>
-                <th field="hold_type" fpath="hold_type"></th>
-                <th field="request_time" fpath="request_time"></th>
-                <th field="expire_time" fpath="expire_time"></th>
+                <th field="call_number_label" fpath="call_number_label"></th>
+                <th field="author" fpath="current_copy.call_number.record.simple_record.author">Author</th>
+                <th field="title" fpath="current_copy.call_number.record.simple_record.title">Title</th>
+                <th field="barcode" fpath="current_copy.barcode"></th>
+                <th field="parts" fpath="current_copy.parts.label" fsort="false">Parts</th>
             </tr>
         </thead>
     </table>
index 0932970..c70f52c 100644 (file)
@@ -16,6 +16,8 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
              * FlattenerGrid in their own right */
             "columnReordering": true,
             "columnPersistKey": null,
+            "autoCoreFields": false,
+            "autoFieldFields": null,
             "showLoadFilter": false,    /* use FlattenerFilterDialog */
             "fetchLock": false,
 
@@ -55,12 +57,12 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 /* 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) {
@@ -70,7 +72,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                         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
@@ -135,85 +137,96 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 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
                         }
@@ -226,8 +239,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
             },
 
             "_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
@@ -262,6 +274,97 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 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);
 
@@ -270,9 +373,11 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
             },
 
             "startup": function() {
-
                 /* Save original query for further filtering later */
                 this._baseQuery = dojo.clone(this.query);
+
+                this._addAutoFields();
+
                 this._startupGridHelperColumns();
 
                 if (!this.columnPicker) {
@@ -296,6 +401,17 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 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) {
@@ -325,12 +441,12 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                     }), 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 = {};
@@ -745,6 +861,11 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                         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);
         };
     })();
 
index 9cc367d..16e84b9 100644 (file)
@@ -63,8 +63,9 @@ if(!dojo._hasResource["openils.widget.GridColumnPicker"]) {
          *  This is necessary if external forces alter the structure. 
          */
         reloadStructure : function() {
-            this.structure = this.grid.structure;
             this.cells = this.structure[0].cells[0].slice();
+            this.pruneInvisibleFields();
+            this.structure = this.grid.structure;
             this.grid.setStructure(this.structure);
         },
 
@@ -211,16 +212,24 @@ if(!dojo._hasResource["openils.widget.GridColumnPicker"]) {
                 else
                     this.dialogTable.appendChild(tr);
 
-                if ( this.grid.canSort(i+1) ) { // column index is 1-based
-
-                    // must be added after its parent node is inserted into the DOM.
-                    var ns = new dijit.form.NumberSpinner(
-                        {   constraints : {places : 0}, 
-                            value : cell._sort || 0,
-                            style : 'width:4em',
-                            name : 'sort',
-                        }, ipt3
-                    );
+                if (this.grid.canSort(
+                    i + 1,  /* column index is 1-based */
+                    true    /* skip structure test (API abuse) */
+                )) { 
+
+                    /* Ugly kludge. When using with FlattenerGrid the
+                     * conditional is needed. Shouldn't hurt usage with
+                     * AutoGrid. */
+                    if (typeof cell.fsort == "undefined" || cell.fsort) {
+                        // must be added after its parent node is inserted into the DOM.
+                        var ns = new dijit.form.NumberSpinner(
+                            {   constraints : {places : 0}, 
+                                value : cell._sort || 0,
+                                style : 'width:4em',
+                                name : 'sort',
+                            }, ipt3
+                        );
+                    }
                 }
             }
         },
@@ -366,6 +375,18 @@ if(!dojo._hasResource["openils.widget.GridColumnPicker"]) {
             this.grid.update();
         },
 
+        // *only* call this when no usr setting tells us what columns
+        // are visible or not.
+        pruneInvisibleFields : function() {
+            this.structure[0].cells[0] = dojo.filter(
+                this.structure[0].cells[0],
+                dojo.hitch(this, function(c) {
+                    // keep true or undef, lose false
+                    return typeof c._visible == "undefined" || c._visible;
+                })
+            );
+        },
+
         load : function() {
             var _this = this;