This is what victory looks like! FlattenerFilterDialog
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Fri, 30 Mar 2012 05:27:53 +0000 (01:27 -0400)
committerLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Fri, 30 Mar 2012 05:27:53 +0000 (01:27 -0400)
Heh, ok, it's not total victory.

FlattenerFilterDialog works (it's a small subclass of
PCrudFilterDialog), and it  cooperates with FlattenerGrid.  That's the
good news.  But the way it works is sometimes less than ideal due to
design under-think.

Imagine core class acp, and you have a map for the flattener with these
columns, because these are the things you want to see in the grid:

{
    "barcode": "barcode",
    "shelving_loc": "location.name",
    "shelving_loc_owning_lib": "location.owning_lib.shortname"
}

The FlattenerFilterDialog figures out the terminal fields for each of
those paths, which are acp.barcode, acpl.name, and aou.shortname.  It
naturally gives you the appropriate AutoWidget for each of those things.

Which is a TextBox. In every case.

Because all of those are text field.

See the problem?

They do /work/, but as a user you probably want to get an org unit
dropdown instead of being expected to type "BR1" or "ARL-ATH" or
whatever.

So I guess the way to make this better is to give an option in the
FlattenerGrid per-column (which will pass this option onto
FlattenerFilterDialog) that tells us how many steps /from/ the end to
stop when we're looking for a field to inform our choice of AutoWidget?
So instead of [acp.]location.owning_lib.shortname = aou.shortname, if we
stop at one step from the end we get acpl.owning_lib instead, which
would give us a much more appropriate autowidget.

yeah.

that'll be the solution

Also, as a bonus, you can now leave out the header name between <th> and
</th>, and it will be filled in from the IDL.  This is surprisingly
often not what you really want, but it's a better reasonable default
than the raw field name.

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Open-ILS/src/templates/conify/flattener_test.tt2
Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js
Open-ILS/web/js/dojo/openils/widget/PCrudFilterDialog.js
Open-ILS/web/js/dojo/openils/widget/_GridHelperColumns.js

index 1011e44..e6788f9 100644 (file)
@@ -1,10 +1,6 @@
 [% WRAPPER base.tt2 %]
 [% ctx.page_title = 'Flattener Test' %]
 <!--
-        fmClass="'acp'"
-        defaultSort="['call_number']"
-        mapExtras="{copy_status: {path: 'status.name', filter: true}}"
-        query="{'copy_status': ['Available','Reshelving','In process'],'circ_lib': 'BR1'}">
         -->
 <script type="text/javascript">
     dojo.require("dijit.form.Button");
         jsid="grid"
         dojoType="openils.widget.FlattenerGrid"
         columnPersistKey='"conify.flattener_test"'
-        fmClass="'brsrc'"
         autoHeight="10"
         editOnEnter="true"
         editStyle="pane"
-        defaultSort="['barcode']"
-        query="{'owner': 'BR1'}">
+        showLoadFilter="true"
+        fmClass="'acp'"
+        defaultSort="['call_number']"
+        mapExtras="{copy_status: {path: 'status.name', filter: true}}"
+        query="{'copy_status': ['Available','Reshelving','In process'],'circ_lib': 'BR1'}">
         <thead>
             <tr>
-                <th field="barcode" fpath="barcode" ffilter="true">Barcode</th>
-                <th field="owner" fpath="owner.shortname" ffilter="true">Circulation Library</th>
-                <th field="resource_type" fpath="type.name">Resource type</th>
+                 <th field="barcode" fpath="barcode" ffilter="true">Barcode</th>
+                <th field="circ_lib_name" fpath="circ_lib.name" ffilter="true">Circulation Library Name</th>
+                <th field="circ_lib" fpath="circ_lib.shortname" ffilter="true">Circulation Library</th>
+                <th field="call_number" fpath="call_number.label" ffilter="true"></th>
+                <th field="shelving_loc" fpath="location.name" ffilter="true">Shelving Location</th>
             </tr>
         </thead>
     </table>
