Trigger Event Log
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Fri, 18 May 2012 16:12:55 +0000 (12:12 -0400)
committerMike Rylander <mrylander@gmail.com>
Mon, 23 Jul 2012 20:12:11 +0000 (16:12 -0400)
A better, more browsy/filtery way to browse Action Trigger Events
related to holds and circs, which staff users sometimes want to filter
by patron barcode.

This is accessed from various menus around the staff client, notably the
patron interface and the item status interface.

This has better printing capabilities than previous versions of this
interface, too.

There's also a new org unit setting to go with this,
"circ.staff.max_visible_event_age", which if set, should hide any events
older than the configured age.

One new permission: VIEW_TRIGGER_EVENT.  This does what it sounds like
it does.

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Signed-off-by: Bill Erickson <berick@esilibrary.com>

Conflicts (resolved):
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/web/js/dojo/openils/widget/PCrudFilterDialog.js

Signed-off-by: Mike Rylander <mrylander@gmail.com>

19 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.actor-event-log-settings.sql [new file with mode: 0644]
Open-ILS/src/templates/actor/user/event_log.tt2 [new file with mode: 0644]
Open-ILS/src/templates/actor/user/trigger_events.tt2 [deleted file]
Open-ILS/web/css/skin/default.css
Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js
Open-ILS/web/js/dojo/openils/widget/FlattenerFilterPane.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/PCrudFilterPane.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/widget/nls/PCrudFilterPane.js [moved from Open-ILS/web/js/dojo/openils/widget/nls/PCrudFilterDialog.js with 92% similarity]
Open-ILS/web/js/ui/default/actor/user/trigger_events.js [deleted file]
Open-ILS/xul/staff_client/chrome/content/main/constants.js
Open-ILS/xul/staff_client/server/circ/copy_status.js
Open-ILS/xul/staff_client/server/patron/display.js
Open-ILS/xul/staff_client/server/patron/items.js
Open-ILS/xul/staff_client/server/patron/trigger_events.js [deleted file]
Open-ILS/xul/staff_client/server/patron/trigger_events.xul [deleted file]

index 7aabb79..9c0ce4a 100644 (file)
@@ -1101,6 +1101,84 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                </permacrud>
        </class>
 
+       <class id="atul" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action_trigger::user_log" reporter:label="Action Trigger User Log" oils_persist:readonly="true">
+               <oils_persist:source_definition><![CDATA[
+               SELECT  atevdef.hook,
+                       atevdef.name,
+                       atevdef.reactor,
+                       atev.id,
+                       atev.event_def,
+                       atev.add_time,
+                       atev.run_time,
+                       atev.start_time,
+                       atev.update_time,
+                       atev.complete_time,
+                       atev.update_process,
+                       atev.state,
+                       atev.user_data,
+                       atev.template_output,
+                       atev.error_output,
+                       atev.async_output,
+                       targ_circ.id AS target_circ,
+                       targ_ahr.id AS target_hold,
+                       COALESCE(
+                               targ_circ.circ_lib,
+                               targ_ahr.pickup_lib
+                       ) AS perm_lib
+               FROM action_trigger.event atev
+               JOIN action_trigger.event_definition atevdef ON
+                       (atevdef.id = atev.event_def)
+               JOIN action_trigger.hook ath ON
+                       (ath.key = atevdef.hook)
+               LEFT JOIN action.circulation targ_circ ON
+                       (ath.core_type = 'circ' AND targ_circ.id = atev.target)
+               LEFT JOIN action.hold_request targ_ahr ON
+                       (ath.core_type = 'ahr' AND targ_ahr.id = atev.target)
+               WHERE atev.add_time > NOW() - (SELECT MAX(value) FROM (
+                       SELECT value::INTERVAL FROM actor.org_unit_ancestor_setting(
+                               'circ.staff.max_visible_event_age',
+                               COALESCE(targ_circ.circ_lib, targ_ahr.pickup_lib)
+                       ) UNION
+                       SELECT '1000 YEARS'::INTERVAL AS value
+               ) ous)
+               ]]></oils_persist:source_definition>
+               <fields oils_persist:primary="id">
+                       <field reporter:label="Hook" name="hook" reporter:datatype="link" />
+                       <field reporter:label="Name" name="name" reporter:datatype="text" />
+                       <field reporter:label="Reactor" name="reactor" reporter:datatype="text" />
+                       <field reporter:label="Event ID" name="id" reporter:datatype="id" />
+                       <field reporter:label="Event Definition ID" name="event_def" reporter:datatype="int" />
+                       <field reporter:label="Event Add Time" name="add_time" reporter:datatype="timestamp" />
+                       <field reporter:label="Event Run Time" name="run_time" reporter:datatype="timestamp" />
+                       <field reporter:label="Event Start Time" name="start_time" reporter:datatype="timestamp" />
+                       <field reporter:label="Event Update Time" name="update_time" reporter:datatype="timestamp" />
+                       <field reporter:label="Event Complete Time" name="complete_time" reporter:datatype="timestamp" />
+                       <field reporter:label="Event Update PID" name="update_process" reporter:datatype="int" />
+                       <field reporter:label="Event State" name="state" reporter:datatype="text" />
+                       <field reporter:label="Event User Data" name="user_data" reporter:datatype="text" />
+                       <field reporter:label="Event Template Output" name="template_output" reporter:datatype="link" />
+                       <field reporter:label="Event Error Output" name="error_output" reporter:datatype="link" />
+                       <field reporter:label="Event Async Output" name="async_output" reporter:datatype="link" />
+                       <field reporter:label="Target Circulation" name="target_circ" reporter:datatype="link" />
+                       <field reporter:label="Target Hold" name="target_hold" reporter:datatype="link" />
+                       <field reporter:label="Permission Context" name="perm_lib" reporter:datatype="org_unit" />
+               </fields>
+               <links>
+                       <link field="hook" reltype="has_a" key="key" map="" class="ath" />
+                       <link field="template_output" reltype="has_a" key="id" map="" class="ateo" />
+                       <link field="error_output" reltype="has_a" key="id" map="" class="ateo" />
+                       <link field="async_output" reltype="has_a" key="id" map="" class="ateo" />
+                       <link field="target_circ" reltype="has_a" key="id" map="" class="circ" />
+                       <link field="target_hold" reltype="has_a" key="id" map="" class="ahr" />
+                       <link field="perm_lib" reltype="has_a" key="id" map="" class="aou" />
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <retrieve permission="VIEW_TRIGGER_EVENT" context_field="perm_lib" />
+                       </actions>
+               </permacrud>
+       </class>
+
        <class id="aws" controller="open-ils.cstore" oils_obj:fieldmapper="actor::workstation" oils_persist:tablename="actor.workstation" reporter:label="Workstation">
                <fields oils_persist:primary="id" oils_persist:sequence="actor.workstation_id_seq">
                        <field reporter:label="Workstation ID" name="id" reporter:datatype="id"/>
@@ -3442,7 +3520,7 @@ SELECT  usr,
             <link field="usr" reltype="has_a" key="id" map="" class="au"/>
         </links>
        </class>
-       <class id="circ" controller="open-ils.cstore" oils_obj:fieldmapper="action::circulation" oils_persist:tablename="action.circulation" reporter:core="true" reporter:label="Circulation">
+       <class id="circ" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action::circulation" oils_persist:tablename="action.circulation" reporter:core="true" reporter:label="Circulation">
                <fields oils_persist:primary="id" oils_persist:sequence="money.billable_xact_id_seq">
                        <field reporter:label="Check In Library" name="checkin_lib" reporter:datatype="org_unit"/>
                        <field reporter:label="Check In Staff" name="checkin_staff" reporter:datatype="link"/>
@@ -3509,6 +3587,11 @@ SELECT  usr,
                        <link field="aaactsc_entries" reltype="has_many" key="xact" map="" class="aaactsc"/>
                        <link field="aaasc_entries" reltype="has_many" key="xact" map="" class="aaasc"/>
                </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <retrieve permission="VIEW_CIRCULATIONS" context_field="circ_lib" />
+                       </actions>
+               </permacrud>
        </class>
        <class id="combcirc" controller="open-ils.cstore" oils_obj:fieldmapper="action::all_circulation" oils_persist:tablename="action.all_circulation" reporter:core="true" reporter:label="Combined Aged and Active Circulations" oils_persist:readonly="true">
                <fields oils_persist:primary="id" oils_persist:sequence="money.billable_xact_id_seq">
index d988a09..c41aa9d 100644 (file)
@@ -1557,7 +1557,10 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
  ( 533, 'ADMIN_COPY_LOCATION_GROUP', oils_i18n_gettext( 533,
     'Allows a user to create/retrieve/update/delete copy location groups', 'ppl', 'description' )), 
  ( 534, 'ADMIN_USER_ACTIVITY_TYPE', oils_i18n_gettext( 534,
-    'Allows a user to create/retrieve/update/delete user activity types', 'ppl', 'description' ));
+    'Allows a user to create/retrieve/update/delete user activity types', 'ppl', 'description' )),
+( 535, 'VIEW_TRIGGER_EVENT', oils_i18n_gettext( 535,
+    'Allows a user to view circ- and hold-related action/trigger events', 'ppl', 'description'))
+;
 
 
 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 1000);
@@ -10175,7 +10178,24 @@ INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,dat
         'description'
     ),
     'string'
-);
+), (
+    'ui.grid_columns.actor.user.event_log',
+    'gui',
+    FALSE,
+    oils_i18n_gettext(
+        'ui.grid_columns.actor.user.event_log',
+        'User Event Log',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ui.grid_columns.actor.user.event_log',
+        'User Event Log Saved Column Settings',
+        'cust',
+        'description'
+    ),
+    'string'
+) ;
 
 SELECT setval( 'config.sms_carrier_id_seq', 1000 );
 INSERT INTO config.sms_carrier VALUES
@@ -11695,3 +11715,14 @@ VALUES (
     ),
     'integer'
 );
