From aff6b4e324e689d12eadb92082b96b0439a20667 Mon Sep 17 00:00:00 2001 From: Lebbeous Fogle-Weekley Date: Sat, 31 Mar 2012 12:17:40 -0400 Subject: [PATCH] New pull list interface taking advantage of flattener for speed, and advanced sorting. For now, access it by the "Simplifed Pull List" button along the bottom edge of the existing holds pull list interface (but I think when/if this thing is widely accepted, it should replace the existing interface outright). With thanks to Mike Peters for testing an early version. Now including some updates requested by Thomas Berezansky. [Column picker not quite right] Signed-off-by: Lebbeous Fogle-Weekley --- Open-ILS/examples/fm_IDL.xml | 157 +++++++++- .../perlmods/lib/OpenILS/Application/Flattener.pm | 2 + .../src/perlmods/lib/OpenILS/Utils/Fieldmapper.pm | 2 + .../src/perlmods/lib/OpenILS/WWW/FlatFielder.pm | 25 +- Open-ILS/src/sql/Pg/090.schema.action.sql | 51 ++++ Open-ILS/src/sql/Pg/500.view.cross-schema.sql | 78 ++++- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 20 ++ .../XXXX.schema.simplified-hold-pull-list.sql | 152 ++++++++++ Open-ILS/src/templates/circ/hold_pull_list.tt2 | 97 ++++++ Open-ILS/web/js/dojo/openils/FlattenerStore.js | 273 +++++++++++------ .../web/js/dojo/openils/widget/FlattenerGrid.js | 332 ++++++++++++++++----- .../web/js/dojo/openils/widget/GridColumnPicker.js | 45 ++- Open-ILS/web/opac/locale/en-US/lang.dtd | 2 + Open-ILS/xsl/FlatFielder2HTML.xsl | 7 + Open-ILS/xul/staff_client/server/patron/holds.js | 43 +++ .../staff_client/server/patron/holds_overlay.xul | 2 + 16 files changed, 1108 insertions(+), 180 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.simplified-hold-pull-list.sql create mode 100644 Open-ILS/src/templates/circ/hold_pull_list.tt2 diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 9d9b30f830..1ff2e353dd 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2298,7 +2298,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + @@ -2644,7 +2644,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + @@ -2742,6 +2742,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + @@ -4766,7 +4771,7 @@ SELECT usr, - + @@ -4783,7 +4788,7 @@ SELECT usr, - + @@ -4826,9 +4831,135 @@ SELECT usr, - + + + ahr.requestor) AS is_staff_hold + FROM action.hold_request ahr + JOIN action.hold_avg_wait_time ahawt ON (ahawt.hold = ahr.id) + JOIN action.hold_queue_approximation ahqa + ON (ahqa.this_hold=ahr.id AND ahqa.other_hold=ahr.id) + JOIN asset.copy acp ON (acp.id = ahr.current_copy) + JOIN asset.call_number acn ON (acp.call_number = acn.id) + JOIN asset.call_number_prefix acnp ON (acn.prefix = acnp.id) + JOIN asset.call_number_suffix acns ON (acn.suffix = acns.id) + JOIN actor.usr au ON (au.id = ahr.usr) + LEFT JOIN serial.issuance siss + ON (ahr.hold_type = 'I' AND siss.id = ahr.target) + LEFT JOIN asset.copy_location_order acplo + ON (acp.location = acplo.location AND + acp.circ_lib = acplo.org) + WHERE + ahr.capture_time IS NULL AND + ahr.cancel_time IS NULL AND + (ahr.expire_time is NULL OR ahr.expire_time > NOW()) + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT ahr.* FROM action.hold_request ahr JOIN (SELECT current_copy, MAX(capture_time) AS capture_time FROM action.hold_request WHERE capture_time IS NOT NULL GROUP BY current_copy)x USING (current_copy, capture_time) @@ -5070,7 +5201,7 @@ SELECT usr, - + @@ -5080,6 +5211,13 @@ SELECT usr, + + + + + + + @@ -7831,7 +7969,7 @@ SELECT usr, - + @@ -7849,6 +7987,11 @@ SELECT usr, + + + + + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Flattener.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Flattener.pm index a6c77d347e..ff232a9469 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Flattener.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Flattener.pm @@ -89,12 +89,14 @@ sub _flattened_search_single_join_clause { my $new_join; if ($reltype eq "has_a") { $new_join = { + type => "left", class => $hint, fkey => $piece, field => $field }; } elsif ($reltype eq "has_many" or $reltype eq "might_have") { $new_join = { + type => "left", class => $hint, fkey => $last_ident, field => $field diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/Fieldmapper.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/Fieldmapper.pm index df00277cbf..eddb944d48 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/Fieldmapper.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/Fieldmapper.pm @@ -139,11 +139,13 @@ sub load_links { my $reltype = get_attribute( $attribute_list, 'reltype' ); my $key = get_attribute( $attribute_list, 'key' ); my $class = get_attribute( $attribute_list, 'class' ); + my $map = get_attribute( $attribute_list, 'map' ); $$fieldmap{$fm}{links}{ $field } = { class => $class, reltype => $reltype, key => $key, + map => $map }; } } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/FlatFielder.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/FlatFielder.pm index 0e5fc9868d..b80c9e8966 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/FlatFielder.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/FlatFielder.pm @@ -58,8 +58,7 @@ my $_output_handler_dispatch = { "prio" => 0, "code" => sub { $_[0]->content_type("text/html; charset=utf-8"); - print html_ish_output( @_, 'FlatFielder2HTML.xsl' ); - return Apache2::Const::OK; + return html_ish_output( @_, 'FlatFielder2HTML.xsl' ); } }, "application/xml" => { @@ -115,15 +114,29 @@ sub data_to_xml { $fs->setAttribute("FS_key", $args->{key}) if $args->{key}; $dom->setDocumentElement($fs); + my @columns; + my %column_labels; + if (@{$args->{columns}}) { + @columns = @{$args->{columns}}; + if (@{$args->{labels}}) { + my @labels = @{$args->{labels}}; + $column_labels{$columns[$_]} = $labels[$_] for (0..$#labels); + } + } + my $rownum = 1; for my $i (@{$$args{data}}) { my $item = $dom->createElement("row"); $item->setAttribute('ordinal', $rownum); $rownum++; - for my $k (keys %$i) { + @columns = keys %$i unless @columns; + for my $k (@columns) { my $val = $dom->createElement('column'); - $val->setAttribute('name', $k); - $val->appendText($i->{$k}); + my $datum = $i->{$k}; + $datum = join(" ", @$datum) if ref $datum eq 'ARRAY'; + + $val->setAttribute('name', $column_labels{$k} || $k); + $val->appendText($datum); $item->addChild($val); } $fs->addChild($item); @@ -214,6 +227,8 @@ sub handler { $args{key} = $cgi->param('key'); $args{id_field} = $cgi->param('identifier'); $args{label_field} = $cgi->param('label'); + $args{columns} = [ $cgi->param('columns') ]; + $args{labels} = [ $cgi->param('labels') ]; my $fielder = OpenSRF::AppSession->create('open-ils.fielder'); if ($args{map}) { diff --git a/Open-ILS/src/sql/Pg/090.schema.action.sql b/Open-ILS/src/sql/Pg/090.schema.action.sql index 1af6e89ead..d72ea6d07d 100644 --- a/Open-ILS/src/sql/Pg/090.schema.action.sql +++ b/Open-ILS/src/sql/Pg/090.schema.action.sql @@ -972,5 +972,56 @@ query-based fieldsets. Returns NULL if successful, or an error message if not. $$; +-- For a given hold, get avg wait time of copies on potentials list and +-- the number of copies on potentials list. Estimated wait time can be +-- calculated from these results based on queue position. +CREATE VIEW action.hold_avg_wait_time AS + SELECT + SUM(num_potentials * avg_wait_time) / SUM(num_potentials) AS avg_wait_time, + SUM(num_potentials) AS num_potentials, + hold + FROM ( + SELECT + COUNT(acp.id) AS num_potentials, + ahcm.hold, + COALESCE(ccm.avg_wait_time, (SELECT MAX(value) FROM ( + SELECT value::INTERVAL FROM actor.org_unit_ancestor_setting('circ.holds.default_estimated_wait_interval', au.home_ou) + UNION + SELECT '0 seconds'::INTERVAL + ) ous)) AS avg_wait_time + FROM action.hold_copy_map ahcm + JOIN action.hold_request ahr ON (ahr.id = ahcm.hold) + JOIN actor.usr au ON (au.id = ahr.usr) + JOIN asset.copy acp ON (acp.id = ahcm.target_copy) + LEFT JOIN config.circ_modifier ccm ON (ccm.code = acp.circ_modifier) + GROUP BY 2, 3, au.home_ou + ) x + GROUP by 3; + + +CREATE OR REPLACE FUNCTION action.estimate_wait_time_for_hold( + avg_wait_time INTERVAL, + queue_position INT, + home_ou INT +) RETURNS INTERVAL AS $$ +SELECT + CASE WHEN min_wait > estimated_wait_time THEN + min_wait + ELSE + estimated_wait_time + END +FROM ( + SELECT + (SELECT MAX(value) FROM ( + SELECT value::INTERVAL + FROM actor.org_unit_ancestor_setting( + 'circ.holds.min_estimated_wait_interval', $3 + ) + UNION + SELECT '0 seconds'::INTERVAL + ) ous) AS min_wait, + $1 * $2 AS estimated_wait_time +) x +$$ LANGUAGE SQL; COMMIT; diff --git a/Open-ILS/src/sql/Pg/500.view.cross-schema.sql b/Open-ILS/src/sql/Pg/500.view.cross-schema.sql index f49defc818..2162ddc525 100644 --- a/Open-ILS/src/sql/Pg/500.view.cross-schema.sql +++ b/Open-ILS/src/sql/Pg/500.view.cross-schema.sql @@ -60,6 +60,82 @@ CREATE TABLE config.idl_field_doc ( ); CREATE UNIQUE INDEX idl_field_doc_identity ON config.idl_field_doc (fm_class,field,owner); +-- Begin hold queue approximation (the first view is cross-schema and the +-- rest depends on that). -COMMIT; +-- All hold queue things in Evergreen have always been approximations. There +-- is no actual holds queue. That's not how Evergreen holds work. + +-- This approximation is faster than the one it aims to replace. It's Mike +-- Rylander's idea and mostly his implementation. + +-- redundant with reporter.hold_request_record for now +-- needs attention someday re M-type holds. +CREATE OR REPLACE VIEW action.hold_request_record AS +SELECT id, + target, + hold_type, + CASE + WHEN hold_type = 'T' + THEN target + WHEN hold_type = 'I' + THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target) + WHEN hold_type = 'V' + THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target) + WHEN hold_type IN ('C','R','F') + THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target) + WHEN hold_type = 'M' + THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target) + WHEN hold_type = 'P' + THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target) + END AS bib_record + FROM action.hold_request ahr; + +-- a mat-view, trigger maintained (when the target changes), that maps bibs to holds of any type +CREATE TABLE action.materialized_hold_record_map AS + SELECT id, bib_record FROM action.hold_request_record; +CREATE INDEX hr_pkey_idx ON action.materialized_hold_record_map (id); +CREATE INDEX hr_rec_idx ON action.materialized_hold_record_map (bib_record); + +CREATE OR REPLACE FUNCTION action.materialize_hold_record_map() +RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'INSERT' OR ( + NEW.target <> OLD.target OR NEW.hold_type <> OLD.hold_type + ) THEN + DELETE FROM action.materialized_hold_record_map WHERE id = NEW.id; + INSERT INTO action.materialized_hold_record_map (id, bib_record) + SELECT id, bib_record + FROM action.hold_request_record + WHERE id = NEW.id; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; +CREATE TRIGGER materialize_hold_record_map + AFTER INSERT OR UPDATE + ON action.hold_request + FOR EACH ROW EXECUTE PROCEDURE action.materialize_hold_record_map(); + +-- To join this usefully, join where +-- this_hold = AND other_hold = +CREATE VIEW action.hold_queue_approximation AS +SELECT h1.id AS this_hold, + h2.id AS other_hold, + ROW_NUMBER() OVER ( + PARTITION BY h1.id + ORDER BY + COALESCE(h2.cut_in_line, FALSE) DESC, + h2.request_time + ) AS queue_position, + COUNT(*) OVER (PARTITION BY h1.id) AS total_holds + FROM action.hold_request h1 + JOIN action.materialized_hold_record_map r1 USING (id) + JOIN action.materialized_hold_record_map r2 USING (bib_record) + JOIN action.hold_request h2 ON (h2.id = r2.id) ; + +-- End hold queue approximation + +COMMIT; diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index d53ae14db5..cb07918b34 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -10140,6 +10140,26 @@ INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,dat ), 'string' ); + +INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES ( + 'ui.grid_columns.circ.hold_pull_list', + 'gui', + FALSE, + oils_i18n_gettext( + 'ui.grid_columns.circ.hold_pull_list', + 'Hold Pull List', + 'cust', + 'label' + ), + oils_i18n_gettext( + 'ui.grid_columns.circ.hold_pull_list', + 'Hold Pull List Saved Column Settings', + 'cust', + 'description' + ), + 'string' +); + SELECT setval( 'config.sms_carrier_id_seq', 1000 ); INSERT INTO config.sms_carrier VALUES diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.simplified-hold-pull-list.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.simplified-hold-pull-list.sql new file mode 100644 index 0000000000..98e017b1ec --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.simplified-hold-pull-list.sql @@ -0,0 +1,152 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +-- Begin hold queue approximation + +-- All hold queue things in Evergreen have always been approximations. There +-- is no actual holds queue. That's not how Evergreen holds work. + +-- This approximation is faster than the one it aims to replace. It's Mike +-- Rylander's idea and more or less his implementation. + + +CREATE OR REPLACE VIEW action.hold_request_record AS +SELECT id, + target, + hold_type, + CASE + WHEN hold_type = 'T' + THEN target + WHEN hold_type = 'I' + THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target) + WHEN hold_type = 'V' + THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target) + WHEN hold_type IN ('C','R','F') + THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target) + WHEN hold_type = 'M' + THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target) + WHEN hold_type = 'P' + THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target) + END AS bib_record + FROM action.hold_request ahr; + +-- a mat-view, trigger maintained (when the target changes), that maps bibs to holds of any type +CREATE TABLE action.materialized_hold_record_map AS + SELECT id, bib_record FROM action.hold_request_record; +CREATE INDEX hr_pkey_idx ON action.materialized_hold_record_map (id); +CREATE INDEX hr_rec_idx ON action.materialized_hold_record_map (bib_record); + +CREATE OR REPLACE FUNCTION action.materialize_hold_record_map() +RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'INSERT' OR ( + NEW.target <> OLD.target OR NEW.hold_type <> OLD.hold_type + ) THEN + DELETE FROM action.materialized_hold_record_map WHERE id = NEW.id; + INSERT INTO action.materialized_hold_record_map (id, bib_record) + SELECT id, bib_record + FROM action.hold_request_record + WHERE id = NEW.id; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE PLPGSQL; + +CREATE TRIGGER materialize_hold_record_map + AFTER INSERT OR UPDATE + ON action.hold_request + FOR EACH ROW EXECUTE PROCEDURE action.materialize_hold_record_map(); + +-- To join this usefully, join where +-- this_hold = AND other_hold = +CREATE VIEW action.hold_queue_approximation AS +SELECT h1.id AS this_hold, + h2.id AS other_hold, + ROW_NUMBER() OVER ( + PARTITION BY h1.id + ORDER BY + COALESCE(h2.cut_in_line, FALSE) DESC, + h2.request_time + ) AS queue_position, + COUNT(*) OVER (PARTITION BY h1.id) AS total_holds + FROM action.hold_request h1 + JOIN action.materialized_hold_record_map r1 USING (id) + JOIN action.materialized_hold_record_map r2 USING (bib_record) + JOIN action.hold_request h2 ON (h2.id = r2.id) ; + +-- End hold queue approximation + +-- For a given hold, get avg wait time of copies on potentials list and +-- the number of copies on potentials list. Estimated wait time can be +-- calculated from these results based on queue position. +CREATE VIEW action.hold_avg_wait_time AS + SELECT + SUM(num_potentials * avg_wait_time) / SUM(num_potentials) AS avg_wait_time, + SUM(num_potentials) AS num_potentials, + hold + FROM ( + SELECT + COUNT(acp.id) AS num_potentials, + ahcm.hold, + COALESCE(ccm.avg_wait_time, (SELECT MAX(value) FROM ( + SELECT value::INTERVAL FROM actor.org_unit_ancestor_setting('circ.holds.default_estimated_wait_interval', au.home_ou) + UNION + SELECT '0 seconds'::INTERVAL + ) ous)) AS avg_wait_time + FROM action.hold_copy_map ahcm + JOIN action.hold_request ahr ON (ahr.id = ahcm.hold) + JOIN actor.usr au ON (au.id = ahr.usr) + JOIN asset.copy acp ON (acp.id = ahcm.target_copy) + LEFT JOIN config.circ_modifier ccm ON (ccm.code = acp.circ_modifier) + GROUP BY 2, 3, au.home_ou + ) x + GROUP by 3; + + +CREATE OR REPLACE FUNCTION action.estimate_wait_time_for_hold( + avg_wait_time INTERVAL, + queue_position INT, + home_ou INT +) RETURNS INTERVAL AS $$ +SELECT + CASE WHEN min_wait > estimated_wait_time THEN + min_wait + ELSE + estimated_wait_time + END +FROM ( + SELECT + (SELECT MAX(value) FROM ( + SELECT value::INTERVAL + FROM actor.org_unit_ancestor_setting( + 'circ.holds.min_estimated_wait_interval', $3 + ) + UNION + SELECT '0 seconds'::INTERVAL + ) ous) AS min_wait, + $1 * $2 AS estimated_wait_time +) x +$$ LANGUAGE SQL; + +INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES ( + 'ui.grid_columns.circ.hold_pull_list', + 'gui', + FALSE, + oils_i18n_gettext( + 'ui.grid_columns.circ.hold_pull_list', + 'Hold Pull List', + 'cust', + 'label' + ), + oils_i18n_gettext( + 'ui.grid_columns.circ.hold_pull_list', + 'Hold Pull List Saved Column Settings', + 'cust', + 'description' + ), + 'string' +); + +COMMIT; diff --git a/Open-ILS/src/templates/circ/hold_pull_list.tt2 b/Open-ILS/src/templates/circ/hold_pull_list.tt2 new file mode 100644 index 0000000000..3e9cccf247 --- /dev/null +++ b/Open-ILS/src/templates/circ/hold_pull_list.tt2 @@ -0,0 +1,97 @@ +[% WRAPPER base.tt2 %] +[% ctx.page_title = 'Hold Pull List' %] + +
+
+
Hold Pull List
+
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + + + +
Shelving LocationAuthorTitlePartsHold NotesPatron BarcodeRequest LibraryRequest Library (Shortname)Selection LocusSMS Carrier
+
+[% END %] diff --git a/Open-ILS/web/js/dojo/openils/FlattenerStore.js b/Open-ILS/web/js/dojo/openils/FlattenerStore.js index e5cbdd7764..700d3f2845 100644 --- a/Open-ILS/web/js/dojo/openils/FlattenerStore.js +++ b/Open-ILS/web/js/dojo/openils/FlattenerStore.js @@ -29,6 +29,7 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { "offset": 0, "baseSort": null, "defaultSort": null, + "sortFieldReMap": null, "constructor": function(/* object */ args) { dojo.mixin(this, args); @@ -51,7 +52,33 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { ); }, - "_prepare_flattener_params": function(req) { + "_remap_sort": function(prepared_sort) { + if (this.sortFieldReMap) { + return prepared_sort.map( + dojo.hitch( + this, function(exp) { + if (typeof exp == "object") { + var key; + for (key in exp) + break; + var newkey = (key in this.sortFieldReMap) ? + this.sortFieldReMap[key] : key; + var o = {}; + o[newkey] = exp[key]; + return o; + } else { + return (exp in this.sortFieldReMap) ? + this.sortFieldReMap[exp] : exp; + } + } + ) + ); + } else { + return prepared_sort; + } + }, + + "_build_flattener_params": function(req) { var params = { "hint": this.fmClass, "ses": openils.User.authtoken @@ -65,31 +92,38 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { params.where = dojo.toJson(where); } else { - var limit = (!isNaN(req.count) && req.count != Infinity) ? - req.count : this.limit; - var offset = (!isNaN(req.start) && req.start != Infinity) ? - req.start : this.offset; - - dojo.mixin( - params, { - "where": dojo.toJson(req.query), - "slo": dojo.toJson({ - "sort": this._prepare_sort(req.sort), - "limit": limit, - "offset": offset - }) - } - ); + params.where = dojo.toJson(req.query); + + var slo = { + "sort": this._remap_sort(this._prepare_sort(req.sort)) + }; + + if (!req.queryOptions.all) { + slo.limit = + (!isNaN(req.count) && req.count != Infinity) ? + req.count : this.limit; + + slo.offset = + (!isNaN(req.start) && req.start != Infinity) ? + req.start : this.offset; + } + + if (req.queryOptions.columns) + params.columns = req.queryOptions.columns; + if (req.queryOptions.labels) + params.labels = req.queryOptions.labels; + + params.slo = dojo.toJson(slo); } - if (this.mapKey) { /* XXX TODO, get a map key */ + if (this.mapKey) { params.key = this.mapKey; } else { params.map = dojo.toJson(this.mapClause); } - for (var key in params) - console.debug("flattener param " + key + " -> " + params[key]); +// for (var key in params) +// console.debug("flattener param " + key + " -> " + params[key]); return params; }, @@ -114,6 +148,94 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { ); }, + "_on_http_error": function(response, ioArgs, req, retry_method) { + if (response.status == 402) { /* 'Payment Required' stands + in for cache miss */ + if (this._retried_map_key_already) { + var e = new FlattenerStoreError( + "Server won't cache flattener map?" + ); + if (typeof req.onError == "function") + req.onError.call(callback_scope, e); + else + throw e; + } else { + this._retried_map_key_already = true; + delete this.mapKey; + if (retry_method) + return this[retry_method](req); + } + } + }, + + "_fetch_prepare": function(req) { + req.queryOptions = req.queryOptions || {}; + req.abort = function() { console.warn("[unimplemented] abort()"); }; + + if (!this.mapKey) + this._get_map_key(); + + return this._build_flattener_params(req); + }, + + "_fetch_execute": function(params,handle_as,mime_type,onload,onerror) { + dojo.xhrPost({ + "url": this._flattener_url, + "content": params, + "handleAs": handle_as, + "sync": false, + "preventCache": true, + "headers": {"Accept": mime_type}, + "load": onload, + "error": onerror + }); + }, + + /* *** Nonstandard but public API - Please think hard about doing + * things the Dojo Way whenever possible before extending the API + * here. *** */ + + /* fetchToPrint() acts like a lot like fetch(), but doesn't call + * onBegin or onComplete. */ + "fetchToPrint": function(req) { + var callback_scope = req.scope || dojo.global; + var post_params; + + try { + post_params = this._fetch_prepare(req); + } catch (E) { + if (typeof req.onError == "function") + req.onError.call(callback_scope, E); + else + throw E; + } + + var process_fetch_all = dojo.hitch( + this, function(text) { + this._retried_map_key_already = false; + + if (typeof req.onComplete == "function") + req.onComplete.call(callback_scope, text, req); + } + ); + + var process_error = dojo.hitch( + this, function(response, ioArgs) { + this._on_http_error(response, ioArgs, req, "fetchToPrint"); + } + ); + + this._fetch_execute( + post_params, + "text", + "text/html", + process_fetch_all, + process_error + ); + + return req; + }, + /* *** Begin dojo.data.api.Read methods *** */ "getValue": function( @@ -223,35 +345,18 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { // onItem a callback that takes each item as we get it // onComplete a callback that takes the list of items // after they're all fetched - // - // The onError callback is ignored for now (haven't thought - // of anything useful to do with it yet). - // - // The Read API also charges this method with adding an abort - // callback to the *req* object for the caller's use, but - // the one we provide does nothing but issue an alert(). - //console.log("fetch(" + dojo.toJson(req) + ")"); var self = this; var callback_scope = req.scope || dojo.global; - - if (!this.mapKey) { - try { - this._get_map_key(); - } catch (E) { - if (req.onError) - req.onError.call(callback_scope, E); - else - throw E; - } - } - - var post_params = this._prepare_flattener_params(req); - - if (!post_params) { - if (typeof req.onComplete == "function") - req.onComplete.call(callback_scope, [], req); - return; + var post_params; + + try { + post_params = this._fetch_prepare(req); + } catch (E) { + if (typeof req.onError == "function") + req.onError.call(callback_scope, E); + else + throw E; } var process_fetch = function(obj, when) { @@ -296,41 +401,21 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { req.onComplete.call(callback_scope, obj, req); }; - req.abort = function() { - throw new FlattenerStoreError( - "The 'abort' operation is not supported" - ); - }; + var process_error = dojo.hitch( + this, function(response, ioArgs) { + this._on_http_error(response, ioArgs, req, "fetch"); + } + ); var fetch_time = this._last_fetch = (new Date().getTime()); - dojo.xhrPost({ - "url": this._flattener_url, - "content": post_params, - "handleAs": "json", - "sync": false, - "preventCache": true, - "headers": {"Accept": "application/json"}, - "load": function(obj) { process_fetch(obj, fetch_time); }, - "error": function(response, ioArgs) { - if (response.status == 402) { /* 'Payment Required' stands - in for cache miss */ - if (self._retried_map_key_already) { - var e = new FlattenerStoreError( - "Server won't cache flattener map?" - ); - if (typeof req.onError == "function") - req.onError.call(callback_scope, e); - else - throw e; - } else { - self._retried_map_key_already = true; - delete self.mapKey; - return self.fetch(req); - } - } - } - }); + this._fetch_execute( + post_params, + "json", + "application/json", + function(obj) { process_fetch(obj, fetch_time); }, + process_error + ); return req; }, @@ -368,7 +453,15 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { return; } - var post_params = this._prepare_flattener_params(keywordArgs); + var post_params; + try { + post_params = this._fetch_prepare(keywordArgs); + } catch (E) { + if (typeof keywordArgs.onError == "function") + keywordArgs.onError.call(callback_scope, E); + else + throw E; + } var process_fetch_one = dojo.hitch( this, function(obj, when) { @@ -404,17 +497,23 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { } ); + var process_error = dojo.hitch( + this, function(response, ioArgs) { + this._on_http_error( + response, ioArgs, keywordArgs, "fetchItemByIdentity" + ); + } + ); + var fetch_time = this._last_fetch = (new Date().getTime()); - dojo.xhrPost({ - "url": this._flattener_url, - "content": post_params, - "handleAs": "json", - "sync": false, - "preventCache": true, - "headers": {"Accept": "application/json"}, - "load": function(obj){ process_fetch_one(obj, fetch_time); } - }); + this._fetch_execute( + post_params, + "json", + "application/json", + function(obj) { process_fetch_one(obj, fetch_time); }, + process_error + ); }, /* dojo.data.api.Write - only very partially implemented, because diff --git a/Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js b/Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js index a18cd4aba4..2be4d488b8 100644 --- a/Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js +++ b/Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js @@ -16,7 +16,10 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { * FlattenerGrid in their own right */ "columnReordering": true, "columnPersistKey": null, + "autoCoreFields": false, + "autoFieldFields": null, "showLoadFilter": false, /* use FlattenerFilterDialog */ + "fetchLock": false, /* These potential constructor arguments maybe useful to * FlattenerGrid in their own right, and are passed to @@ -24,6 +27,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { "fmClass": null, "fmIdentifier": null, "mapExtras": null, + "sortFieldReMap": null, "defaultSort": null, /* whatever any part of the UI says will /replace/ this */ "baseSort": null, /* will contains what the columnpicker @@ -53,12 +57,12 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { /* These are the fields defined in thead -> tr -> [th,th,...]. * For purposes of building the map, where each field has * three boolean attributes "display", "sort" and "filter", - * assume "display" and "sort" are always true for these. + * assume "display" is always true for these. * That doesn't mean that at the UI level we can't hide a * column later. * * If you need extra fields in the map for which display - * or sort should *not* be true, use mapExtras. + * should *not* be true, use mapExtras. */ dojo.forEach( fields, function(field) { @@ -68,7 +72,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { map[field.field] = { "display": true, "filter": (field.ffilter || false), - "sort": true, + "sort": field.fsort, "path": field.fpath || field.field }; /* The following attribute is not for the flattener @@ -133,78 +137,96 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { return clean; }, - /* The FlattenerStore doesn't need this, but it has at least two - * uses: 1) FlattenerFilterDialog, 2) setting column header labels - * to IDL defaults. - * - * To call these 'Terminii' can be misleading. In certain - * (actually probably common) cases, they won't really be the last - * field in a path, but the next-to-last. Read on. */ - "_calculateMapTerminii": function() { - function _fm_is_selector_for_class(hint, field) { - var cl = fieldmapper.IDL.fmclasses[hint]; + /* Given the hint of a class to start at, follow path to the end + * and return information on the last field. */ + "_followPathToEnd": function(hint, path, allow_selector_backoff) { + function _fm_is_selector_for_class(h, field) { + var cl = fieldmapper.IDL.fmclasses[h]; return (cl.field_map[cl.pkey].selector == field); } - function _follow_to_end(hint, path) { - var last_field, last_hint; - var orig_path = dojo.clone(path); - var field; - - while (field = path.shift()) { - /* XXX this assumes we have the whole IDL loaded. I - * guess we could teach this to work by loading classes - * on demand when we don't have the whole IDL loaded. */ - var field_def = - fieldmapper.IDL.fmclasses[hint].field_map[field]; - - if (field_def["class"] && path.length) { - last_field = field; - last_hint = hint; - - hint = field_def["class"]; - } else if (path.length) { - /* There are more fields left but we can't follow - * the chain via IDL any further. */ - throw new Error( - "_calculateMapTerminii can't parse path " + - orig_path + " (at " + field + ")" - ); - } else { - break; /* keeps field defined after loop */ - } + var last_field, last_hint; + var orig_path = dojo.clone(path); + var field, field_def; + + while (field = path.shift()) { + /* XXX this assumes we have the whole IDL loaded. I + * guess we could teach this to work by loading classes + * on demand when we don't have the whole IDL loaded. */ + field_def = + fieldmapper.IDL.fmclasses[hint].field_map[field]; + + if (!field_def) { + /* This can be ok in some cases. Columns following + * IDL paths involving links with a nonempty "map" + * attribute can be used for display only (no + * sort, no filter). */ + console.info( + "Lost our way in IDL at hint " + hint + + ", field " + field + "; may be ok" + ); + return null; } - var datatype = field_def.datatype; - var indirect = false; - /* Back off the last field in the path if it's a selector - * for its class, because the preceding field will be - * a better thing to hand to AutoFieldWidget. - */ - if (orig_path.length > 1 && - _fm_is_selector_for_class(hint, field)) { - hint = last_hint; - field = last_field; - datatype = "link"; - indirect = true; + if (field_def["class"]) { + last_field = field; + last_hint = hint; + + hint = field_def["class"]; + } else if (path.length) { + /* There are more fields left but we can't follow + * the chain via IDL any further. */ + throw new Error( + "_calculateMapTerminii can't parse path " + + orig_path + " (at " + field + ")" + ); } + } - return { - "fmClass": hint, - "name": field, - "label": field_def.label, - "datatype": datatype, - "indirect": indirect - }; + var datatype = field_def.datatype; + var indirect = false; + /* If allowed, back off the last field in the path if it's a + * selector for its class, because the preceding field will be + * a better thing to hand to AutoFieldWidget. + */ + if (orig_path.length > 1 && allow_selector_backoff && + _fm_is_selector_for_class(hint, field_def.name)) { + hint = last_hint; + field = last_field; + datatype = "link"; + indirect = true; + } else { + field = field_def.name; } + return { + "fmClass": hint, + "name": field, + "label": field_def.label, + "datatype": datatype, + "indirect": indirect + }; + }, + + /* The FlattenerStore doesn't need this, but it has at least two + * uses: 1) FlattenerFilterDialog, 2) setting column header labels + * to IDL defaults. + * + * To call these 'Terminii' can be misleading. In certain + * (actually probably common) cases, they won't really be the last + * field in a path, but the next-to-last. Read on. */ + "_calculateMapTerminii": function() { this.mapTerminii = []; for (var column in this.mapClause) { + var end = this._followPathToEnd( + this.fmClass, + this.mapClause[column].path.split(/\./), + true /* allow selector backoff */ + ); + if (!end) + continue; var terminus = dojo.mixin( - _follow_to_end( - this.fmClass, - this.mapClause[column].path.split(/\./) - ), { + end, { "simple_name": column, "isfilter": this.mapClause[column].filter } @@ -217,8 +239,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { }, "_supplementHeaderNames": function() { - /* You'd be surprised how rarely this make sense in Flattener - * use cases, but if we didn't give a particular header cell + /* If we didn't give a particular header cell * () a display name (the innerHTML of that ), then * use the IDL to provide the label of the terminus of the * flattener path for that column. It may be better than using @@ -237,6 +258,122 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { ); }, + "_columnOrderingAndLabels": function() { + var labels = []; + var columns = []; + + this.views.views[0].structure.cells[0].forEach( + function(c) { + if (!c.field.match(/^\+/)) { + labels.push(c.name); + columns.push(c.field); + } + } + ); + + return {"labels": labels, "columns": columns}; + }, + + "_getAutoFieldFields": function(fmclass) { + return dojo.clone( + fieldmapper.IDL.fmclasses[fmclass].fields) + .filter( + function(field) { + return !field.virtual && field.datatype != "link"; + } + ).sort( + function(a, b) { return a.label > b.label ? 1 : -1; } + ); + }, + + /* Take our core class (this.fmClass) and add table columns for + * any field we don't already have covered by actual hard-coded + * columns. */ + "_addAutoCoreFields": function() { + var cell_list = this.structure[0].cells[0]; + var fields = dojo.clone( + fieldmapper.IDL.fmclasses[this.fmClass].fields + ).sort( + function(a, b) { return a.label > b.label ? 1 : -1; } + ); + + dojo.forEach( + fields, function(f) { + if (f.datatype == "link" || f.virtual) + return; + + if (cell_list.filter( + function(c) { + if (!c.fpath) return false; + return c.fpath.split(/\./)[0] == f.name; + } + ).length) + return; + + cell_list.push({ + "field": f.name, + "name": f.label, + "fsort": true, + "_visible": false + }); + } + ); + }, + + "_addAutoFieldFields": function(paths) { + var self = this; + var n = 0; + + dojo.forEach( + paths, function(path) { + /* The beginning is the end. */ + var beginning = self._followPathToEnd( + self.fmClass, path.split(/\./), false + ); + if (!beginning) { + return; + } else { + console.log(dojo.toJson(beginning)); + dojo.forEach( + self._getAutoFieldFields(beginning.fmClass), + function(field) { + var would_be_path = + path + "." + field.name; + var wbp_re = + new RegExp("^" + would_be_path); + if (!self.structure[0].cells[0].filter( + function(c) { + return c.fpath && + c.fpath.match(wbp_re); + } + ).length) { + self.structure[0].cells[0].push({ + "field": "AUTO_" + beginning.name + + "_" + field.name, + "name": beginning.label + " - " + + field.label, + "fsort": true, + "fpath": would_be_path, + "_visible": false + }); + } + } + ); + } + } + ); + }, + + "_addAutoFields": function() { + if (this.autoCoreFields) + this._addAutoCoreFields(); + + if (dojo.isArray(this.autoFieldFields)) + this._addAutoFieldFields(this.autoFieldFields); + + this.setStructure(this.structure); + }, + "constructor": function(args) { dojo.mixin(this, args); @@ -245,9 +382,11 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { }, "startup": function() { - /* Save original query for further filtering later */ this._baseQuery = dojo.clone(this.query); + + this._addAutoFields(); + this._startupGridHelperColumns(); if (!this.columnPicker) { @@ -271,6 +410,17 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { this.inherited(arguments); }, + "canSort": function(idx, skip_structure /* API abuse */) { + var initial = this.inherited(arguments); + + /* idx is one-based instead of zero-based for a reason. */ + var view_idx = Math.abs(idx) - 1; + return initial && ( + skip_structure || + this.views.views[0].structure.cells[0][view_idx].fsort + ); + }, + /* Maps ColumnPicker sort fields to the correct format. If no sort fields specified, falls back to defaultSort */ "_mapCPSortFields": function(sortFields) { @@ -287,20 +437,25 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { "_finishStartup": function(sortFields) { - this.setStore( + this._setStore( /* Seriously, let's leave this as _setStore. */ new openils.FlattenerStore({ "fmClass": this.fmClass, "fmIdentifier": this.fmIdentifier, "mapClause": (this.mapClause || this._cleanMapForStore(this._generateMap())), "baseSort": this.baseSort, - "defaultSort": this._mapCPSortFields(sortFields) + "defaultSort": this._mapCPSortFields(sortFields), + "sortFieldReMap": this.sortFieldReMap + }), this.query ); // pick up any column label changes this.columnPicker.reloadStructure(); + if (!this.fetchLock) + this._refresh(true); + this._showing_create_pane = false; this.overrideEditWidgets = {}; @@ -352,6 +507,18 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { } }, + "refresh": function() { + this.fetchLock = false; + this._refresh(/* isRender */ true); + }, + + "_fetch": function() { + if (this.fetchLock) + return; + else + return this.inherited(arguments); + }, + /* ******** below are methods mostly copied but * slightly changed from AutoGrid ******** */ @@ -662,6 +829,26 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { ); } ); + }, + + /* 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() { + var coal = this._columnOrderingAndLabels(); + var req = { + "query": this.query, + "queryOptions": { + "all": true, + "columns": coal.columns, + "labels": coal.labels + }, + "onComplete": function(text) { + openils.Util.printHtmlString(text); + } + }; + + this.store.fetchToPrint(req); } } ); @@ -683,6 +870,15 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { cellDef[a] = value; } ); + + /* fsort and _visible are different. Assume true unless defined. */ + dojo.forEach( + ["fsort", "_visible"], function(a) { + var val = dojo.attr(node, a); + cellDef[a] = (typeof val == "undefined" || val === null) ? + true : dojo.fromJson(val); + } + ); }; })(); diff --git a/Open-ILS/web/js/dojo/openils/widget/GridColumnPicker.js b/Open-ILS/web/js/dojo/openils/widget/GridColumnPicker.js index 9cc367d8f4..d0c86dbfd2 100644 --- a/Open-ILS/web/js/dojo/openils/widget/GridColumnPicker.js +++ b/Open-ILS/web/js/dojo/openils/widget/GridColumnPicker.js @@ -63,8 +63,9 @@ if(!dojo._hasResource["openils.widget.GridColumnPicker"]) { * This is necessary if external forces alter the structure. */ reloadStructure : function() { - this.structure = this.grid.structure; this.cells = this.structure[0].cells[0].slice(); + this.pruneInvisibleFields(); + this.structure = this.grid.structure; this.grid.setStructure(this.structure); }, @@ -114,7 +115,7 @@ if(!dojo._hasResource["openils.widget.GridColumnPicker"]) { "Auto WidthSort Priority" + ""}); - var tDiv = dojo.create('div', {style : 'height:400px; overflow-y:auto;'}); + var tDiv = dojo.create('div', {style : 'min-height: 400px;'}); tDiv.appendChild(table); var bDiv = dojo.create('div', {style : 'text-align:right; width:100%;', @@ -211,16 +212,24 @@ if(!dojo._hasResource["openils.widget.GridColumnPicker"]) { else this.dialogTable.appendChild(tr); - if ( this.grid.canSort(i+1) ) { // column index is 1-based - - // must be added after its parent node is inserted into the DOM. - var ns = new dijit.form.NumberSpinner( - { constraints : {places : 0}, - value : cell._sort || 0, - style : 'width:4em', - name : 'sort', - }, ipt3 - ); + if (this.grid.canSort( + i + 1, /* column index is 1-based */ + true /* skip structure test (API abuse) */ + )) { + + /* Ugly kludge. When using with FlattenerGrid the + * conditional is needed. Shouldn't hurt usage with + * AutoGrid. */ + if (typeof cell.fsort == "undefined" || cell.fsort) { + // must be added after its parent node is inserted into the DOM. + var ns = new dijit.form.NumberSpinner( + { constraints : {places : 0}, + value : cell._sort || 0, + style : 'width:4em', + name : 'sort', + }, ipt3 + ); + } } } }, @@ -366,6 +375,18 @@ if(!dojo._hasResource["openils.widget.GridColumnPicker"]) { this.grid.update(); }, + // *only* call this when no usr setting tells us what columns + // are visible or not. + pruneInvisibleFields : function() { + this.structure[0].cells[0] = dojo.filter( + this.structure[0].cells[0], + dojo.hitch(this, function(c) { + // keep true or undef, lose false + return typeof c._visible == "undefined" || c._visible; + }) + ); + }, + load : function() { var _this = this; diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index e9385ddb22..17823fc8a8 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -3247,6 +3247,8 @@ + + diff --git a/Open-ILS/xsl/FlatFielder2HTML.xsl b/Open-ILS/xsl/FlatFielder2HTML.xsl index c988ba8fcd..eaa7e37d4a 100644 --- a/Open-ILS/xsl/FlatFielder2HTML.xsl +++ b/Open-ILS/xsl/FlatFielder2HTML.xsl @@ -9,6 +9,13 @@ + diff --git a/Open-ILS/xul/staff_client/server/patron/holds.js b/Open-ILS/xul/staff_client/server/patron/holds.js index cb45955da9..f2892605cd 100644 --- a/Open-ILS/xul/staff_client/server/patron/holds.js +++ b/Open-ILS/xul/staff_client/server/patron/holds.js @@ -389,6 +389,44 @@ patron.holds.prototype = { } } ], + 'cmd_simplified_pull_list' : [ + ['command'], + function() { + try { + var content_params = { + "session": ses(), + "authtime": ses("authtime"), + "no_xulG": false, + "show_nav_buttons": true, + "show_print_button": true + }; + ["url_prefix", "new_tab", "set_tab", + "close_tab", "new_patron_tab", + "set_patron_tab", "volume_item_creator", + "get_new_session", + "holdings_maintenance_tab", "set_tab_name", + "open_chrome_window", "url_prefix", + "network_meter", "page_meter", + "set_statusbar", "set_help_context" + ].forEach(function(k) { + content_params[k] = xulG[k]; + }); + + var loc = urls.XUL_BROWSER + "?url=" + window.escape( + xulG.url_prefix("/eg/circ/hold_pull_list").replace("http:","https:") + ); + xulG.new_tab( + loc, { + "tab_name": "Simplified Pull List", /* XXX i18n */ + "browser": false, + "show_print_button": false + }, content_params + ); + } catch (E) { + g.error.sdump("D_ERROR", E); + } + } + ], 'cmd_holds_print' : [ ['command'], function() { @@ -1489,6 +1527,7 @@ patron.holds.prototype = { var x_expired_checkbox = document.getElementById('expired_checkbox'); var x_print_full_pull_list = document.getElementById('print_full_btn'); var x_print_full_pull_list_alt = document.getElementById('print_alt_btn'); + var x_simplified_pull_list = document.getElementById('simplified_pull_list_btn'); switch(obj.hold_interface_type) { case 'shelf': obj.render_lib_menus({'pickup_lib':true}); @@ -1497,6 +1536,7 @@ patron.holds.prototype = { if (x_lib_menu_placeholder) x_lib_menu_placeholder.hidden = false; if (x_clear_shelf_widgets) x_clear_shelf_widgets.hidden = false; if (x_print_full_pull_list_alt) x_print_full_pull_list_alt.hidden = true; + if (x_simplified_pull_list) x_simplified_pull_list.hidden = true; break; case 'pull' : if (x_fetch_more) x_fetch_more.hidden = false; @@ -1504,6 +1544,7 @@ patron.holds.prototype = { if (x_print_full_pull_list_alt) x_print_full_pull_list_alt.hidden = false; if (x_lib_type_menu) x_lib_type_menu.hidden = true; if (x_lib_menu_placeholder) x_lib_menu_placeholder.hidden = true; + if (x_simplified_pull_list) x_simplified_pull_list.hidden = false; break; case 'record' : obj.render_lib_menus({'pickup_lib':true,'request_lib':true}); @@ -1511,6 +1552,7 @@ patron.holds.prototype = { if (x_lib_type_menu) x_lib_type_menu.hidden = false; if (x_print_full_pull_list_alt) x_print_full_pull_list_alt.hidden = true; if (x_lib_menu_placeholder) x_lib_menu_placeholder.hidden = false; + if (x_simplified_pull_list) x_simplified_pull_list.hidden = true; break; default: if (obj.controller.view.cmd_search_opac) obj.controller.view.cmd_search_opac.setAttribute('hidden', false); @@ -1519,6 +1561,7 @@ patron.holds.prototype = { if (x_lib_menu_placeholder) x_lib_menu_placeholder.hidden = true; if (x_show_cancelled_deck) x_show_cancelled_deck.hidden = false; if (x_print_full_pull_list_alt) x_print_full_pull_list_alt.hidden = true; + if (x_simplified_pull_list) x_simplified_pull_list.hidden = true; break; } setTimeout( // We do this because render_lib_menus above creates and appends a DOM node, but until this thread exits, it doesn't really happen diff --git a/Open-ILS/xul/staff_client/server/patron/holds_overlay.xul b/Open-ILS/xul/staff_client/server/patron/holds_overlay.xul index 1e050026dd..2326cdfcb0 100644 --- a/Open-ILS/xul/staff_client/server/patron/holds_overlay.xul +++ b/Open-ILS/xul/staff_client/server/patron/holds_overlay.xul @@ -20,6 +20,7 @@ + @@ -200,6 +201,7 @@