diff --git a/Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js b/Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js
new file mode 100644 (file)
index 0000000..47bbe04
--- /dev/null
@@ -0,0 +1,56 @@
+if (!dojo._hasResource["openils.widget.FlattenerFilterDialog"]) {
+    dojo._hasResource["openils.widget.FlattenerFilterDialog"] = true;
+
+    dojo.provide("openils.widget.FlattenerFilterDialog");
+    dojo.require("openils.widget.PCrudFilterDialog");
+
+    dojo.declare(
+        "openils.widget.FlattenerFilterDialog",
+        [openils.widget.PCrudFilterDialog], {
+            "mapTerminii": null,
+
+            "constructor": function(args) {
+                dojo.mixin(this, args);
+            },
+
+            "_buildFieldStore": function() {
+                var self = this;
+
+                if (!this.mapTerminii)
+                    throw new Error("No mapTerminii list; can't proceed");
+
+                var realFieldList = dojo.clone(this.mapTerminii).filter(
+                    function(o) {
+                        if (self.suppressFilterFields &&
+                            dojo.indexOf(
+                                self.suppressFilterFields, o.simple_name
+                            ) >= -1
+                        ) {
+                            return false;
+                        }
+
+                        return o.isfilter;
+                    }
+                );
+
+                this.fieldStore = new dojo.data.ItemFileReadStore({
+                    "data": {
+                        "identifier": "simple_name",
+                        "name": "label",
+                        "items": realFieldList.map(
+                            function(item) {
+                                return {
+                                    "label": item.label,
+                                    "name": item.name,
+                                    "type": item.datatype,
+                                    "fmClass": item.fmClass,
+                                    "simple_name": item.simple_name
+                                };
+                            }
+                        )
+                    }
+                });
+            }
+        }
+    );
+}
index 0cdb3f4..939cdac 100644 (file)
@@ -16,6 +16,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
              * FlattenerGrid in their own right */
             "columnReordering": true,
             "columnPersistKey": null,
+            "showLoadFilter": false,    /* use FlattenerFilterDialog */
 
             /* These potential constructor arguments maybe useful to
              * FlattenerGrid in their own right, and are passed to
@@ -33,7 +34,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
             "requiredFields": null,     /* affects create/edit dialogs */
             "suppressEditFields": null, /* affects create/edit dialogs */
 
-            /* _generate_map() lives to interpret the attributes of the
+            /* _generateMap() lives to interpret the attributes of the
              * FlattenerGrid dijit itself plus those definined in
              * <table>
              *  <thead>
@@ -42,7 +43,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
              * to build the map to hand to the FlattenerStore, which in turn
              * uses it to query the flattener service.
              */
-            "_generate_map": function() {
+            "_generateMap": function() {
                 var map = this.mapClause = {};
                 var fields = this.structure[0].cells[0];
 
@@ -64,19 +65,14 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                             "sort": true,
                             "path": field.fpath || field.field
                         };
+                        /* The following attribute is not for the flattener
+                         * service's benefit, but for other uses. We capture
+                         * the hardcoded <th> value (the header label) if any.*/
+                        if (field.name)
+                            map[field.field]._label = field.name;
                     }
                 );
 
-                /* make sure we always have a field for fm identifier */
-                if (!map[this.fmIdentifier]) {
-                    map[this.fmIdentifier] = {
-                        "path": this.fmIdentifier,
-                        "display": true,
-                        "sort": false,
-                        "filter": false
-                    };
-                }
-
                 if (this.mapExtras) {
                     /* It's not particularly useful to add simple fields, i.e.
                      *  circ_lib: "circ_lib.name"
@@ -98,9 +94,108 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                     dojo.mixin(map, this.mapExtras);
                 }
 
+                /* Do this now, since we don't want a silently added
+                 * identifier attribute in the terminii list (see its uses). */
+                this._calculateMapTerminii();
+                this._supplementHeaderNames();
+
+                /* make sure we always have a field for fm identifier */
+                if (!map[this.fmIdentifier]) {
+                    map[this.fmIdentifier] = {
+                        "path": this.fmIdentifier,
+                        "display": true,    /* Flattener displays it to us,
+                                               but we don't display to user. */
+                        "sort": false,
+                        "filter": true
+                    };
+                }
+
                 return map;
             },
 