+
+INSERT INTO config.org_unit_setting_type (
+    name, grp, label, description, datatype
+) VALUES (
+    'circ.staff.max_visible_event_age',
+    'circ',
+    'Maximum visible age of User Trigger Events in Staff Interfaces',
+    'If this is unset, staff can view User Trigger Events regardless of age. When this is set to an interval, it represents the age of the oldest possible User Trigger Event that can be viewed.',
+    'interval'
+);
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.actor-event-log-settings.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.actor-event-log-settings.sql
new file mode 100644 (file)
index 0000000..17a6d9b
--- /dev/null
@@ -0,0 +1,46 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.org_unit_setting_type (
+    name, label, grp, description, datatype
+) VALUES (
+    'circ.staff.max_visible_event_age',
+    'Maximum visible age of User Trigger Events in Staff Interfaces',
+    'circ',
+    'If this is unset, staff can view User Trigger Events regardless of age. When this is set to an interval, it represents the age of the oldest possible User Trigger Event that can be viewed.',
+    'interval'
+);
+
+INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
+    'ui.grid_columns.actor.user.event_log',
+    'gui',
+    FALSE,
+    oils_i18n_gettext(
+        'ui.grid_columns.actor.user.event_log',
+        'User Event Log',
+        'cust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ui.grid_columns.actor.user.event_log',
+        'User Event Log Saved Column Settings',
+        'cust',
+        'description'
+    ),
+    'string'
+);
+
+INSERT INTO permission.perm_list ( id, code, description )
+    VALUES (
+        535,
+        'VIEW_TRIGGER_EVENT',
+        oils_i18n_gettext(
+            535,
+            'Allows a user to view circ- and hold-related action/trigger events',
+            'ppl',
+            'description'
+        )
+    );
+
+COMMIT;
diff --git a/Open-ILS/src/templates/actor/user/event_log.tt2 b/Open-ILS/src/templates/actor/user/event_log.tt2
new file mode 100644 (file)
index 0000000..2756c7b
--- /dev/null
@@ -0,0 +1,282 @@
+[% WRAPPER base.tt2 %]
+[% ctx.page_title = "Triggered Event Log" %]
+<script type="text/javascript">
+    dojo.require("dijit.form.Button");
+    dojo.require("dojo.data.ItemFileReadStore");
+    dojo.require("openils.CGI");
+    dojo.require("openils.widget.FlattenerGrid");
+    dojo.require("openils.widget.OrgUnitFilteringSelect");
+    dojo.require("openils.widget.ProgressDialog");
+
+    /* If you want to expand this list of supported core_types, you must also
+       change openils.widget._FSBuilder.CoreType further down, so that it
+       includes labels for these in its data store for filter widget
+       purposes. */
+    var initial_query = {"core_type": ["circ", "ahr"]};
+
+    /* Pre-filter by patron ID or copy ID if those are passed in as CGI
+       parameters.  It's an either-or proposition for now. */
+    var cgi = new openils.CGI();
+    var patron_id = cgi.param("patron_id");
+    var copy_id = cgi.param("copy_id");
+
+    /* These features depends on target_{circ,hold}_{patron,copy}_id fields
+       being present in map_extras later. */
+    if (patron_id) {
+        initial_query["-or"] = {
+            "target_circ_patron_id": patron_id,
+            "target_hold_patron_id": patron_id
+        };
+        openils.Util.addOnLoad(
+            function() { openils.Util.show("patron_specific", "inline"); }
+        );
+    } else if (copy_id) {
+        initial_query["-or"] = {
+            "target_circ_copy_id": copy_id,
+            "target_hold_copy_id": copy_id
+        };
+        openils.Util.addOnLoad(
+            function() { openils.Util.show("copy_specific", "inline"); }
+        );
+    }
+
+    /* This semaphore business exists to prevent a race condition. Both
+       the OrgUnitFilteringSelect and the FlattenerFilterPane try to refresh
+       the grid when they're done loading, but that doesn't need to happen
+       until they're /both/ done loading. */
+    var _filter_semaphore_ctr = 2;
+    function filter_semaphore() { return --_filter_semaphore_ctr <= 0; }
+    function filter_semaphore_callback() {
+        grid.fetchLock = false;
+        grid.filterUi.doApply();
+    }
+
+    /* This and its subclasses exist because *FilterPane expect things that
+       act like AutoFieldWidget, which is a widget /builder/.  */
+    dojo.declare(
+        "openils.widget._FSBuilder", null, {
+            "dijitArgs": null,
+            "parentNode": null,
+            "useCorrectly": true,
+
+            "constructor": function(args) {
+                dojo.mixin(this, args);
+            },
+
+            "build": function() {
+                var dijitArgs = dojo.mixin(
+                    {
+                        "store": this.store,
+                        "query": {},
+                        "labelAttr": "label",
+                        "searchAttr": "label",
+                    }, this.dijitArgs
+                );
+
+                this.widget =
+                    new dijit.form.FilteringSelect(dijitArgs, this.parentNode);
+            }
+        }
+    );
+
+    dojo.declare(
+        "openils.widget._FSBuilder.CoreType",
+        [openils.widget._FSBuilder], {
+            "store": new dojo.data.ItemFileReadStore({
+                "data": {
+                    "identifier": "name",
+                    "items": [  /* XXX i18n */
+                        {"name": "circ", "label": "Circulation"},
+                        {"name": "ahr", "label": "Hold"}
+                    ]
+                }
+            })
+        }
+    );
+
+    dojo.declare(
+        "openils.widget._FSBuilder.EventState",
+        [openils.widget._FSBuilder], {
+            "store": new dojo.data.ItemFileReadStore({
+                "data": {
+                    "identifier": "name",
+                    "items": [  /* XXX i18n */
+                        {"name": "found", "label": "Found"},
+                        {"name": "collected", "label": "Collected"},
+                        {"name": "invalid", "label": "Invalid"},
+                        {"name": "pending", "label": "Pending"},
+                        {"name": "reacting", "label": "Reacting"},
+                        {"name": "complete", "label": "Complete"},
+                        {"name": "error", "label": "Error"}
+                    ]
+                }
+            })
+        }
+    );
+
+    /* Various things with which to initialize FlattenerGrid. */
+    var map_extras = {
+        "core_type": {"path": "hook.core_type", "filter": true},
+        "perm_lib": {"path": "perm_lib", "filter": true},
+        "target_circ_patron_id": {"path": "target_circ.usr", "filter": true},
+        "target_hold_patron_id": {"path": "target_hold.usr", "filter": true},
+        "target_circ_copy_id":{"path":"target_circ.target_copy","filter":true},
+        "target_hold_copy_id":{"path":"target_hold.current_copy","filter":true}
+    };
+
+    var filter_initializers = [
+        {"field": "state", "operator": "=", "value": "complete"},
+        {"field": "core_type", "operator": "=", "value": "circ"}
+    ];
+
+    var filter_widget_builders = {
+        "ath:core_type": openils.widget._FSBuilder.CoreType,
+        "atul:state": openils.widget._FSBuilder.EventState,
+    };
+
+    function print_all() {
+        var n;
+        if ((n = parseInt(prompt('[% l("Up to how many rows?") %]','100'))) > 0)
+            grid.print(n);
+    }
+
+    function act_on_events(id_list, action) {
+        console.log(id_list);
+        var method = "open-ils.actor.user.event." + action + ".batch";
+        if (id_list.length < 1) {
+            alert("[% l('You selected nothing.') %]");
+            return;
+        }
+
+        var count = 0;
+        progress_dialog.show(true);
+        fieldmapper.standardRequest(
+            ["open-ils.actor", method], {
+                "async": true,
+                "params": [openils.User.authtoken, id_list],
+                "onresponse": function(r) {
+                    if (r = openils.Util.readResponse(r)) {
+                        progress_dialog.update(
+                            {"maximum": id_list.length, "progress": ++count}
+                        );
+                    }
+                },
+                "oncomplete": function(r) {
+                    progress_dialog.hide();
+                    r = openils.Util.readResponse(r);
+                    grid.refresh();
+                }
+            }
+        );
+    }
+
+    /* The callback fired when the OrgUnitFilteringSelect is changed */
+    function set_grid_query_from_org_selector() {
+        /* Naughty: shouldn't use _baseQuery like this, but need to rethink
+           multiple competing filtering mechanisms. */
+        grid._baseQuery.perm_lib = aou.descendantNodeList(
+            org_selector.attr("value"), /* as id */ true
+        );
+
+        /* But for the persistent filter UI, this would be grid.refresh() */
+        if (filter_semaphore())   /* avoid race between ou selector and other
+                                     filter thing */
+            filter_semaphore_callback();
+    }
+
+    /* Builds a OrgUnitFilteringSelect limited to org units where you have
+       a given permission. */
+    function prepare_org_selector(perm) {
+        new openils.User().buildPermOrgSelector(
+            perm, org_selector, null,
+            function() {
+                dojo.connect(
+                    org_selector, "onChange", set_grid_query_from_org_selector
+                );
+                set_grid_query_from_org_selector();
+            }
+        );
+    }
+
+    openils.Util.addOnLoad(
+        function() {
+            prepare_org_selector("VIEW_TRIGGER_EVENT");
+        }
+    );
+</script>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <div dojoType="dijit.layout.ContentPane"
+         layoutAlign="top" class="oils-header-panel">
+        <div>
+            [% ctx.page_title %]
+            <span id="copy_specific" class="hidden">([% l('copy specific') %])</span>
+            <span id="patron_specific" class="hidden">([% l('patron specific') %])</span>
+        </div>
+        <div>
+            <button dojoType="dijit.form.Button"
+                onClick="act_on_events(grid.getSelectedIDs(), 'reset');">[% l('Reset Selected Events') %]</button>
+            <button dojoType="dijit.form.Button"
+                onClick="act_on_events(grid.getSelectedIDs(), 'cancel');">[% l('Cancel Selected Events') %]</button>
+            <button dojoType="dijit.form.Button"
+                onClick="grid.printSelected();">[% l('Print Selected Events') %]</button>
+            <button dojoType="dijit.form.Button"
+                onClick="print_all();">[% l('Print All Events') %]</button>
+        </div>
+    </div>
+    <div class="oils-acq-basic-roomy">
+        <label for="org_selector">[% l('Show events at and below') %]:</label>
+        <select
+            id="org_selector" jsId="org_selector"
+            dojoType="openils.widget.OrgUnitFilteringSelect"
+            searchAttr="name" labelAttr="name">
+        </select>
+    </div>
+    <div>
+        <div style="float: left; width: 66%;">
+            <table id="gridNode"
+                jsid="grid"
+                dojoType="openils.widget.FlattenerGrid"
+                columnPersistKey='"ui.grid_columns.actor.user.event_log"'
+                autoHeight="10"
+                selectable="true"
+                editOnEnter="false"
+                showLoadFilter="true"
+                filterAlwaysInDiv="'filter_holder'"
+                filterInitializers="filter_initializers"
+                filterWidgetBuilders="filter_widget_builders"
+                filterSemaphore="filter_semaphore"
+                filterSemaphoreCallback="filter_semaphore_callback"
+                fmClass="'atul'"
+                autoFieldFields="['target_hold','target_circ']"
+                defaultSort="['run_time']"
+                mapExtras="map_extras"
+                fetchLock="true"
+                query="initial_query">
+                <thead>
+                    <tr>
+                        <th field="name" fpath="name" ffilter="true">Event Name</th>
+                        <th field="reactor" fpath="reactor" ffilter="true"></th>
+                        <th field="run_time" fpath="run_time" ffilter="true"></th><!-- XXX TODO formatters for *_time -->
+                        <th field="add_time" fpath="add_time" ffilter="true" _visible="false"></th>
+                        <th field="start_time" fpath="start_time" ffilter="true" _visible="false"></th>
+                        <th field="update_time" fpath="update_time" ffilter="true" _visible="false"></th>
+                        <th field="complete_time" fpath="complete_time" ffilter="true" _visible="false"></th>
+                        <th field="id" fpath="id" ffilter="true" _visible="false"></th>
+                        <th field="state" fpath="state" ffilter="true"></th>
+                        <th field="target_circ_copy_barcode" fpath="target_circ.target_copy.barcode" ffilter="true">Target Circulation - Copy Barcode</th>
+                        <th field="target_circ_copy_title" fpath="target_circ.target_copy.call_number.record.simple_record.title">Target Circulation - Title</th>
+                        <th field="target_circ_copy_author" fpath="target_circ.target_copy.call_number.record.simple_record.author">Target Circulation - Author</th>
+                        <th field="target_circ_patron_barcode" fpath="target_circ.usr.card.barcode" ffilter="true">Target Circulation - Patron Barcode</th>
+                        <th field="target_hold_patron_barcode" fpath="target_hold.usr.card.barcode" ffilter="true">Target Hold - Patron Barcode</th>
+                    </tr>
+                </thead>
+            </table>
+        </div>
+        <div style="float: right; width: 33%;">
+            <div id="filter_holder"></div>
+        </div>
+        <div style="clear: both;"></div>
+    </div>
+</div>
+<div dojoType="openils.widget.ProgressDialog" jsId="progress_dialog"></div>
+[% END %]
diff --git a/Open-ILS/src/templates/actor/user/trigger_events.tt2 b/Open-ILS/src/templates/actor/user/trigger_events.tt2
deleted file mode 100644 (file)
index ec0c2f4..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-[% ctx.page_title = 'Events' %]
-[% WRAPPER base.tt2 %]
-<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/actor/user/trigger_events.js'></script>
-<script type="text/javascript">patronId = '[% ctx.page_args.0 %]'</script>
-
-<div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
-    <div>User Events</div>
-    <div>
-        <button dojoType='dijit.form.Button' onClick='evtCancelSelected()'>Cancel Selected Events</button>
-    </div>
-</div>
-
-
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client" style='height:100%;'> 
-    <table jsId="evtGrid" dojoType="dojox.grid.DataGrid" query="{id: '*'}">
-        <thead> 
-            <tr> 
-                <th field="event_def" get='getField' width='auto'>Event Def.</th> 
-                <th field="hook" get='getField' width='auto'>Hook</th> 
-                <th field="reactor" get='getField' width='auto'>Reactor</th> 
-                <th field="validator" get='getField' width='auto'>Validator</th> 
-                <th field="target" get='getField' width='auto'>Target</th> 
-                <th field="state" get='getField' width='auto'>State</th> 
-            </tr> 
-        </thead> 
-    </table>     
-</div>
-
-[% END %]
index 2098329..af96f07 100644 (file)
@@ -92,6 +92,13 @@ table { border-collapse: collapse; }
 }
 .oils-fm-edit-dialog td { border:1px solid #999;}
 .oils-pcrudfilterdialog-table tr td { padding: 0.75ex 0.5em; }
+.oils-pcrudfilterdialog-table tr:nth-child(event) {
+    background-color: #d7d7d7;
+}
+.oils-pcrudfilterdialog-remover-holder {
+    text-align: left; vertical-align: middle;
+    width: 33%;
+}
 .oils-pcrudfilterdialog-remover {
     background-color: #ccc; color: #f00;
     padding: 0.25em; border: 1px outset #000;
index af6b7df..187b69f 100644 (file)
@@ -1,57 +1,11 @@
 if (!dojo._hasResource["openils.widget.FlattenerFilterDialog"]) {
-    dojo._hasResource["openils.widget.FlattenerFilterDialog"] = true;
-
     dojo.provide("openils.widget.FlattenerFilterDialog");
-    dojo.require("openils.widget.PCrudFilterDialog");
+    dojo.require("openils.widget.FlattenerFilterPane");
+    dojo.require("dijit.Dialog");
 
     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,
-                                    "indirect": item.indirect
-                                };
-                            }
-                        )
-                    }
-                });
-            }
-        }
+        "openils.widget.FlattenerFilterDialog", [
+            dijit.Dialog, openils.widget.FlattenerFilterPane
+        ]
     );
 }
diff --git a/Open-ILS/web/js/dojo/openils/widget/FlattenerFilterPane.js b/Open-ILS/web/js/dojo/openils/widget/FlattenerFilterPane.js
new file mode 100644 (file)
index 0000000..5b74a99
--- /dev/null
@@ -0,0 +1,57 @@
+if (!dojo._hasResource["openils.widget.FlattenerFilterPane"]) {
+    dojo._hasResource["openils.widget.FlattenerFilterPane"] = true;
+
+    dojo.provide("openils.widget.FlattenerFilterPane");
+    dojo.require("openils.widget.PCrudFilterPane");
+
+    dojo.declare(
+        "openils.widget.FlattenerFilterPane",
+        [openils.widget.PCrudFilterPane], {
+            "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,
+                                    "indirect": item.indirect
+                                };
+                            }
+                        )
+                    }
+                });
+            }
+        }
+    );
+}
index 7691789..9a350c5 100644 (file)
@@ -18,10 +18,16 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
             "columnPersistKey": null,
             "autoCoreFields": false,
             "autoFieldFields": null,
-            "showLoadFilter": false,    /* use FlattenerFilterDialog */
+            "showLoadFilter": false,    /* use FlattenerFilter(Dialog|Pane) */
+            "filterAlwaysInDiv": null,  /* use FlattenerFilterPane and put its
+                                           content in this HTML element */
             "fetchLock": false,
+            "filterInitializers": null,
+            "filterWidgetBuilders": null,
+            "filterSemaphore": null,
+            "filterSemaphoreCallback": null,
 
-            /* These potential constructor arguments maybe useful to
+            /* These potential constructor arguments may be useful to
              * FlattenerGrid in their own right, and are passed to
              * FlattenerStore. */
             "fmClass": null,