+            "_cleanMapForStore": function(map) {
+                var clean = dojo.clone(map);
+
+                for (var column in clean) {
+                    openils.Util.objectProperties(clean[column]).filter(
+                        function(k) { return k.match(/^_/); }
+                    ).forEach(
+                        function(k) { delete clean[column][k]; }
+                    );
+                }
+
+                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. */
+            "_calculateMapTerminii": function() {
+                function _fm_get_field_def(hint, field) {
+                    /* 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. */
+                    return fieldmapper.IDL.fmclasses[hint].fields.filter(
+                        function(f) { return f.name == field; }
+                    ).shift();
+                }
+
+                function _follow_to_end(hint, path) {
+                    while (field = path.shift()) {
+                        var field_def = _fm_get_field_def(hint, field);
+                        if (field_def["class"])
+                            hint = field_def["class"];
+                        else
+                            break;
+                    }
+
+                    return {
+                        "fmClass": hint,
+                        "name": field,
+                        "label": field_def.label,
+                        "datatype": field_def.datatype
+                    };
+                }
+
+                this.mapTerminii = [];
+                for (var column in this.mapClause) {
+                    var terminus = dojo.mixin(
+                        _follow_to_end(
+                            this.fmClass,
+                            this.mapClause[column].path.split(/\./)
+                        ), {
+                            "simple_name": column,
+                            "isfilter": this.mapClause[column].filter
+                        }
+                    );
+                    if (this.mapClause[column]._label)
+                        terminus.label = this.mapClause[column]._label;
+
+                    this.mapTerminii.push(terminus);
+                }
+            },
+
+            "_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
+                 * (<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
+                 * the raw field name. */
+                var self = this;
+                this.structure[0].cells[0].forEach(
+                    function(header) {
+                        if (!header.name) {
+                            header.name = self.mapTerminii.filter(
+                                function(t) {
+                                    return t.simple_name == header.field;
+                                }
+                            )[0].label;
+                        }
+                    }
+                );
+            },
+
             "constructor": function(args) {
                 dojo.mixin(this, args);
 
@@ -114,14 +209,17 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                         new openils.FlattenerStore({
                             "fmClass": this.fmClass,
                             "fmIdentifier": this.fmIdentifier,
-                            "mapClause":
-                                (this.mapClause || this._generate_map()),
+                            "mapClause": (this.mapClause ||
+                                this._cleanMapForStore(this._generateMap())),
                             "baseSort": this.baseSort,
                             "defaultSort": this.defaultSort
                         })
                     );
                 }
 
+                /* Save original query for further filtering later */
+                this._baseQuery = dojo.clone(this.query);
+
                 if (!this.columnPicker) {
                     this.columnPicker =
                         new openils.widget.GridColumnPicker(
@@ -143,6 +241,44 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                     this._applyEditOnEnter();
                 else if (this.singleEditStyle)
                     this._applySingleEditStyle();
+
+                /* Like AutoGrid's paginator, but we'll never have Back/Next
+                 * links.  Just a place to hold misc links */
+                this._setupLinks();
+            },
+
+            "_setupLinks": function() {
+                this.linkHolder = new dijit.layout.ContentPane();
+                dojo.place(this.linkHolder.domNode, this.domNode, "before");
+
+                if (this.showLoadFilter) {
+                    dojo.require("openils.widget.FlattenerFilterDialog");
+                    this.filterDialog =
+                        new openils.widget.FlattenerFilterDialog({
+                            "fmClass": this.fmClass,
+                            "mapTerminii": this.mapTerminii
+                        });
+
+                    this.filterDialog.onApply = dojo.hitch(
+                        this, function(filter) {
+                            this.filter(
+                                dojo.mixin(filter, this._baseQuery),
+                                true    /* re-render */
+                            );
+                        }
+                    );
+
+                    this.filterDialog.startup();
+                    dojo.create(
+                        "a", {
+                            "innerHTML": "Filter",  /* XXX i18n */
+                            "href": "javascript:void(0);",
+                            "onclick": dojo.hitch(this, function() {
+                                this.filterDialog.show();
+                            })
+                        }, this.linkHolder.domNode
+                    );
+                }
             },
 
             /* ******** below are methods mostly copied but
index 9ed23ce..47cc9e8 100644 (file)
@@ -31,6 +31,7 @@ if (!dojo._hasResource['openils.widget.PCrudFilterDialog']) {
      * org unit selector will not respect selected filters in this dijit, and
      * vice-versa.
      */
+
     dojo.provide('openils.widget.PCrudFilterDialog');
     dojo.require('openils.widget.AutoFieldWidget');
     dojo.require('dijit.form.FilteringSelect');
@@ -40,6 +41,8 @@ if (!dojo._hasResource['openils.widget.PCrudFilterDialog']) {
     dojo.require('openils.Util');
 
     dojo.requireLocalization("openils.widget", "PCrudFilterDialog");
+
+    /* XXX namespace pollution! arg! Fix this whole module sometime. */
     var localeStrings = dojo.i18n.getLocalization(
         "openils.widget", "PCrudFilterDialog"
     );
@@ -410,8 +413,8 @@ if (!dojo._hasResource['openils.widget.PCrudFilterDialog']) {
 
             for (var i = 0; i < param_count; i++) {
                 var widg = new openils.widget.AutoFieldWidget({
-                    "fmClass": this.filter_row_manager.fm_class,
-                    "fmField": this.selected_field,
+                    "fmClass": this.selected_field_fm_class,
+                    "fmField": this.selected_field_fm_field,
                     "parentNode": dojo.create("span", {}, this.value_slot),
                     "dijitArgs": {"scrollOnFocus": false}
                 });
@@ -450,6 +453,15 @@ if (!dojo._hasResource['openils.widget.PCrudFilterDialog']) {
             if (this.field_selector.item) {
                 this.selected_field = value;
                 this.selected_field_type = this.field_selector.item.type;
+
+                /* This is really about supporting flattenergrid, of which
+                 * we're in the superclass (in a sloppy sad way). From now
+                 * on I won't mix this kind of lazy object with Dojo modules. */
+                this.selected_field_fm_field = this.field_selector.item.name;
+                this.selected_field_fm_class =
+                    this.field_selector.item.fmClass ||
+                    this.filter_row_manager.fm_class;
+
                 this._adjust_operator_selector();
                 this._rebuild_value_widgets();
             }
@@ -502,16 +514,52 @@ if (!dojo._hasResource['openils.widget.PCrudFilterDialog']) {
                 this.widgetCache = {};
             },
 
-            /* All we really do here is create a data store out of the fields
-             * from the IDL for our given class, place a few buttons at the
-             * bottom of the dialog, and hand off to PCrudFilterRowManager to
-             * do the actual work.
-             */
+            _buildButtons : function() {
+                var self = this;
 
-            startup : function() {
+                var button_holder = dojo.create(
+                    "div", {
+                        "className": "oils-pcrudfilterdialog-buttonholder"
+                    }, this.domNode
+                );
+
+                new dijit.form.Button(
+                    {
+                        "label": localeStrings.ADD_ROW,
+                        "scrollOnFocus": false, /* almost always better */
+                        "onClick": function() {
+                            self.filter_row_manager.add_row();
+                        }
+                    }, dojo.create("span", {}, button_holder)
+                );
+
+                new dijit.form.Button(
+                    {
+                        "label": localeStrings.APPLY,
+                        "scrollOnFocus": false,
+                        "onClick": function() {
+                            if (self.onApply)
+                                self.onApply(self.filter_row_manager.compile());
+                            self.hide();
+                        }
+                    }, dojo.create("span", {}, button_holder)
+                );
+
+                new dijit.form.Button(
+                    {
+                        "label": localeStrings.CANCEL,
+                        "scrollOnFocus": false,
+                        "onClick": function() {
+                            if (self.onCancel)
+                            self.onCancel();
+                            self.hide();
+                        }
+                    }, dojo.create("span", {}, button_holder)
+                );
+            },
+
+            _buildFieldStore : function() {
                 var self = this;
-                this.inherited(arguments);
-                this.initAutoEnv();
                 var realFieldList = this.sortedFieldList.filter(
                     function(item) { return !(item.virtual || item.nonIdl); }
                 );
@@ -549,51 +597,27 @@ if (!dojo._hasResource['openils.widget.PCrudFilterDialog']) {
                         )
                     }
                 });
+            },
 
-                this.filter_row_manager = new PCrudFilterRowManager(
-                    dojo.create("div", {}, this.domNode),
-                    this.fieldStore, this.fmClass
-                );
+            /* All we really do here is create a data store out of the fields
+             * from the IDL for our given class, place a few buttons at the
+             * bottom of the dialog, and hand off to PCrudFilterRowManager to
+             * do the actual work.
+             */
 
-                var button_holder = dojo.create(
-                    "div", {
-                        "className": "oils-pcrudfilterdialog-buttonholder"
-                    }, this.domNode
-                );
+            startup : function() {
+                var self = this;
+                this.inherited(arguments);
+                this.initAutoEnv();
 
-                new dijit.form.Button(
-                    {
-                        "label": localeStrings.ADD_ROW,
-                        "scrollOnFocus": false, /* almost always better */
-                        "onClick": function() {
-                            self.filter_row_manager.add_row();
-                        }
-                    }, dojo.create("span", {}, button_holder)
-                );
+                this._buildFieldStore();
 
-                new dijit.form.Button(
-                    {
-                        "label": localeStrings.APPLY,
-                        "scrollOnFocus": false,
-                        "onClick": function() {
-                            if (self.onApply)
-                                self.onApply(self.filter_row_manager.compile());
-                            self.hide();
-                        }
-                    }, dojo.create("span", {}, button_holder)
+                this.filter_row_manager = new PCrudFilterRowManager(
+                    dojo.create("div", {}, this.domNode),
+                    this.fieldStore, this.fmClass
                 );
 
-                new dijit.form.Button(
-                    {
-                        "label": localeStrings.CANCEL,
-                        "scrollOnFocus": false,
-                        "onClick": function() {
-                            if (self.onCancel)
-                            self.onCancel();
-                            self.hide();
-                        }
-                    }, dojo.create("span", {}, button_holder)
-                );
+                this._buildButtons();
             }
         }
     );
index bce2f4c..1b004c1 100644 (file)
@@ -100,7 +100,6 @@ if (!dojo._hasResource["openils.widget._GridHelperColumns"]) {
 
             "_startupGridHelperColumns": function() {
                 if (!this.hideLineNumber) {
-                    console.log("line numbers");
                     this.structure[0].cells[0].unshift({
                         "field": "+lineno",
                         "get": function(rowIdx, item) {
@@ -112,7 +111,6 @@ if (!dojo._hasResource["openils.widget._GridHelperColumns"]) {
                     });
                 }
                 if (!this.hideSelector) {
-                    console.log("checkbox selectors");
                     this.structure[0].cells[0].unshift({
                         "field": "+selector",
                         "formatter": dojo.hitch(