@@ -346,7 +352,6 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                                                 c.fpath.match(wbp_re);
                                         }
                                     ).length) {
-                                        console.info("adding auto field" + would_be_path);
                                         self.structure[0].cells[0].push({
                                             "field": "AUTO_" + beginning.name +
                                                 "_" + field.name,
@@ -479,14 +484,21 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 dojo.place(this.linkHolder.domNode, this.domNode, "before");
 
                 if (this.showLoadFilter) {
-                    dojo.require("openils.widget.FlattenerFilterDialog");
-                    this.filterDialog =
-                        new openils.widget.FlattenerFilterDialog({
+                    var which_filter_ui = this.filterAlwaysInDiv ?
+                        "FlattenerFilterPane" : "FlattenerFilterDialog";
+
+                    dojo.require("openils.widget." + which_filter_ui);
+                    this.filterUi =
+                        new openils.widget[which_filter_ui]({
                             "fmClass": this.fmClass,
-                            "mapTerminii": this.mapTerminii
+                            "mapTerminii": this.mapTerminii,
+                            "useDiv": this.filterAlwaysInDiv,
+                            "compact": true,
+                            "initializers": this.filterInitializers,
+                            "widgetBuilders": this.filterWidgetBuilders
                         });
 
-                    this.filterDialog.onApply = dojo.hitch(
+                    this.filterUi.onApply = dojo.hitch(
                         this, function(filter) {
                             this.filter(
                                 dojo.mixin(filter, this._baseQuery),
@@ -495,16 +507,23 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                         }
                     );
 
-                    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
-                    );
+                    this.filterUi.startup();
+
+                    if (this.filterSemaphore && this.filterSemaphore()) {
+                        if (this.filterSemaphoreCallback)
+                            this.filterSemaphoreCallback();
+                    }
+                    if (!this.filterAlwaysInDiv) {
+                        dojo.create(
+                            "a", {
+                                "innerHTML": "Filter",  /* XXX i18n */
+                                "href": "javascript:void(0);",
+                                "onclick": dojo.hitch(this, function() {
+                                    this.filterUi.show();
+                                })
+                            }, this.linkHolder.domNode
+                        );
+                    }
                 }
             },
 
@@ -832,15 +851,24 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 );
             },
 
+            "getSelectedIDs": function() {
+                return this.getSelectedItems().map(
+                    dojo.hitch(
+                        this,
+                        function(item) { return this.store.getIdentity(item); }
+                    )
+                );
+            },
+
             /* Print the same data that the Flattener is feeding to the
-             * grid, sorted the same way too. remove limit and offset (i.e.,
-             * print it all. */
-            "print": function() {
+             * grid, sorted the same way too. Remove limit and offset (i.e.,
+             * print it all) unless those are passed in to the print() method.
+             */
+            "print": function(limit, offset, query_mixin) {
                 var coal = this._columnOrderingAndLabels();
                 var req = {
-                    "query": this.query,
+                    "query": dojo.mixin({}, this.query, query_mixin),
                     "queryOptions": {
-                        "all": true,
                         "columns": coal.columns,
                         "labels": coal.labels
                     },
@@ -849,7 +877,22 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                     }
                 };
 
+                if (limit) {
+                    req.count = limit;
+                    req.start = offset || 0;
+                } else {
+                    req.queryOptions.all = true;
+                }
+
                 this.store.fetchToPrint(req);
+            },
+
+            "printSelected": function() {
+                var id_blob = {};
+                id_blob[this.store.getIdentityAttributes()[0]] =
+                    this.getSelectedIDs();
+
+                this.print(null, null, id_blob);
             }
         }
     );
index 09a26fb..6adf45c 100644 (file)
-if (!dojo._hasResource['openils.widget.PCrudFilterDialog']) {
-
-    /* openils.widget.PCrudFilterDialog is a dijit that, given a fieldmapper
-     * class, provides a dialog in which users can define inclusionary
-     * filters based on fields selected from the fieldmapper class and values
-     * for those fields.  Operators can be selected so that not only equality
-     * comparisons are possible in the filter, but also inequality filters,
-     * likeness (for text fields only) betweenness, and nullity tests.
-     *
-     * The dijit yields its result in the form of a JSON query suitable for
-     * use as the where clause of a pcrud search, via the onApply callback.
-     *
-     * In addition to its fmClass paramter, note the useful parameter
-     * suppressFilterFields.  Say for instance you're using this dijit
-     * on an fmClass like "brt" which has a field "record" that points to the
-     * bre class.  The AutoWidget provided for users to enter values for
-     * comparisons on the record field would be a dropdown containing all
-     * the bre ID's in the system!  That would be unusable in any realistic
-     * system, unless/until we teach AutoWidget to use a lazy-loading store
-     * for dropdowns.
-     *
-     * The comparisons in each filter row are "and-ed" together in the JSON
-     * query yielded, except for repetitions of the same field, which are
-     * "or-ed" together /within/ the overall "and" group.  Look at comments
-     * within PCrudFilterRowManager.compile() for more information.
-     *
-     * AutoGrid has some ability to use this dijit to offer a filtering dialog,
-     * but be aware that the filtering dialog is /not/ aware of other
-     * fitering measures in place in a given AutoGrid-based interface, such as
-     * (typically) context org unit selectors, and therefore using the context
-     * 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');
-    dojo.require('dijit.form.Button');
-    dojo.require('dojo.data.ItemFileReadStore');
-    dojo.require('dijit.Dialog');
-    dojo.require('openils.Util');
-
-    dojo.requireLocalization("openils.widget", "PCrudFilterDialog");
-
-    var pcFilterLocaleStrings = dojo.i18n.getLocalization(
-        "openils.widget", "PCrudFilterDialog"
-    );
-
-    /* These are the operators that make up the central dropdown in each
-     * row of the widget.  When fields of different datatypes are selected,
-     * some of these operators may be masked via the "minimal" and "strict"
-     * properties.
-     */
-    var _operator_store = new dojo.data.ItemFileReadStore(
-        {
-            "data": {
-                "identifier": "name",
-                "items": [
-                    {
-                        "name": "=",
-                        "label": pcFilterLocaleStrings.OPERATOR_EQ,
-                        "param_count": 1,
-                        "minimal": true,
-                        "strict": true
-                    }, {
-                        "name": "!=",
-                        "label": pcFilterLocaleStrings.OPERATOR_NE,
-                        "param_count": 1,
-                        "minimal": true,
-                        "strict": true
-                    }, {
-                        "name": "null",
-                        "label": pcFilterLocaleStrings.OPERATOR_IS_NULL,
-                        "param_count": 0,
-                        "minimal": true,
-                        "strict": true
-                    }, {
-                        "name": "not null",
-                        "label": pcFilterLocaleStrings.OPERATOR_IS_NOT_NULL,
-                        "param_count": 0,
-                        "minimal": true,
-                        "strict": true
-                    }, {
-                        "name": ">",
-                        "label": pcFilterLocaleStrings.OPERATOR_GT,
-                        "param_count": 1,
-                        "strict": true
-                    }, {
-                        "name": "<",
-                        "label": pcFilterLocaleStrings.OPERATOR_LT,
-                        "param_count": 1,
-                        "strict": true
-                    }, {
-                        "name": ">=",
-                        "label": pcFilterLocaleStrings.OPERATOR_GTE,
-                        "param_count": 1,
-                        "strict": true
-                    }, {
-                        "name": "<=",
-                        "label": pcFilterLocaleStrings.OPERATOR_LTE,
-                        "param_count": 1,
-                        "strict": true
-                    }, {
-                        "name": "between",
-                        "label": pcFilterLocaleStrings.OPERATOR_BETWEEN,
-                        "param_count": 2,
-                        "strict": true
-                    }, {
-                        "name": "not between",
-                        "label": pcFilterLocaleStrings.OPERATOR_NOT_BETWEEN,
-                        "param_count": 2,
-                        "strict": true
-                    }, {
-                        "name": "like",
-                        "label": pcFilterLocaleStrings.OPERATOR_LIKE,
-                        "param_count": 1
-                    }, {
-                        "name": "not like",
-                        "label": pcFilterLocaleStrings.OPERATOR_NOT_LIKE,
-                        "param_count": 1
-                    }
-                ]
-            }
-        }
-    );
-
-    /* The text datatype supports all the above operators for comparisons. */
-    var _store_query_by_datatype = {"text": {}};
-
-    /* These three datatypes support only minimal operators. */
-    ["bool", "link", "org_unit"].forEach(
-        function(type) {
-            _store_query_by_datatype[type] = {"minimal": true};
-        }
-    );
-
-    /* These datatypes support strict operators (everything save [not] like). */
-    ["float", "id", "int", "interval", "money", "number", "timestamp"].forEach(
-        function(type) {
-            _store_query_by_datatype[type] = {"strict": true};
-        }
-    );
-
-    /* This helps convert things that pcrud won't accept ("not between", "not
-     * like") into proper JSON query expressions.
-     * It returns false if a clause doesn't have any such negative operator,
-     * or it returns true AND gets rid of the "not " part in the clause
-     * object itself.  It's up to the caller to wrap it in {"-not": {}} in
-     * the right place. */
-    function _clause_was_negative(clause) {
-        /* clause objects really only ever have one property */
-        var ops = openils.Util.objectProperties(clause);
-        var op = ops.pop();
-        var matches = op.match(/^not (\w+)$/);
-        if (matches) {
-            clause[matches[1]] = clause[op];
-            delete clause[op];
-            return true;
-        }
-        return false;
-    }
-
-    /* This is not the dijit per se. Search further in this file for
-     * "dojo.declare" for the beginning of the dijit.
-     *
-     * This is, however, the object that represents a collection of filter
-     * rows and knows how to compile a filter from those rows. */
-    function PCrudFilterRowManager() {
-        var self = this;
-
-        this._init = function(container, field_store, fm_class) {
-            this.container = container;
-            this.field_store = field_store;
-            this.fm_class = fm_class;
-
-            this.rows = {};
-            this.row_index = 0;
-
-            this._build_table();
-        };
-
-        this._build_table = function() {
-            this.table = dojo.create(
-                "table", {
-                    "className": "oils-pcrudfilterdialog-table"
-                }, this.container
-            );
-
-            var tr = dojo.create(
-                "tr", {
-                    "id": "pcrudfilterdialog-empty",
-                    "className": "hidden"
-                }, this.table
-            );
-
-            dojo.create(
-                "td", {
-                    "colspan": 4,
-                    "innerHTML": pcFilterLocaleStrings.EMPTY_CASE
-                }, tr
-            );
-
-            this.add_row();
-        };
-
-        this._compile_second_pass = function(first_pass) {
-            var and = [];
-            var result = {"-and": and};
-
-            for (var field in first_pass) {
-                var list = first_pass[field];
-                if (list.length == 1) {
-                    var obj = {};
-                    var clause = list.pop();
-                    if (_clause_was_negative(clause)) {
-                        obj["-not"] = {};
-                        obj["-not"][field] = clause;
-                    } else {
-                        obj[field] = clause;
-                    }
-                    and.push(obj);
-                } else {
-                    var or = list.map(
-                        function(clause) {
-                            var obj = {};
-                            if (_clause_was_negative(clause)) {
-                                obj["-not"] = {};
-                                obj["-not"][field] = clause;
-                            } else {
-                                obj[field] = clause;
-                            }
-                            return obj;
-                        }
-                    );
-                    and.push({"-or": or});
-                }
-            }
-
-            return result;
-        };
-
-        this.add_row = function() {
-            this.hide_empty_placeholder();
-            var row_id = this.row_index++;
-            this.rows[row_id] = new PCrudFilterRow(this, row_id);
-        };
-
-        this.remove_row = function(row_id) {
-            this.rows[row_id].destroy();
-            delete this.rows[row_id];
-
-            if (openils.Util.objectProperties(this.rows).length < 1)
-                this.show_empty_placeholder();
-        };
-
-        this.hide_empty_placeholder = function() {
-            openils.Util.hide("pcrudfilterdialog-empty");
-        };
-
-        this.show_empty_placeholder = function() {
-            openils.Util.show("pcrudfilterdialog-empty");
-        };
-
-        this.compile = function() {
-            /* We'll prepare a first-pass data structure that looks like:
-             * {
-             *  field1: [{"op": "one value"}],
-             *  field2: [{"op": "a value"}, {"op": "b value"}],
-             *  field3: [{"op": "first value"}, {"op": ["range start", "range end"]}]
-             * }
-             *
-             * which will be passed to _compile_second_pass() to yield an
-             * actual filter suitable for pcrud (with -and and -or in all the
-             * right places) so the above example would come out like:
-             *
-             * { "-and": [
-             *   {"field1": {"op": "one value"}},
-             *   {"-or": [ {"field2": {"op": "a value"}}, {"field2": {"op": "b value"}} ] },
-             *   {"-or": [
-             *     {"field3": {"op": "first value"}},
-             *     {"field3": {"op": ["range start", "range end"]}}
-             *   ] }
-             * ] }
-             */
-            var first_pass = {};
-
-            for (var row_id in this.rows) {
-                var row = this.rows[row_id];
-                var value = row.compile();
-                var field = row.selected_field;
-
-                if (typeof(value) != "undefined" &&
-                    typeof(field) != "undefined") {
-                    if (!first_pass[field])
-                        first_pass[field] = [];
-                    first_pass[field].push(value);
-                }
-            }
-
-            /* Don't return an empty filter: pcrud can't use that. */
-            if (openils.Util.objectProperties(first_pass).length < 1) {
-                var result = {};
-                result[fieldmapper[this.fm_class].Identifier] = {"!=": null};
-                return result;
-            } else {
-                return this._compile_second_pass(first_pass);
-            }
-        };
-
-        this._init.apply(this, arguments);
-    }
-
-    /* As the name implies, objects of this class manage a single row of the
-     * query.  Therefore they know about their own field dropdown, their own
-     * selector dropdown, and their own value widget (or widgets in the case
-     * of between searches, which call for two widgets to define a range),
-     * and not much else. */
-    function PCrudFilterRow() {
-        var self = this;
-
-        this._init = function(filter_row_manager, row_id) {
-            this.filter_row_manager = filter_row_manager;
-            this.row_id = row_id;
-
-            this._build();
-        };
-
-        this._build = function() {
-            this.tr = dojo.create("tr", {}, this.filter_row_manager.table);
-
-            this._create_field_selector();
-            this._create_operator_selector();
-            this._create_value_slot();
-            this._create_remover();
-        };
-
-        this._create_field_selector = function() {
-            var td = dojo.create("td", {}, this.tr);
-            this.field_selector = new dijit.form.FilteringSelect(
-                {
-                    "labelAttr": "label",
-                    "searchAttr": "label",
-                    "scrollOnFocus": false,
-                    "onChange": function(value) {
-                        self.update_selected_field(value);
-                    },
-                    "store": this.filter_row_manager.field_store
-                }, dojo.create("span", {}, td)
-            );
-        };
-
-        this._create_operator_selector = function() {
-            var td = dojo.create("td", {}, this.tr);
-            this.operator_selector = new dijit.form.FilteringSelect(
-                {
-                    "labelAttr": "label",
-                    "searchAttr": "label",
-                    "scrollOnFocus": false,
-                    "onChange": function(value) {
-                        self.update_selected_operator(value);
-                    },
-                    "store": _operator_store
-                }, dojo.create("span", {}, td)
-            );
-        };
-
-        this._adjust_operator_selector = function() {
-            this.operator_selector.attr(
-                "query", _store_query_by_datatype[this.selected_field_type]
-            );
-            this.operator_selector.reset();
-        };
-
-        this._create_value_slot = function() {
-            this.value_slot = dojo.create("td", {"innerHTML": "-"}, this.tr);
-        };
-
-        this._create_remover = function() {
-            var td = dojo.create("td", {}, this.tr);
-            var anchor = dojo.create(
-                "a", {
-                    "className": "oils-pcrudfilterdialog-remover",
-                    "innerHTML": "X",
-                    "href": "#",
-                    "onclick": function() {
-                        self.filter_row_manager.remove_row(self.row_id);
-                    }
-                }, td
-            );
-        };
-
-        this._clear_value_slot = function() {
-            if (this.value_widgets) {
-                this.value_widgets.forEach(
-                    function(autowidg) { autowidg.widget.destroy(); }
-                );
-                delete this.value_widgets;
-            }
-
-            dojo.empty(this.value_slot);
-        };
-
-        this._rebuild_value_widgets = function() {
-            this._clear_value_slot();
-
-            if (!this.get_selected_operator() || !this.selected_field)
-                return;
-
-            this.value_widgets = [];
-
-            var param_count = this.operator_selector.item.param_count;
-
-            for (var i = 0; i < param_count; i++) {
-                var widg = new openils.widget.AutoFieldWidget({
-                    "fmClass": this.selected_field_fm_class,
-                    "fmField": this.selected_field_fm_field,
-                    "parentNode": dojo.create("span", {}, this.value_slot),
-                    "dijitArgs": {"scrollOnFocus": false}
-                });
-
-                widg.build();
-                this.value_widgets.push(widg);
-            }
-        };
-
-        /* for ugly special cases in compliation */
-        this._null_clause = function() {
-            var opname = this.get_selected_operator_name();
-            if (opname == "not null")
-                return {"!=": null};
-            else if (opname == "null")
-                return null;
-            else
-                return;
-        };
-
-        this.get_selected_operator = function() {
-            if (this.operator_selector)
-                return this.operator_selector.item;
-        };
-
-        this.get_selected_operator_name = function() {
-            var op = this.get_selected_operator();
-            return op ? op.name : null;
-        };
-
-        this.update_selected_operator = function(value) {
-            this._rebuild_value_widgets();
-        };
-
-        this.update_selected_field = function(value) {
-            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. */
-                //console.log(dojo.toJson(this.field_selector.item));
-                this.selected_field_fm_field = this.field_selector.item.name;
-                this.selected_field_is_indirect =
-                    this.field_selector.item.indirect || false;
-                this.selected_field_fm_class =
-                    this.field_selector.item.fmClass ||
-                    this.filter_row_manager.fm_class;
-
-                this._adjust_operator_selector();
-                this._rebuild_value_widgets();
-            }
-        };
-
-        this.compile = function() {
-            if (this.value_widgets) {
-                var values = this.value_widgets.map(
-                    function(widg) {
-                        return self.selected_field_is_indirect ?
-                            widg.widget.attr('displayedValue') :
-                            widg.getFormattedValue();
-                    }
-                );
-
-                if (!values.length) {
-                    return this._null_clause(); /* null/not null */
-                } else {
-                    var clause = {};
-                    var op = this.get_selected_operator_name();
-                    if (values.length == 1)
-                        clause[op] = values.pop();
-                    else
-                        clause[op] = values;
-                    return clause;
-                }
-            } else {
-                return;
-            }
-        };
-
-        this.destroy = function() {
-            this._clear_value_slot();
-            this.field_selector.destroy();
-            if (this.operator_selector)
-                this.operator_selector.destroy();
-
-            dojo.destroy(this.tr);
-        };
-
-        this._init.apply(this, arguments);
-    }
+if (!dojo._hasResource["openils.widget.PCrudFilterDialog"]) {
+    dojo.provide("openils.widget.PCrudFilterDialog");
+    dojo.require("openils.widget.PCrudFilterPane");
+    dojo.require("dijit.Dialog");
 
     dojo.declare(
-        'openils.widget.PCrudFilterDialog',
-        [dijit.Dialog, openils.widget.AutoWidget],
-        {
-
-            constructor : function(args) {
-                for(var k in args)
-                    this[k] = args[k];
-                this.title = this.title || pcFilterLocaleStrings.DEFAULT_DIALOG_TITLE;
-                this.widgetIndex = 0;
-                this.widgetCache = {};
-            },
-
-            _buildButtons : function() {
-                var self = this;
-
-                var button_holder = dojo.create(
-                    "div", {
-                        "className": "oils-pcrudfilterdialog-buttonholder"
-                    }, this.domNode
-                );
-
-                new dijit.form.Button(
-                    {
-                        "label": pcFilterLocaleStrings.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": pcFilterLocaleStrings.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": pcFilterLocaleStrings.CANCEL,
-                        "scrollOnFocus": false,
-                        "onClick": function() {
-                            if (self.onCancel)
-                            self.onCancel();
-                            self.hide();
-                        }
-                    }, dojo.create("span", {}, button_holder)
-                );
-            },
-
-            _buildFieldStore : function() {
-                var self = this;
-                var realFieldList = this.sortedFieldList.filter(
-                    function(item) { return !(item.virtual || item.nonIdl); }
-                );
-
-                /* Prevent any explicitly unwanted fields from being available
-                 * in our field dropdowns. */
-                if (dojo.isArray(this.suppressFilterFields)) {
-                    realFieldList = realFieldList.filter(
-                        function(item) {
-                            for (
-                                var i = 0;
-                                i < self.suppressFilterFields.length;
-                                i++
-                            ) {
-                                if (item.name == self.suppressFilterFields[i])
-                                    return false;
-                            }
-                            return true;
-                        }
-                    );
-                }
-
-                this.fieldStore = new dojo.data.ItemFileReadStore({
-                    "data": {
-                        "identifier": "name",
-                        "name": "label",
-                        "items": realFieldList.map(
-                            function(item) {
-                                return {
-                                    "label": item.label,
-                                    "name": item.name,
-                                    "type": item.datatype
-                                };
-                            }
-                        )
-                    }
-                });
-            },
-
-            /* 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.
-             */
-
-            startup : function() {
-                var self = this;
-                this.inherited(arguments);
-                this.initAutoEnv();
-
-                this._buildFieldStore();
-
-                this.filter_row_manager = new PCrudFilterRowManager(
-                    dojo.create("div", {}, this.domNode),
-                    this.fieldStore, this.fmClass
-                );
-
-                this._buildButtons();
-            }
-        }
+        "openils.widget.PCrudFilterDialog", [
+            dijit.Dialog, openils.widget.PCrudFilterPane
+        ]
     );
 }
diff --git a/Open-ILS/web/js/dojo/openils/widget/PCrudFilterPane.js b/Open-ILS/web/js/dojo/openils/widget/PCrudFilterPane.js
new file mode 100644 (file)
index 0000000..eb34315
--- /dev/null
@@ -0,0 +1,756 @@
+if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
+
+    /* openils.widget.PCrudFilterPane is a dijit that, given a fieldmapper
+     * class, provides a pane in which users can define inclusionary
+     * filters based on fields selected from the fieldmapper class and values
+     * for those fields.  Operators can be selected so that not only equality
+     * comparisons are possible in the filter, but also inequality filters,
+     * likeness (for text fields only) betweenness, and nullity tests.  *
+     * The dijit yields its result in the form of a JSON query suitable for
+     * use as the where clause of a pcrud search, via the onApply callback.
+     *
+     * In addition to its fmClass paramter, note the useful parameter
+     * suppressFilterFields.  Say for instance you're using this dijit
+     * on an fmClass like "brt" which has a field "record" that points to the
+     * bre class.  The AutoWidget provided for users to enter values for
+     * comparisons on the record field would be a dropdown containing all
+     * the bre ID's in the system!  That would be unusable in any realistic
+     * system, unless/until we teach AutoWidget to use a lazy-loading store
+     * for dropdowns.
+     *
+     * The comparisons in each filter row are "and-ed" together in the JSON
+     * query yielded, except for repetitions of the same field, which are
+     * "or-ed" together /within/ the overall "and" group.  Look at comments
+     * within PCrudFilterRowManager.compile() for more information.
+     *
+     * AutoGrid has some ability to use this dijits based on this to offer a
+     * filtering dialog, but be aware that the filtering dialog is /not/ aware
+     * of other fitering measures in place in a given AutoGrid-based interface,
+     * such as (typically) context org unit selectors, and therefore using the
+     * context org unit selector will not respect selected filters in this
+     * dijit, and vice-versa.
+     */
+
+    dojo.provide('openils.widget.PCrudFilterPane');
+    dojo.require('openils.widget.AutoFieldWidget');
+    dojo.require('dijit.form.FilteringSelect');
+    dojo.require('dijit.form.Button');
+    dojo.require('dojo.data.ItemFileReadStore');
+    dojo.require('openils.Util');
+
+    dojo.requireLocalization("openils.widget", "PCrudFilterPane");
+
+    /* XXX namespace pollution! arg! Fix this whole module sometime. */
+    var localeStrings = dojo.i18n.getLocalization(
+        "openils.widget", "PCrudFilterPane"
+    );
+
+    /* These are the operators that make up the central dropdown in each
+     * row of the widget.  When fields of different datatypes are selected,
+     * some of these operators may be masked via the "minimal" and "strict"
+     * properties.
+     */
+    var _operator_store = new dojo.data.ItemFileReadStore(
+        {
+            "data": {
+                "identifier": "name",
+                "items": [
+                    {
+                        "name": "=",
+                        "label": localeStrings.OPERATOR_EQ,
+                        "param_count": 1,
+                        "minimal": true,
+                        "strict": true
+                    }, {
+                        "name": "!=",
+                        "label": localeStrings.OPERATOR_NE,
+                        "param_count": 1,
+                        "minimal": true,
+                        "strict": true
+                    }, {
+                        "name": "null",
+                        "label": localeStrings.OPERATOR_IS_NULL,
+                        "param_count": 0,
+                        "minimal": true,
+                        "strict": true
+                    }, {
+                        "name": "not null",
+                        "label": localeStrings.OPERATOR_IS_NOT_NULL,
+                        "param_count": 0,
+                        "minimal": true,
+                        "strict": true
+                    }, {
+                        "name": ">",
+                        "label": localeStrings.OPERATOR_GT,
+                        "param_count": 1,
+                        "strict": true
+                    }, {
+                        "name": "<",
+                        "label": localeStrings.OPERATOR_LT,
+                        "param_count": 1,
+                        "strict": true
+                    }, {
+                        "name": ">=",
+                        "label": localeStrings.OPERATOR_GTE,
+                        "param_count": 1,
+                        "strict": true
+                    }, {
+                        "name": "<=",
+                        "label": localeStrings.OPERATOR_LTE,
+                        "param_count": 1,
+                        "strict": true
+                    }, {
+                        "name": "between",
+                        "label": localeStrings.OPERATOR_BETWEEN,
+                        "param_count": 2,
+                        "strict": true
+                    }, {
+                        "name": "not between",
+                        "label": localeStrings.OPERATOR_NOT_BETWEEN,
+                        "param_count": 2,
+                        "strict": true
+                    }, {
+                        "name": "like",
+                        "label": localeStrings.OPERATOR_LIKE,
+                        "param_count": 1
+                    }, {
+                        "name": "not like",
+                        "label": localeStrings.OPERATOR_NOT_LIKE,
+                        "param_count": 1
+                    }
+                ]
+            }
+        }
+    );
+
+    /* The text datatype supports all the above operators for comparisons. */
+    var _store_query_by_datatype = {"text": {}};
+
+    /* These three datatypes support only minimal operators. */
+    ["bool", "link", "org_unit"].forEach(
+        function(type) {
+            _store_query_by_datatype[type] = {"minimal": true};
+        }
+    );
+
+    /* These datatypes support strict operators (everything save [not] like). */
+    ["float", "id", "int", "interval", "money", "number", "timestamp"].forEach(
+        function(type) {
+            _store_query_by_datatype[type] = {"strict": true};
+        }
+    );
+
+    /* This helps convert things that pcrud won't accept ("not between", "not
+     * like") into proper JSON query expressions.
+     * It returns false if a clause doesn't have any such negative operator,
+     * or it returns true AND gets rid of the "not " part in the clause
+     * object itself.  It's up to the caller to wrap it in {"-not": {}} in
+     * the right place. */
+    function _clause_was_negative(clause) {
+        /* clause objects really only ever have one property */
+        var ops = openils.Util.objectProperties(clause);
+        var op = ops.pop();
+        var matches = op.match(/^not (\w+)$/);
+        if (matches) {
+            clause[matches[1]] = clause[op];
+            delete clause[op];
+            return true;
+        }
+        return false;
+    }
+
+    /* This is not the dijit per se. Search further in this file for
+     * "dojo.declare" for the beginning of the dijit.
+     *
+     * This is, however, the object that represents a collection of filter
+     * rows and knows how to compile a filter from those rows. */
+    function PCrudFilterRowManager() {
+        var self = this;
+
+        this._init = function(
+            container, field_store, fm_class, compact, widget_builders,
+            skip_first_add_row, do_apply
+        ) {
+            this.container = container;
+            this.field_store = field_store;
+            this.fm_class = fm_class;
+            this.compact = compact;
+            this.widget_builders = widget_builders || {};
+            this.skip_first_add_row = skip_first_add_row;
+            this.do_apply = do_apply;
+
+            this.rows = {};
+            this.row_index = 0;
+
+            this._build_table();
+        };
+
+        this._build_table = function() {
+            this.table = dojo.create(
+                "table", {
+                    "className": "oils-pcrudfilterdialog-table"
+                }, this.container
+            );
+
+            var tr = dojo.create(
+                "tr", {
+                    "id": "pcrudfilterdialog-empty",
+                    "className": "hidden"
+                }, this.table
+            );
+
+            dojo.create(
+                "td", {
+                    "colspan": 4,
+                    "innerHTML": localeStrings[
+                        this.compact ? "EMPTY_CASE_COMPACT" : "EMPTY_CASE"
+                    ]
+                }, tr
+            );
+
+            if (!this.skip_first_add_row)
+                this.add_row();
+        };
+
+        this._compile_second_pass = function(first_pass) {
+            var and = [];
+            var result = {"-and": and};
+
+            for (var field in first_pass) {
+                var list = first_pass[field];
+                if (list.length == 1) {
+                    var obj = {};
+                    var clause = list.pop();
+                    if (_clause_was_negative(clause)) {
+                        obj["-not"] = {};
+                        obj["-not"][field] = clause;
+                    } else {
+                        obj[field] = clause;
+                    }
+                    and.push(obj);
+                } else {
+                    var or = list.map(
+                        function(clause) {
+                            var obj = {};
+                            if (_clause_was_negative(clause)) {
+                                obj["-not"] = {};
+                                obj["-not"][field] = clause;
+                            } else {
+                                obj[field] = clause;
+                            }
+                            return obj;
+                        }
+                    );
+                    and.push({"-or": or});
+                }
+            }
+
+            return result;
+        };
+
+        this.add_row = function(initializer) {
+            this.hide_empty_placeholder();
+            var row_id = this.row_index++;
+            this.rows[row_id] = new PCrudFilterRow(this, row_id, initializer);
+        };
+
+        this.remove_row = function(row_id) {
+            this.rows[row_id].destroy();
+            delete this.rows[row_id];
+
+            if (openils.Util.objectProperties(this.rows).length < 1)
+                this.show_empty_placeholder();
+
+            if (this.compact)
+                this.do_apply();
+        };
+
+        this.hide_empty_placeholder = function() {
+            openils.Util.hide("pcrudfilterdialog-empty");
+        };
+
+        this.show_empty_placeholder = function() {
+            openils.Util.show("pcrudfilterdialog-empty");
+        };
+
+        this.compile = function() {
+            /* We'll prepare a first-pass data structure that looks like:
+             * {
+             *  field1: [{"op": "one value"}],
+             *  field2: [{"op": "a value"}, {"op": "b value"}],
+             *  field3: [{"op": "first value"}, {"op": ["range start", "range end"]}]
+             * }
+             *
+             * which will be passed to _compile_second_pass() to yield an
+             * actual filter suitable for pcrud (with -and and -or in all the
+             * right places) so the above example would come out like:
+             *
+             * { "-and": [
+             *   {"field1": {"op": "one value"}},
+             *   {"-or": [ {"field2": {"op": "a value"}}, {"field2": {"op": "b value"}} ] },
+             *   {"-or": [
+             *     {"field3": {"op": "first value"}},
+             *     {"field3": {"op": ["range start", "range end"]}}
+             *   ] }
+             * ] }
+             */
+            var first_pass = {};
+
+            for (var row_id in this.rows) {
+                var row = this.rows[row_id];
+                var value = row.compile();
+                var field = row.selected_field;
+
+                if (typeof(value) != "undefined" &&
+                    typeof(field) != "undefined") {
+                    if (!first_pass[field])
+                        first_pass[field] = [];
+                    first_pass[field].push(value);
+                }
+            }
+
+            /* Don't return an empty filter: pcrud can't use that. */
+            if (openils.Util.objectProperties(first_pass).length < 1) {
+                var result = {};
+                result[fieldmapper[this.fm_class].Identifier] = {"!=": null};
+                return result;
+            } else {
+                return this._compile_second_pass(first_pass);
+            }
+        };
+
+        this._init.apply(this, arguments);
+    }
+
+    /* As the name implies, objects of this class manage a single row of the
+     * query.  Therefore they know about their own field dropdown, their own
+     * selector dropdown, and their own value widget (or widgets in the case
+     * of between searches, which call for two widgets to define a range),
+     * and not much else. */
+    function PCrudFilterRow() {
+        var self = this;
+
+        this._init = function(filter_row_manager, row_id, initializer) {
+            this.filter_row_manager = filter_row_manager;
+            this.row_id = row_id;
+
+            if (this.filter_row_manager.compact)
+                this._build_compact();
+            else
+                this._build();
+
+            if (initializer)
+                this.initialize(initializer);
+        };
+
+        this._build = function() {
+            this.tr = dojo.create("tr", {}, this.filter_row_manager.table);
+
+            this._create_field_selector();
+            this._create_operator_selector();
+            this._create_value_slot();
+            this._create_remover();
+        };
+
+        this._build_compact = function() {
+            this.tr = dojo.create("tr", {}, this.filter_row_manager.table);
+
+            var td = dojo.create("td", {}, this.tr);
+
+            this._create_field_selector(td);
+            this._create_operator_selector(td);
+
+            dojo.create("br", {}, td);
+            this._create_value_slot(td);
+
+            td = dojo.create(
+                "td",
+                {"className": "oils-pcrudfilterdialog-remover-holder"},
+                this.tr
+            );
+
+            this._create_remover(td);
+        };
+
+        this._create_field_selector = function(use_element) {
+            var td = use_element || dojo.create("td", {}, this.tr);
+
+            this.field_selector = new dijit.form.FilteringSelect(
+                {
+                    "labelAttr": "label",
+                    "searchAttr": "label",
+                    "scrollOnFocus": false,
+                    "onChange": function(value) {
+                        self.update_selected_field(value);
+                    },
+                    "store": this.filter_row_manager.field_store
+                }, dojo.create("span", {}, td)
+            );
+        };
+
+        this._create_operator_selector = function(use_element) {
+            var td = use_element || dojo.create("td", {}, this.tr);
+
+            this.operator_selector = new dijit.form.FilteringSelect(
+                {
+                    "labelAttr": "label",
+                    "searchAttr": "label",
+                    "scrollOnFocus": false,
+                    "onChange": function(value) {
+                        self.update_selected_operator(value);
+                    },
+                    "store": _operator_store
+                }, dojo.create("span", {}, td)
+            );
+        };
+
+        this._adjust_operator_selector = function() {
+            this.operator_selector.attr(
+                "query", _store_query_by_datatype[this.selected_field_type]
+            );
+            this.operator_selector.reset();
+        };
+
+        this._create_value_slot = function(use_element) {
+            if (use_element)
+                this.value_slot = dojo.create(
+                    "span", {"innerHTML": "-"}, use_element
+                );
+            else
+                this.value_slot = dojo.create("td",{"innerHTML":"-"},this.tr);
+        };
+
+        this._create_remover = function(use_element) {
+            var td = use_element || dojo.create("td", {}, this.tr);
+            var anchor = dojo.create(
+                "a", {
+                    "className": "oils-pcrudfilterdialog-remover",
+                    "innerHTML": "X",
+                    "href": "#",
+                    "onclick": function() {
+                        self.filter_row_manager.remove_row(self.row_id);
+                    }
+                }, td
+            );
+        };
+
+        this._clear_value_slot = function() {
+            if (this.value_widgets) {
+                this.value_widgets.forEach(
+                    function(autowidg) { autowidg.widget.destroy(); }
+                );
+                delete this.value_widgets;
+            }
+
+            dojo.empty(this.value_slot);
+        };
+
+        this._rebuild_value_widgets = function() {
+            this._clear_value_slot();
+
+            if (!this.get_selected_operator() || !this.selected_field)
+                return;
+
+            this.value_widgets = [];
+
+            var param_count = this.operator_selector.item.param_count;
+
+            /* This is where find and deploy custom widget builders. */
+            var widget_builder_key = this.selected_field_fm_class + ":" +
+                this.selected_field_fm_field;
+            var constr =
+                this.filter_row_manager.widget_builders[widget_builder_key] ||
+                openils.widget.AutoFieldWidget;
+
+            for (var i = 0; i < param_count; i++) {
+                var widg = new constr({
+                    "fmClass": this.selected_field_fm_class,
+                    "fmField": this.selected_field_fm_field,
+                    "parentNode": dojo.create("span", {}, this.value_slot),
+                    "dijitArgs": {"scrollOnFocus": false}
+                });
+
+                widg.build();
+                this.value_widgets.push(widg);
+            }
+        };
+
+        /* for ugly special cases in compliation */
+        this._null_clause = function() {
+            var opname = this.get_selected_operator_name();
+            if (opname == "not null")
+                return {"!=": null};
+            else if (opname == "null")
+                return null;
+            else
+                return;
+        };
+
+        this.get_selected_operator = function() {
+            if (this.operator_selector)
+                return this.operator_selector.item;
+        };
+
+        this.get_selected_operator_name = function() {
+            var op = this.get_selected_operator();
+            return op ? op.name : null;
+        };
+
+        this.update_selected_operator = function(value) {
+            this._rebuild_value_widgets();
+        };
+
+        this.update_selected_field = function(value) {
+            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_is_indirect =
+                    this.field_selector.item.indirect || false;
+                this.selected_field_fm_class =
+                    this.field_selector.item.fmClass ||
+                    this.filter_row_manager.fm_class;
+
+                this._adjust_operator_selector();
+                this._rebuild_value_widgets();
+            }
+        };
+
+        this.compile = function() {
+            if (this.value_widgets) {
+                var values = this.value_widgets.map(
+                    function(widg) {
+                        if (widg.useCorrectly)
+                            return widg.widget.attr("value");
+                        else if (self.selected_field_is_indirect)
+                            return widg.widget.attr("displayedValue");
+                        else
+                            return widg.getFormattedValue();
+                    }
+                );
+
+                if (!values.length) {
+                    return this._null_clause(); /* null/not null */
+                } else {
+                    var clause = {};
+                    var op = this.get_selected_operator_name();
+                    if (values.length == 1)
+                        clause[op] = values.pop();
+                    else
+                        clause[op] = values;
+                    return clause;
+                }
+            } else {
+                return;
+            }
+        };
+
+        this.destroy = function() {
+            this._clear_value_slot();
+            this.field_selector.destroy();
+            if (this.operator_selector)
+                this.operator_selector.destroy();
+
+            dojo.destroy(this.tr);
+        };
+
+        this.initialize = function(initializer) {
+            this.field_selector.attr("value", initializer.field);
+            this.operator_selector.attr("value", initializer.operator);
+
+            /* Caller supplies value for one value, values (array) for
+             * multiple. */
+            if (!initializer.values || !dojo.isArray(initializer.values))
+                initializer.values = [initializer.values || initializer.value];
+
+            for (var i = 0; i < initializer.values.length; i++) {
+                this.value_widgets[i].widget.attr(
+                    "value", initializer.values[i]
+                );
+            }
+        };
+
+        this._init.apply(this, arguments);
+    }
+
+    dojo.declare(
+        "openils.widget.PCrudFilterPane", [openils.widget.AutoWidget],
+        {
+            "useDiv": null, /* should always be null for subclass dialogs */
+            "initializers": null,
+            "compact": false,
+            "widgetBuilders": null,
+
+            "constructor": function(args) {
+                for(var k in args)
+                    this[k] = args[k];
+                this.widgetIndex = 0;
+                this.widgetCache = {};
+
+                /* Meaningless in a pane, but better here than in
+                 * PCrudFilterDialog so that we don't need to load i18n
+                 * strings there: */
+                this.title = this.title || localeStrings.DEFAULT_DIALOG_TITLE;
+            },
+
+            "_buildButtons": function() {
+                var self = this;
+
+                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)
+                );
+
+                this._apply_button = new dijit.form.Button(
+                    {
+                        "label": localeStrings.APPLY,
+                        "scrollOnFocus": false,
+                        "onClick": function() { self.doApply(); }
+                    }, dojo.create("span", {}, button_holder)
+                );
+
+                if (!this.useDiv) {
+                    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;
+                var realFieldList = this.sortedFieldList.filter(
+                    function(item) { return !(item.virtual || item.nonIdl); }
+                );
+
+                /* Prevent any explicitly unwanted fields from being available
+                 * in our field dropdowns. */
+                if (dojo.isArray(this.suppressFilterFields)) {
+                    realFieldList = realFieldList.filter(
+                        function(item) {
+                            for (
+                                var i = 0;
+                                i < self.suppressFilterFields.length;
+                                i++
+                            ) {
+                                if (item.name == self.suppressFilterFields[i])
+                                    return false;
+                            }
+                            return true;
+                        }
+                    );
+                }
+
+                this.fieldStore = new dojo.data.ItemFileReadStore({
+                    "data": {
+                        "identifier": "name",
+                        "name": "label",
+                        "items": realFieldList.map(
+                            function(item) {
+                                return {
+                                    "label": item.label,
+                                    "name": item.name,
+                                    "type": item.datatype
+                                };
+                            }
+                        )
+                    }
+                });
+            },
+
+            "hide": function() {
+                try {
+                    this.inherited(arguments);
+                } catch (E) {
+                    /* When using *FilterPane directly (without a *Dialog
+                     * subclass), do nothing.  */
+                    void(0);
+                }
+            },
+
+            /* 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.
+             */
+
+            "startup": function() {
+                if (this.useDiv)
+                    this.domNode = this.useDiv;
+
+                try {
+                    this.inherited(arguments);
+                } catch (E) {
+                    /* When using *FilterPane directly (without a *Dialog
+                     * subclass), there is no startup method in any ancestor
+                     * class. XXX Refactor?
+                     */
+                    void(0);
+                }
+
+                this.initAutoEnv();
+
+                this._buildFieldStore();
+
+                this.filter_row_manager = new PCrudFilterRowManager(
+                    dojo.create("div", {}, this.domNode),
+                    this.fieldStore, this.fmClass, this.compact,
+                    this.widgetBuilders,
+                    Boolean(this.initializers)  /* avoid adding empty row */,
+                    dojo.hitch(this, function() { this.doApply(); })
+                );
+
+                this._buildButtons();
+
+                if (this.initializers) {
+                    this.initializers.forEach(
+                        dojo.hitch(this, function(initializer) {
+                            this.filter_row_manager.add_row(initializer);
+                        })
+                    );
+                }
+            },
+
+            /* This should just be named 'apply', but that is kind of a special
+             * word in Javascript, no? */
+            "doApply": function() {
+                this._apply_button.attr("disabled", true);
+
+                var _E; /* Try pretty hard not to leave the apply button
+                           disabled forever, even if 'apply' blows up. */
+                try {
+                    if (this.onApply)
+                        this.onApply(this.filter_row_manager.compile());
+                } catch (E) {
+                    _E = E;
+                }
+                this.hide();
+                this._apply_button.attr("disabled", false);
+
+                if (_E) throw _E;
+            }
+        }
+    );
+}
@@ -12,6 +12,7 @@
     "OPERATOR_LIKE": "is like",
     "OPERATOR_NOT_LIKE": "is not like",
     "EMPTY_CASE": "Add rows to filter results, or just click Apply to see unfiltered results.",
+    "EMPTY_CASE_COMPACT": "Add rows to filter results.",
     "DEFAULT_DIALOG_TITLE": "Filter Results",
     "ADD_ROW": "Add Row",
     "APPLY": "Apply",
diff --git a/Open-ILS/web/js/ui/default/actor/user/trigger_events.js b/Open-ILS/web/js/ui/default/actor/user/trigger_events.js
deleted file mode 100644 (file)
index 1f734df..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-dojo.require('dojox.grid.DataGrid');
-dojo.require('dojo.data.ItemFileWriteStore');
-dojo.require('openils.Util');
-dojo.require('openils.User');
-
-// need these to represent the event def name
-dojo.requireLocalization('openils.conify', 'conify');
-var localeStrings = dojo.i18n.getLocalization('openils.conify', 'conify');
-
-var evtCache = {};
-
-function init() {
-    var store = new dojo.data.ItemFileWriteStore({data:acqf.initStoreData()});
-    evtGrid.setStore(store);
-    evtGrid.render();
-
-    function onResponse(r) {
-        var evt = openils.Util.readResponse(r);
-        evtCache[evt.id()] = evt;
-        evtGrid.store.newItem(evt.toStoreItem()); 
-    }
-
-    fieldmapper.standardRequest(
-        ['open-ils.actor', 'open-ils.actor.user.events.circ'],
-        {   async: true,
-            params: [openils.User.authtoken, patronId],
-            onresponse : onResponse
-        }
-    );
-
-    fieldmapper.standardRequest(
-        ['open-ils.actor', 'open-ils.actor.user.events.ahr'],
-        {   async: true,
-            params: [openils.User.authtoken, patronId],
-            onresponse : onResponse
-        }
-    );
-}
-
-function getField(rowIdx, item) {
-    if(!item) return '';
-    var evt = evtCache[this.grid.store.getValue(item, 'id')];
-
-    switch(this.field) {
-        case 'event_def':
-            return dojo.string.substitute(
-                localeStrings.EVENT_DEF_LABEL, [
-                    fieldmapper.aou.findOrgUnit(evt.event_def().owner()).shortname(), 
-                    evt.event_def().name()
-                ]);
-        case 'reactor':
-            return evt.event_def().reactor().module();
-        case 'validator':
-            return evt.event_def().validator().module();
-        case 'hook':
-            return evt.event_def().hook();
-        case 'target':
-            switch(evt.target().classname) {
-                case 'circ':
-                    return evt.target().target_copy().barcode();
-                case 'ahr':
-                    if(evt.target().currrent_copy())
-                        return evt.target().currrent_copy().barcode();
-            }
-            
-    }
-
-    return this.grid.store.getValue(item, this.field) || '';
-}
-
-function evtCancelSelected() {
-    var selected = evtGrid.selection.getSelected();
-    if(selected.length == 0) return;
-    var eventIds = selected.map(
-        function(item) { return evtGrid.store.getValue(item, 'id') } );
-    alert(eventIds);
-    fieldmapper.standardRequest(
-        ['open-ils.actor', 'open-ils.actor.user.event.cancel.batch'],
-        {   async: true,
-            params: [openils.User.authtoken, eventIds],
-            oncomplete : init
-        }
-    );
-}
-
-openils.Util.addOnLoad(init);
-
index 6bc4d44..870f430 100644 (file)
@@ -492,7 +492,6 @@ var urls = {
     'XUL_SURVEY_WIZARD' : 'chrome://open_ils_staff_client/content/admin/survey_wizard.xul',
     'XUL_TIMESTAMP_DIALOG' : '/xul/server/util/timestamp.xul',
     'XUL_TOOLBAR_CONFIG' : '/xul/server/admin/toolbar.xul',
-    'XUL_TRIGGER_EVENTS' : '/xul/server/patron/trigger_events.xul',
     'XUL_USER_BUCKETS' : '/xul/server/patron/user_buckets.xul',
     'XUL_VERIFY_CREDENTIALS' : '/xul/server/main/verify_credentials.xul',
     'XUL_VOLUME_BUCKETS' : '/xul/server/cat/volume_buckets.xul',
@@ -512,7 +511,8 @@ var urls = {
     'XUL_REPORTS' : '/reports/oils_rpt.xhtml',
     'EG_ACQ_PO_VIEW' : '/eg/acq/po/view',
     'EG_ACQ_USER_REQUESTS' : '/eg/acq/picklist/user_request',
-    'XUL_SERIAL_BATCH_RECEIVE': '/xul/server/serial/batch_receive.xul'
+    'XUL_SERIAL_BATCH_RECEIVE': '/xul/server/serial/batch_receive.xul',
+    'EG_TRIGGER_EVENTS' : '/eg/actor/user/event_log'
 }
 
 if(use_tpac) {
index af1c9b1..b19477a 100644 (file)
@@ -187,12 +187,14 @@ circ.copy_status.prototype = {
                             try {
                                 for (var i = 0; i < obj.selection_list.length; i++) {
                                     xulG.new_tab(
-                                        urls.XUL_TRIGGER_EVENTS,
+                                        xulG.url_prefix(urls.XUL_REMOTE_BROWSER),
                                         {
                                             'tab_name' : document.getElementById('commonStrings').getFormattedString('tab.label.triggered_events_for_copy',[ obj.selection_list[i].barcode ])
                                         },
                                         {
-                                            'copy_id' : obj.selection_list[i].copy_id
+                                            'url': urls.EG_TRIGGER_EVENTS + "?copy_id=" + obj.selection_list[i].copy_id,
+                                            'show_print_button': false,
+                                            'show_nav_buttons': false
                                         }
                                     );
                                 }
index a383082..9608ebd 100644 (file)
@@ -344,10 +344,12 @@ patron.display.prototype = {
                         ['command'],
                         function(ev) {
                             obj.right_deck.set_iframe(
-                                urls.XUL_TRIGGER_EVENTS,
+                                xulG.url_prefix(urls.XUL_REMOTE_BROWSER),
                                 {},
                                 {
-                                    'patron_id' : obj.patron.id()
+                                    'url': urls.EG_TRIGGER_EVENTS + "?patron_id=" + obj.patron.id(),
+                                    'show_print_button': false,
+                                    'show_nav_buttons': false
                                 }
                             );
                         }
index a38f728..ac0f2df 100644 (file)
@@ -44,12 +44,14 @@ patron.items.prototype = {
                                 var barcodes = util.functional.map_list( obj.retrieve_ids, function(o) { return o.barcode; } ); 
                                 for (var i = 0; i < copy_ids.length; i++) {
                                     xulG.new_tab(
-                                        urls.XUL_TRIGGER_EVENTS,
+                                        xulG.url_prefix(urls.XUL_REMOTE_BROWSER),
                                         {
                                             'tab_name' : document.getElementById('commonStrings').getFormattedString('tab.label.triggered_events_for_copy',[ barcodes[i] ])
                                         },
                                         {
-                                            'copy_id' : copy_ids[i]
+                                            'url': urls.EG_TRIGGER_EVENTS + "?copy_id=" + copy_ids[i],
+                                            'show_nav_buttons': false,
+                                            'show_print_button': false
                                         }
                                     );
                                 }
@@ -65,10 +67,12 @@ patron.items.prototype = {
                                 var copy_ids = util.functional.map_list( obj.retrieve_ids2, function(o) { return o.copy_id; } ); 
                                 for (var i = 0; i < copy_ids.length; i++) {
                                     xulG.new_tab(
-                                        urls.XUL_TRIGGER_EVENTS,
+                                        xulG.url_prefix(urls.XUL_REMOTE_BROWSER),
                                         {},
                                         {
-                                            'copy_id' : copy_ids[i]
+                                            'url': urls.EG_TRIGGER_EVENTS + "?copy_id=" + copy_ids[i],
+                                            'show_nav_buttons': false,
+                                            'show_print_button': false
                                         }
                                     );
                                 }
diff --git a/Open-ILS/xul/staff_client/server/patron/trigger_events.js b/Open-ILS/xul/staff_client/server/patron/trigger_events.js
deleted file mode 100644 (file)
index 29c3fa2..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-var list; var error; var net; var rows;
-
-function $(id) { return document.getElementById(id); }
-
-//// parent interfaces often call these
-function default_focus() { $('atev_list').focus(); }
-function refresh() { populate_list(); }
-////
-
-function trigger_event_init() {
-    try {
-        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); 
-
-        commonStrings = $('commonStrings');
-        patronStrings = $('patronStrings');
-
-        if (typeof JSAN == 'undefined') {
-            throw(
-                commonStrings.getString('common.jsan.missing')
-            );
-        }
-
-        JSAN.errorLevel = "die"; // none, warn, or die
-        JSAN.addRepository('..');
-
-        JSAN.use('OpenILS.data'); data = new OpenILS.data(); data.stash_retrieve();
-        XML_HTTP_SERVER = data.server_unadorned;
-
-        JSAN.use('util.error'); error = new util.error();
-        JSAN.use('util.network'); net = new util.network();
-        JSAN.use('patron.util'); 
-        JSAN.use('util.list'); 
-        JSAN.use('util.functional'); 
-        JSAN.use('util.widgets');
-
-        dojo.require('openils.Util');
-
-        init_list();
-        $('list_actions').appendChild( list.render_list_actions() );
-        list.set_list_actions();
-        $('cmd_cancel_event').addEventListener('command', gen_event_handler('cancel'), false);
-        $('cmd_reset_event').addEventListener('command', gen_event_handler('reset'), false);
-        $('circ').addEventListener('command', function() { populate_list(); }, false);
-        $('ahr').addEventListener('command', function() { populate_list(); }, false);
-        $('pending').addEventListener('command', function() { populate_list(); }, false);
-        $('complete').addEventListener('command', function() { populate_list(); }, false);
-        $('error').addEventListener('command', function() { populate_list(); }, false);
-        populate_list();
-        default_focus();
-
-    } catch(E) {
-        var err_prefix = 'trigger_events.js -> trigger_event_init() : ';
-        if (error) error.standard_unexpected_error_alert(err_prefix,E); else alert(err_prefix + E);
-    }
-}
-
-function gen_event_handler(method) { // cancel or reset?
-    return function(ev) {
-        try {
-            var sel = list.retrieve_selection();
-            var ids = util.functional.map_list( sel, function(o) { return JSON2js( o.getAttribute('retrieve_id') ); } );
-
-            var pm = $('progress'); pm.value = 0; pm.hidden = false;
-            var idx = -1;
-
-            var i = method == 'cancel' ? 'FM_ATEV_CANCEL' : 'FM_ATEV_RESET';
-            fieldmapper.standardRequest(
-                [ api[i].app, api[i].method ],
-                {   async: true,
-                    params: [ses(), ids],
-                    onresponse: function(r) {
-                        try {
-                            idx++; pm.value = Number( pm.value ) + 100/ids.length;
-                            var result = openils.Util.readResponse(r);
-                            if (typeof result.ilsevent != 'undefined') { throw(result); }
-                        } catch(E) {
-                            error.standard_unexpected_error_alert('In patron/trigger_events.js, handle_'+i+'_event onresponse.',E);
-                        }
-                    },
-                    onerror: function(r) {
-                        try {
-                            var result = openils.Util.readResponse(r);
-                            throw(result);
-                        } catch(E) {
-                            error.standard_unexpected_error_alert('In patron/trigger_events.js, handle_'+i+'_event onerror.',E);
-                        }
-                        pm.hidden = true; pm.value = 0; populate_list();
-                    },
-                    oncomplete: function(r) {
-                        try {
-                            var result = openils.Util.readResponse(r);
-                        } catch(E) {
-                            error.standard_unexpected_error_alert('In patron/trigger_events.js, handle_'+i+'_event oncomplete.',E);
-                        }
-                        pm.hidden = true; pm.value = 0; populate_list();
-                    }
-                }
-            );
-
-        } catch(E) {
-            alert('Error in patron/trigger_events.js, handle_???_event(): ' + E);
-        }
-    };
-}
-
-function init_list() {
-    try {
-
-        list = new util.list( 'atev_list' );
-        list.init( 
-            {
-                'columns' : [].concat(
-                    list.fm_columns('atev', {
-                        'atev_target' : { 'render' : function(my) { return fieldmapper.IDL.fmclasses[my.atev.target().classname].label; } }
-                    })
-                ).concat(
-                    list.fm_columns('atevdef', { 
-                        '*' : { 'expanded_label' : true, 'hidden' : true }, 
-                        'atevdef_name' : { 'hidden' : false }, 
-                        'atevdef_reactor' : { 'render' : function(my) { return my.atevdef.reactor().id(); } }, 
-                        'atevdef_validator' : { 'render' : function(my) { return my.atevdef.validator().id(); } } 
-                    })
-                ).concat(
-                    list.fm_columns('atreact', { 
-                        '*' : { 'expanded_label' : true, 'hidden' : true }, 
-                        'atreact_module' : { 'hidden' : false } 
-                    })
-                ).concat(
-                    list.fm_columns('atval', { 
-                        '*' : { 'expanded_label' : true, 'hidden' : true }, 
-                        'atval_module' : { 'hidden' : false } 
-                    })
-                ).concat(
-                    list.fm_columns('circ', { 
-                        '*' : { 'expanded_label' : true, 'hidden' : true }, 
-                        'circ_due_date' : { 'hidden' : false } 
-                    })
-                ).concat(
-                    list.fm_columns('acp', { 
-                        '*' : { 'expanded_label' : true, 'hidden' : true }, 
-                        'acp_barcode' : { 'hidden' : false } 
-                    })
-                ).concat(
-                    list.fm_columns('ahr', { 
-                        '*' : { 'expanded_label' : true, 'hidden' : true },
-                        'ahr_id' : { 'hidden' : false } 
-                    })
-                ),
-                'retrieve_row' : retrieve_row,
-                'on_select' : handle_selection
-            }
-        );
-
-    } catch(E) {
-        var err_prefix = 'trigger_events.js -> init_list() : ';
-        if (error) error.standard_unexpected_error_alert(err_prefix,E); else alert(err_prefix + E);
-    }
-}
-
-function retrieve_row(params) { // callback function for fleshing rows in a list
-    params.treeitem_node.setAttribute('retrieve_id',params.row.my.atev.id()); 
-    params.on_retrieve(params.row); 
-    return params.row; 
-}
-
-function handle_selection(ev) { // handler for list row selection event
-    var sel = list.retrieve_selection();
-    if (sel.length > 0) {
-        $('cmd_cancel_event').setAttribute('disabled','false');
-        $('cmd_reset_event').setAttribute('disabled','false');
-    } else {
-        $('cmd_cancel_event').setAttribute('disabled','true');
-        $('cmd_reset_event').setAttribute('disabled','true');
-    }
-};
-
-function populate_list() {
-    try {
-
-        $('circ').disabled = true; $('ahr').disabled = true; $('pending').disabled = true; $('complete').disabled = true; $('error').disabled = true;
-
-        rows = {};
-        list.clear();
-
-        function onResponse(r) {
-            var evt = openils.Util.readResponse(r);
-            var row_params = {
-                'row' : {
-                    'my' : {
-                        'atev' : evt,
-                        'atevdef' : evt.event_def(),
-                        'atreact' : evt.event_def().reactor(),
-                        'atval' : evt.event_def().validator(),
-                        'circ' : evt.target().classname == 'circ' ? evt.target() : null,
-                        'ahr' : evt.target().classname == 'ahr' ? evt.target() : null,
-                        'acp' : evt.target().classname == 'circ' ? evt.target().target_copy() : evt.target().current_copy()
-                    }
-                }
-            };
-            rows[ evt.id() ] = list.append( row_params );
-
-        }
-
-        function onError(r) {
-            var evt = openils.Util.readResponse(r);
-            alert('error, evt = ' + js2JSON(evt));
-            $('circ').disabled = false; $('ahr').disabled = false; $('pending').disabled = false; $('complete').disabled = false; $('error').disabled = false;
-        }
-
-        var method = $('circ').checked ? 'FM_ATEV_APROPOS_CIRC' : 'FM_ATEV_APROPOS_AHR';
-        if (xul_param('copy_id')) { method += '_VIA_COPY'; }
-
-        var filter = {"event":{"state":"complete"}, "order_by":[{"class":"atev", "field":"run_time", "direction":"desc"}]};
-
-        if ($('pending').checked) { filter.event.state = 'pending'; filter.order_by[0].direction = 'asc'; }
-        if ($('error').checked) { filter.event.state = 'error'; }
-
-        fieldmapper.standardRequest(
-            [api[method].app, api[method].method ],
-            {   async: true,
-                params: [ses(), xul_param('copy_id') || xul_param('patron_id'), filter],
-                onresponse : onResponse,
-                onerror : onError,
-                oncomplete : function() {
-                    $('circ').disabled = false; $('ahr').disabled = false; $('pending').disabled = false; $('complete').disabled = false; $('error').disabled = false;
-                }
-            }
-        );
-
-    } catch(E) {
-        var err_prefix = 'trigger_events.js -> populate_list() : ';
-        if (error) error.standard_unexpected_error_alert(err_prefix,E); else alert(err_prefix + E);
-    }
-}
diff --git a/Open-ILS/xul/staff_client/server/patron/trigger_events.xul b/Open-ILS/xul/staff_client/server/patron/trigger_events.xul
deleted file mode 100644 (file)
index 368d80c..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-<?xml version="1.0"?>
-<!-- Application: Evergreen Staff Client -->
-<!-- Screen: Cancel/Reset Action::Trigger Events for Patron -->
-
-<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
-<!-- PRESENTATION -->
-<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
-
-<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
-<!-- LOCALIZATION -->
-<!DOCTYPE window PUBLIC "" ""[
-    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
-]>
-
-<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
-<!-- OVERLAYS -->
-<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
-
-<window id="trigger_event_win" onload="try { font_helper(); persist_helper(); trigger_event_init(); } catch(E) { alert(E); }" active="true"
-    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-    <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
-    <!-- BEHAVIOR -->
-    <script type="text/javascript">var myPackageDir = 'open_ils_staff_client'; var IAMXUL = true;</script>
-    <scripts id="openils_util_scripts"/>
-
-    <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
-    <script type="text/javascript" src="trigger_events.js"/>
-
-    <messagecatalog id="patronStrings" src="/xul/server/locale/<!--#echo var='locale'-->/patron.properties" />
-
-    <commandset id="trigger_event_cmds">
-        <command id="cmd_cancel_event" disabled="true"/>
-        <command id="cmd_reset_event" disabled="true"/>
-    </commandset>
-
-    <!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
-    <!-- CONTENT -->
-    <groupbox id="trigger_event_groupbox" flex="1" class="my_overflow">
-        <caption id="trigger_event_caption" label="&staff.patron_display.trigger_event.caption;"/>
-        <vbox flex="0">
-            <hbox flex="1">
-                <toolbar id="filters" flex="1">
-                    <toolbarbutton id="circ" label="&staff.patron_display.trigger_event.radio_button.circ_events.label;" accesskey="&staff.patron_display.trigger_event.radio_button.circ_events.accesskey;" 
-                        type="radio" group="atev_type" checked="true" style="-moz-user-focus: normal" oils_persist="checked" oils_persist_no_poke="true"
-                        oncommand="oils_persist(document.getElementById('ahr'));" />
-                    <toolbarbutton id="ahr" label="&staff.patron_display.trigger_event.radio_button.hold_events.label;" accesskey="&staff.patron_display.trigger_event.radio_button.hold_events.accesskey;" 
-                        type="radio" group="atev_type" style="-moz-user-focus: normal" oils_persist="checked" oils_persist_no_poke="true"
-                        oncommand="oils_persist(document.getElementById('circ'));" />
-                    <toolbarseparator/>
-                    <toolbarbutton id="pending" label="&staff.patron_display.trigger_event.checkbox_filter.pending.label;" accesskey="&staff.patron_display.trigger_event.checkbox_filter.pending.accesskey;" 
-                        type="radio" group="atev_status" checked="true" style="-moz-user-focus: normal" oils_persist="checked" oils_persist_no_poke="true"
-                        oncommand="oils_persist(document.getElementById('complete')); oils_persist(document.getElementById('error'));"/>
-                    <toolbarbutton id="complete" label="&staff.patron_display.trigger_event.checkbox_filter.complete.label;" accesskey="&staff.patron_display.trigger_event.checkbox_filter.complete.accesskey;" 
-                        type="radio" group="atev_status" style="-moz-user-focus: normal" oils_persist="checked" oils_persist_no_poke="true"
-                        oncommand="oils_persist(document.getElementById('pending')); oils_persist(document.getElementById('error'));"/>
-                    <toolbarbutton id="error" label="&staff.patron_display.trigger_event.checkbox_filter.error.label;" accesskey="&staff.patron_display.trigger_event.checkbox_filter.error.accesskey;" 
-                        type="radio" group="atev_status" style="-moz-user-focus: normal" oils_persist="checked" oils_persist_no_poke="true"
-                        oncommand="oils_persist(document.getElementById('pending')); oils_persist(document.getElementById('complete'));"/>
-                </toolbar>
-                <spacer flex="1" />
-                <progressmeter id="progress" flex="1" hidden="true" mode="undetermined"/>
-                <menubar>
-                    <menu label="&staff.patron_display.trigger_event.menu.actions.label;" 
-                        accesskey="&staff.patron_display.trigger_event.menu.actions.accesskey;" 
-                        style="-moz-user-focus: normal">
-                        <menupopup>
-                            <menuitem command="cmd_cancel_event" label="&staff.patron_display.trigger_event.menu.actions.cancel.label;" accesskey="&staff.patron_display.menu.actions.cancel.accesskey;"/>
-                            <menuitem command="cmd_reset_event" label="&staff.patron_display.trigger_event.menu.actions.reset.label;" accesskey="&staff.patron_display.menu.actions.reset.accesskey;"/>
-                        </menupopup>
-                    </menu>
-                </menubar>
-            </hbox>
-        </vbox>
-        <tree id="atev_list" flex="1" enableColumnDrag="true" context="atev_actions" />
-        <hbox id="list_actions" />
-    </groupbox>
-
-    <popupset id="atev_popupset">
-        <popup id="atev_actions" position="at_pointer">
-            <menuitem command="cmd_cancel_event" label="&staff.patron_display.trigger_event.menu.actions.cancel.label;" accesskey="&staff.patron_display.menu.actions.cancel.accesskey;"/>
-            <menuitem command="cmd_reset_event" label="&staff.patron_display.trigger_event.menu.actions.reset.label;" accesskey="&staff.patron_display.menu.actions.reset.accesskey;"/>
-        </popup>
-    </popupset>
-
-</window>
-