From b7a5f1040ffb678f2db0fb8c05321c9291414007 Mon Sep 17 00:00:00 2001 From: Lebbeous Fogle-Weekley Date: Fri, 31 Aug 2012 17:31:43 -0400 Subject: [PATCH] Link checker: user interface and supporting fixes (part 2) Started verification review UI, also SCHEMA CHANGES It just doesn't work for me to not have url_verify.url directly related to url_verify.session. When dealing with the "root" URL in a redirect chain, you can get the related session through url_selector, but not when you have any later URL in the chain. The only way for IDL perms to work would be to have a link to a view using a CTE to find the "root" URL. That's too complex, so instead of that I've just added a session fkey on url_verify.url. Corrections to the preceding commit Vertical scrolling UI glitches fixed Fix broken display of verification attempt in progress Implement the "process immediately" switch, hitherto unhooked up Verify-all now means all-matching-my-search-terms, not necessarily all-in-uvs let's do filter sets a little more generalized-like Permission fixing Filter set loading works. Filter loading: gracefully skip unknown fields, remove inital empty row Saving filter sets Fix filter dialog for pkey fields, scrolliness issue, saved filters issue Pretty start page for staff client menu to land on Staff client menu entry User settings for saved columns Session cloning, working and rather tested show name of session on url select page ... ... and link back to that on review attempt page IN / NOT IN for filter somewhat working, but doesn't save/load yet Saving/loading filter rows for IN, NOT IN operators Printing Signed-off-by: Lebbeous Fogle-Weekley Signed-off-by: Mike Rylander --- Open-ILS/examples/fm_IDL.xml | 99 ++-- .../lib/OpenILS/Application/URLVerify.pm | 9 +- Open-ILS/src/sql/Pg/002.schema.config.sql | 17 + Open-ILS/src/sql/Pg/075.schema.url_verify.sql | 11 +- .../src/sql/Pg/076.functions.url_verify.sql | 4 +- Open-ILS/src/sql/Pg/800.fkeys.sql | 13 + Open-ILS/src/sql/Pg/950.data.seed-values.sql | 57 +- .../sql/Pg/upgrade/XXXX.schema.url_verify.sql | 13 +- .../Pg/upgrade/YYYY.functions.url_verify.sql | 4 +- .../sql/Pg/upgrade/ZZZZ.data.url_verify.sql | 62 +++ Open-ILS/src/templates/base.tt2 | 4 + .../templates/url_verify/create_session.tt2 | 15 +- .../templates/url_verify/review_attempt.tt2 | 78 +++ .../src/templates/url_verify/select_urls.tt2 | 49 +- .../src/templates/url_verify/sessions.tt2 | 51 ++ .../web/js/dojo/openils/FlattenerStore.js | 12 +- .../dojo/openils/URLVerify/CreateSession.js | 143 ++++- .../dojo/openils/URLVerify/ReviewAttempt.js | 67 +++ .../js/dojo/openils/URLVerify/SelectURLs.js | 110 ++-- .../web/js/dojo/openils/URLVerify/Sessions.js | 74 +++ .../web/js/dojo/openils/URLVerify/Verify.js | 59 +++ .../dojo/openils/URLVerify/nls/URLVerify.js | 20 +- .../openils/widget/FlattenerFilterDialog.js | 20 +- .../js/dojo/openils/widget/FlattenerGrid.js | 47 +- .../js/dojo/openils/widget/PCrudFilterPane.js | 487 ++++++++++++++++-- .../openils/widget/nls/PCrudFilterPane.js | 10 +- Open-ILS/web/opac/locale/en-US/lang.dtd | 2 + .../staff_client/chrome/content/main/menu.js | 28 +- .../chrome/content/main/menu_frame_menus.xul | 3 + .../chrome/locale/en-US/offline.properties | 1 + 30 files changed, 1319 insertions(+), 250 deletions(-) create mode 100644 Open-ILS/src/templates/url_verify/review_attempt.tt2 create mode 100644 Open-ILS/src/templates/url_verify/sessions.tt2 create mode 100644 Open-ILS/web/js/dojo/openils/URLVerify/ReviewAttempt.js create mode 100644 Open-ILS/web/js/dojo/openils/URLVerify/Sessions.js create mode 100644 Open-ILS/web/js/dojo/openils/URLVerify/Verify.js diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 7726438b8a..1510df1779 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -9345,7 +9345,7 @@ SELECT usr, reporter:label="URL Verification Session" > - + @@ -9411,7 +9411,7 @@ SELECT usr, reporter:label="URL Verification URL Selector" > - + @@ -9449,9 +9449,10 @@ SELECT usr, reporter:label="URL Verification URL" > - + + @@ -9465,27 +9466,30 @@ SELECT usr, + - + + + - + - + - + - + @@ -9500,7 +9504,7 @@ SELECT usr, reporter:label="URL Verification Attempt" > - + @@ -9539,7 +9543,7 @@ SELECT usr, reporter:label="URL Verification" > - + @@ -9574,37 +9578,56 @@ SELECT usr, - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/URLVerify.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/URLVerify.pm index 0e49a006bd..ea1e4f7d14 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/URLVerify.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/URLVerify.pm @@ -83,12 +83,8 @@ sub verify_session { select => {uvu => ['id']}, from => { uvu => { # url - cbrebi => { # bucket item - join => { cbreb => { # bucket - join => { uvs => { # session - filter => {id => $session_id} - }} - }} + uvs => { # session + filter => {id => $session_id} } } } @@ -589,6 +585,7 @@ sub verify_one_url { if (my $loc = $res->headers->{location}) { $redir_url = Fieldmapper::url_verify::url->new; + $redir_url->session($attempt->session); $redir_url->redirect_from($url->id); $redir_url->full_url($loc); diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index 25fa29294f..84635982f4 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -1002,4 +1002,21 @@ CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,'')); +CREATE TABLE config.filter_dialog_interface ( + key TEXT PRIMARY KEY, + description TEXT +); + +CREATE TABLE config.filter_dialog_filter_set ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + owning_lib INT NOT NULL, -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, + creator INT NOT NULL, -- REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, + create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + interface TEXT NOT NULL REFERENCES config.filter_dialog_interface (key) DEFERRABLE INITIALLY DEFERRED, + filters TEXT NOT NULL, -- CHECK (evergreen.is_json(filters)) + CONSTRAINT cfdfs_name_once_per_lib UNIQUE (name, owning_lib) +); + + COMMIT; diff --git a/Open-ILS/src/sql/Pg/075.schema.url_verify.sql b/Open-ILS/src/sql/Pg/075.schema.url_verify.sql index db42861d4a..7e39ebabc2 100644 --- a/Open-ILS/src/sql/Pg/075.schema.url_verify.sql +++ b/Open-ILS/src/sql/Pg/075.schema.url_verify.sql @@ -42,6 +42,7 @@ CREATE TABLE url_verify.url ( id SERIAL PRIMARY KEY, redirect_from INT REFERENCES url_verify.url(id) DEFERRABLE INITIALLY DEFERRED, item INT REFERENCES container.biblio_record_entry_bucket_item (id) DEFERRABLE INITIALLY DEFERRED, + session INT REFERENCES url_verify.session (id) DEFERRABLE INITIALLY DEFERRED, url_selector INT REFERENCES url_verify.url_selector (id) DEFERRABLE INITIALLY DEFERRED, tag TEXT, subfield TEXT, @@ -88,15 +89,5 @@ CREATE TABLE url_verify.url_verification ( redirect_to INT REFERENCES url_verify.url (id) DEFERRABLE INITIALLY DEFERRED -- if redirected ); -CREATE TABLE url_verify.filter_set ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - owning_lib INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, - creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, - create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - filter TEXT NOT NULL, - CONSTRAINT uvfs_name_once_per_lib UNIQUE (name, owning_lib) -); - COMMIT; diff --git a/Open-ILS/src/sql/Pg/076.functions.url_verify.sql b/Open-ILS/src/sql/Pg/076.functions.url_verify.sql index a49e5fc2ba..5443d51930 100644 --- a/Open-ILS/src/sql/Pg/076.functions.url_verify.sql +++ b/Open-ILS/src/sql/Pg/076.functions.url_verify.sql @@ -91,8 +91,8 @@ BEGIN JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id) WHERE c.id = item_id; - INSERT INTO url_verify.url (item, url_selector, tag, subfield, ord, full_url) - VALUES ( item_id, current_selector.id, current_tag, current_sf, current_ord, current_url); + INSERT INTO url_verify.url (session, item, url_selector, tag, subfield, ord, full_url) + VALUES ( session_id, item_id, current_selector.id, current_tag, current_sf, current_ord, current_url); current_url_pos := current_url_pos + 1; current_ord := current_ord + 1; diff --git a/Open-ILS/src/sql/Pg/800.fkeys.sql b/Open-ILS/src/sql/Pg/800.fkeys.sql index 53be2e1a9e..e24ca9f594 100644 --- a/Open-ILS/src/sql/Pg/800.fkeys.sql +++ b/Open-ILS/src/sql/Pg/800.fkeys.sql @@ -126,4 +126,17 @@ ALTER TABLE config.z3950_source ADD CONSTRAINT use_perm_fkey FOREIGN KEY (use_pe ALTER TABLE config.org_unit_setting_type_log ADD CONSTRAINT config_org_unit_setting_type_log_fkey FOREIGN KEY (org) REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE config.filter_dialog_filter_set + ADD CONSTRAINT config_filter_dialog_filter_set_owning_lib_fkey + FOREIGN KEY (owning_lib) REFERENCES actor.org_unit (id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; + +ALTER TABLE config.filter_dialog_filter_set + ADD CONSTRAINT config_filter_dialog_filter_set_creator_fkey + FOREIGN KEY (creator) REFERENCES actor.usr (id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; + +ALTER TABLE config.filter_dialog_filter_set + ADD CONSTRAINT config_filter_dialog_filter_set_filters_check + CHECK (evergreen.is_json(filters)) 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 d6f0547917..120120e8de 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -1577,7 +1577,11 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES ( 543, 'URL_VERIFY', oils_i18n_gettext( 543, 'Allows a user to process and verify ULSs', 'ppl', 'description')), ( 544, 'URL_VERIFY_UPDATE_SETTINGS', oils_i18n_gettext( 544, - 'Allows a user to configure URL verification org unit settings', 'ppl', 'description')) + 'Allows a user to configure URL verification org unit settings', 'ppl', 'description')), + ( 545, 'SAVED_FILTER_DIALOG_FILTERS', oils_i18n_gettext( 545, + 'Allows users to save and load sets of filters for filter dialogs, available in certain staff interfaces', 'ppl', 'description')) + + ; @@ -11937,9 +11941,7 @@ INSERT INTO actor.search_filter_group_entry (grp, query, pos) (SELECT id FROM actor.search_filter_group WHERE code = 'kpac_main'), (SELECT id FROM actor.search_query WHERE label = 'Children''s Materials'), 0 - ); -INSERT INTO actor.search_filter_group_entry (grp, query, pos) - VALUES ( + ); INSERT INTO actor.search_filter_group_entry (grp, query, pos) VALUES ( (SELECT id FROM actor.search_filter_group WHERE code = 'kpac_main'), (SELECT id FROM actor.search_query WHERE label = 'Young Adult Materials'), 1 @@ -12080,6 +12082,53 @@ INSERT INTO config.org_unit_setting_type 544 ); +INSERT INTO config.filter_dialog_interface (key, description) VALUES ( + 'url_verify', + oils_i18n_gettext( + 'url_verify', + 'All Link Checker filter dialogs', + 'cfdi', + 'description' + ) +); + +INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES ( + 'url_verify.select_urls', + 'url_verify', + FALSE, + oils_i18n_gettext( + 'url_verify.select_urls', + 'Link Checker''s URL Selection interface''s saved columns', + 'cust', + 'label' + ), + oils_i18n_gettext( + 'url_verify.select_urls', + 'Link Checker''s URL Selection interface''s saved columns', + 'cust', + 'description' + ), + 'string' +); + +INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES ( + 'url_verify.review_attempt', + 'url_verify', + FALSE, + oils_i18n_gettext( + 'url_verify.review_attempt', + 'Link Checker''s Review Attempt interface''s saved columns', + 'cust', + 'label' + ), + oils_i18n_gettext( + 'url_verify.review_attempt', + 'Link Checker''s Review Attempt interface''s saved columns', + 'cust', + 'description' + ), + 'string' +); INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype, update_perm) diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.url_verify.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.url_verify.sql index 82da8ee24c..ddddd41626 100644 --- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.url_verify.sql +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.url_verify.sql @@ -29,6 +29,7 @@ CREATE TABLE url_verify.url ( redirect_from INT REFERENCES url_verify.url(id) DEFERRABLE INITIALLY DEFERRED, item INT REFERENCES container.biblio_record_entry_bucket_item (id) DEFERRABLE INITIALLY DEFERRED, url_selector INT REFERENCES url_verify.url_selector (id) DEFERRABLE INITIALLY DEFERRED, + session INT REFERENCES url_verify.session (id) DEFERRABLE INITIALLY DEFERRED, tag TEXT, subfield TEXT, ord INT, @@ -74,14 +75,20 @@ CREATE TABLE url_verify.url_verification ( redirect_to INT REFERENCES url_verify.url (id) DEFERRABLE INITIALLY DEFERRED -- if redirected ); -CREATE TABLE url_verify.filter_set ( +CREATE TABLE config.filter_dialog_interface ( + key TEXT PRIMARY KEY, + description TEXT +); + +CREATE TABLE config.filter_dialog_filter_set ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, owning_lib INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED, create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - filter TEXT NOT NULL, - CONSTRAINT uvfs_name_once_per_lib UNIQUE (name, owning_lib) + interface TEXT NOT NULL REFERENCES config.filter_dialog_interface (key) DEFERRABLE INITIALLY DEFERRED, + filters TEXT NOT NULL CHECK (evergreen.is_json(filters)), + CONSTRAINT cfdfs_name_once_per_lib UNIQUE (name, owning_lib) ); COMMIT; diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.functions.url_verify.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.functions.url_verify.sql index f8a5bad0ab..bed0ae3db2 100644 --- a/Open-ILS/src/sql/Pg/upgrade/YYYY.functions.url_verify.sql +++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.functions.url_verify.sql @@ -75,8 +75,8 @@ BEGIN JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id) WHERE c.id = item_id; - INSERT INTO url_verify.url (item, url_selector, tag, subfield, ord, full_url) - VALUES ( item_id, current_selector.id, current_tag, current_sf, current_ord, current_url); + INSERT INTO url_verify.url (session, item, url_selector, tag, subfield, ord, full_url) + VALUES ( session_id, item_id, current_selector.id, current_tag, current_sf, current_ord, current_url); current_url_pos := current_url_pos + 1; current_ord := current_ord + 1; diff --git a/Open-ILS/src/sql/Pg/upgrade/ZZZZ.data.url_verify.sql b/Open-ILS/src/sql/Pg/upgrade/ZZZZ.data.url_verify.sql index f0f98469eb..4e14754862 100644 --- a/Open-ILS/src/sql/Pg/upgrade/ZZZZ.data.url_verify.sql +++ b/Open-ILS/src/sql/Pg/upgrade/ZZZZ.data.url_verify.sql @@ -30,6 +30,19 @@ INSERT INTO permission.perm_list (id, code, description) ); +INSERT INTO permission.perm_list (id, code, description) + VALUES ( + 545, + 'SAVED_FILTER_DIALOG_FILTERS', + oils_i18n_gettext( + 545, + 'Allows users to save and load sets of filters for filter dialogs, available in certain staff interfaces', + 'ppl', + 'description' + ) + ); + + INSERT INTO config.settings_group (name, label) VALUES ( 'url_verify', @@ -127,5 +140,54 @@ INSERT INTO config.org_unit_setting_type ); +INSERT INTO config.filter_dialog_interface (key, description) VALUES ( + 'url_verify', + oils_i18n_gettext( + 'url_verify', + 'All Link Checker filter dialogs', + 'cfdi', + 'description' + ) +); + + +INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES ( + 'url_verify.select_urls', + 'url_verify', + FALSE, + oils_i18n_gettext( + 'url_verify.select_urls', + 'Link Checker''s URL Selection interface''s saved columns', + 'cust', + 'label' + ), + oils_i18n_gettext( + 'url_verify.select_urls', + 'Link Checker''s URL Selection interface''s saved columns', + 'cust', + 'description' + ), + 'string' +); + +INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES ( + 'url_verify.review_attempt', + 'url_verify', + FALSE, + oils_i18n_gettext( + 'url_verify.review_attempt', + 'Link Checker''s Review Attempt interface''s saved columns', + 'cust', + 'label' + ), + oils_i18n_gettext( + 'url_verify.review_attempt', + 'Link Checker''s Review Attempt interface''s saved columns', + 'cust', + 'description' + ), + 'string' +); + COMMIT; diff --git a/Open-ILS/src/templates/base.tt2 b/Open-ILS/src/templates/base.tt2 index 0bd3e5e0b6..efdee63755 100644 --- a/Open-ILS/src/templates/base.tt2 +++ b/Open-ILS/src/templates/base.tt2 @@ -26,9 +26,13 @@
+ [% IF no_content_pane %] + [% content %] + [% ELSE %]
[% content %]
+ [% END %]
diff --git a/Open-ILS/src/templates/url_verify/create_session.tt2 b/Open-ILS/src/templates/url_verify/create_session.tt2 index d29ccd9765..72983e90ab 100644 --- a/Open-ILS/src/templates/url_verify/create_session.tt2 +++ b/Open-ILS/src/templates/url_verify/create_session.tt2 @@ -20,6 +20,7 @@ +
+
+
[% ctx.page_title %] -
+
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ +[% END %] diff --git a/Open-ILS/src/templates/url_verify/select_urls.tt2 b/Open-ILS/src/templates/url_verify/select_urls.tt2 index b3985d95ef..8ad683aa0b 100644 --- a/Open-ILS/src/templates/url_verify/select_urls.tt2 +++ b/Open-ILS/src/templates/url_verify/select_urls.tt2 @@ -1,4 +1,4 @@ -[% WRAPPER base.tt2 %] +[% WRAPPER base.tt2 no_content_pane=1 %] [% ctx.page_title = "Link Checker - Select URLs" %] -
-
-
[% ctx.page_title %]
-
- -
+
+
[% ctx.page_title %] -
+
+ +
-
-
-
-
-
- +
+
@@ -56,6 +54,13 @@ +
diff --git a/Open-ILS/src/templates/url_verify/sessions.tt2 b/Open-ILS/src/templates/url_verify/sessions.tt2 new file mode 100644 index 0000000000..6458705457 --- /dev/null +++ b/Open-ILS/src/templates/url_verify/sessions.tt2 @@ -0,0 +1,51 @@ +[% WRAPPER base.tt2 no_content_pane=1 %] +[% ctx.page_title = "Link Checker" %] + +
+
[% ctx.page_title %]
+ +
+
+ + +
+
+ + + + + + + + + + + + +
[% l("Verification Attempts") %][% l("Creator ") %][% l("URL Selectors") %]
+
+[% END %] diff --git a/Open-ILS/web/js/dojo/openils/FlattenerStore.js b/Open-ILS/web/js/dojo/openils/FlattenerStore.js index 6f3f989979..d36100cb31 100644 --- a/Open-ILS/web/js/dojo/openils/FlattenerStore.js +++ b/Open-ILS/web/js/dojo/openils/FlattenerStore.js @@ -182,7 +182,9 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { if (!this.mapKey) this._get_map_key(); - return this._build_flattener_params(req); + var p = this._build_flattener_params(req); + console.debug("_fetch_prepare() returning " + dojo.toJson(p)); + return p; }, "_fetch_execute": function(params,handle_as,mime_type,onload,onerror) { @@ -390,9 +392,17 @@ if (!dojo._hasResource["openils.FlattenerStore"]) { else might_be_a_lie += obj.length; + console.debug( + "process_fetch() calling onBegin with " + + might_be_a_lie + ", " + dojo.toJson(req) + ); req.onBegin.call(callback_scope, might_be_a_lie, req); } + console.debug( + "about to call onItem for " + obj.length + + " elements in the obj array" + ); dojo.forEach( obj, function(item) { diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/CreateSession.js b/Open-ILS/web/js/dojo/openils/URLVerify/CreateSession.js index cb79e82b79..c17681caf9 100644 --- a/Open-ILS/web/js/dojo/openils/URLVerify/CreateSession.js +++ b/Open-ILS/web/js/dojo/openils/URLVerify/CreateSession.js @@ -3,8 +3,10 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { dojo.require("dojox.jsonPath"); dojo.require("fieldmapper.OrgUtils"); dojo.require("openils.Util"); + dojo.require("openils.CGI"); dojo.require("openils.PermaCrud"); dojo.require("openils.widget.FilteringTreeSelect"); + dojo.require("openils.URLVerify.Verify"); dojo.requireLocalization("openils.URLVerify", "URLVerify"); @@ -87,7 +89,7 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { ); }; - /* 2) save the tag/subfield sets for URL extraction, */ + /* 2a) save the tag/subfield sets for URL extraction, */ module.save_tags = function() { module.progress_dialog.attr("title", localeStrings.SAVING_TAGS); module.progress_dialog.show(); /* sic */ @@ -95,7 +97,8 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { uvus_progress = 0; /* Note we're not using openils.PermaCrud, which is inadequate - * when you want transactions. Thanks for figuring it out, Bill. */ + * when you need one big transaction. Thanks for figuring it + * out Bill. */ var pcrud_raw = new OpenSRF.ClientSession("open-ils.pcrud"); pcrud_raw.connect(); @@ -106,9 +109,7 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { "oncomplete": function(r) { module._create_uvus_one_at_a_time( pcrud_raw, - module.tag_and_subfields.generate_uvus( - module.session_id - ) + module.tag_and_subfields.generate_uvus(module.session_id) ); } }).send(); @@ -125,7 +126,7 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { {"maximum": uvus_list.length, "progress": ++uvus_progress} ); - uvus_list.shift(); /* /now/ actually shorten the list */ + uvus_list.shift(); /* /now/ actually shorten working list */ if (uvus_list.length < 1) { pcrud_raw.request({ @@ -147,8 +148,9 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { }; /* 3) search and populate the container (API call). */ - var search_result_count = 0; module.perform_search = function() { + var search_result_count = 0; + module.progress_dialog.attr("title", localeStrings.PERFORMING_SEARCH); module.progress_dialog.show(true); @@ -180,11 +182,13 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { module.progress_dialog.show(true); if (no_url_selection.checked) { - location.href = oilsBasePath + - "/url_verify/validation_review?" + - "session_id=" + module.session_id + - "&validate=1"; + /* verify URLs and ultimately redirect to review page */ + openils.URLVerify.Verify.go( + module.session_id, null, module.progress_dialog + ); } else { + /* go to the URL selection page, allowing users to + * selectively verify URLs */ location.href = oilsBasePath + "/url_verify/select_urls?session_id=" + module.session_id; @@ -200,14 +204,12 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { * fewer moving parts that can go haywire anyway. */ module._populate_saved_searches = function(node) { - var pcrud = new openils.PermaCrud(); - var list = pcrud.retrieveAll( + var list = module.pcrud.retrieveAll( "asq", {"order_by": {"asq": "label"}} ); dojo.forEach( - list, - function(o) { + list, function(o) { dojo.create( "option", { "innerHTML": o.label(), @@ -217,8 +219,6 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { ); } ); - - pcrud.disconnect(); }; /* set up an all-org-units-in-the-tree selector */ @@ -234,7 +234,34 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { module.org_selector = widget; }; + /* Can only be called by setup() */ + module._clone = function(id) { + module.progress_dialog.attr("title", localeStrings.CLONING); + var old_session = module.pcrud.retrieve( + "uvs", id, {"flesh": 1, "flesh_fields": {"uvs": ["selectors"]}} + ); + + /* Set name to "Copy of [old name]" */ + uv_session_name.attr( + "value", dojo.string.substitute( + localeStrings.CLONE_SESSION_NAME, [old_session.name()] + ) + ); + + /* Set search field. */ + uv_search.attr("value", old_session.search()); + + /* Explain to user why we don't affect the saved searches picker. */ + if (old_session.search().match(/saved_query/)) + openils.Util.show("clone-saved-search-warning"); + + /* Add related xpaths (URL selectors) to TagAndSubfieldsMgr. */ + module.tag_and_subfields.add_xpaths(old_session.selectors()); + }; + module.setup = function(saved_search_id, org_selector_id, progress_dialog) { + module.pcrud = new openils.PermaCrud(); /* only used for setup */ + module.progress_dialog = progress_dialog; module.progress_dialog.attr("title", localeStrings.INTERFACE_SETUP); @@ -243,6 +270,12 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { module._populate_saved_searches(dojo.byId(saved_search_id)); module._prepare_org_selector(dojo.byId(org_selector_id)); + var cgi = new openils.CGI(); + if (cgi.param("clone")) + module._clone(cgi.param("clone")); + + module.pcrud.disconnect(); + module.progress_dialog.hide(); }; @@ -286,8 +319,37 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { "innerHTML": "[X]" /* XXX i18n */ }, div, "last" ); + }; - this.counter++; + this.add_xpaths = function(xpaths) { + if (!dojo.isArray(xpaths)) { + console.info("No xpaths to add"); + return; + } + + dojo.forEach( + xpaths, dojo.hitch(this, function(xpath) { + var newid = "t-and-s-row-" + String(this.counter++); + var div = dojo.create( + "div", { + "id": newid, + "innerHTML": localeStrings.XPATH + + " " + + xpath.xpath() + "" + }, this.container_id, "last" + ); + dojo.create( + "a", { + "href": "javascript:void(0);", + "onclick": function() { + var me = dojo.byId(newid); + me.parentNode.removeChild(me); + }, + "innerHTML": "[X]" /* XXX i18n */ + }, div, "last" + ); + }) + ); }; /* return a boolean indicating whether or not we have any rows */ @@ -307,27 +369,52 @@ if (!dojo._hasResource["openils.URLVerify.CreateSession"]) { '[id^="t-and-s-row-"]', dojo.byId(this.container_id) ).forEach( function(row) { - var tag = dojo.query(".t-and-s-tag", row)[0].innerHTML; - var subfield = dojo.query(".t-and-s-subfields", row)[0].innerHTML; - - var existing; - if ((existing = uniquely_grouped[tag])) { /* sic, assignment */ - existing = openils.Util.uniqueElements( - (existing + subfield).split("") - ).sort().join(""); + var holds_xpath = dojo.query(".t-and-s-xpath", row); + if (holds_xpath.length) { + uniquely_grouped.xpath = uniquely_grouped.xpath || []; + uniquely_grouped.xpath.push(holds_xpath[0].innerHTML); } else { - uniquely_grouped[tag] = subfield; + var tag = dojo.query(".t-and-s-tag", row)[0].innerHTML; + var subfield = + dojo.query(".t-and-s-subfields", row)[0].innerHTML; + + var existing; + if ((existing = uniquely_grouped[tag])) { // assignment + existing = openils.Util.uniqueElements( + (existing + subfield).split("") + ).sort().join(""); + } else { + uniquely_grouped[tag] = subfield; + } } } ); var uvus_list = []; + /* Handle things that are already in XPath form first (these + * come from cloning link checker sessions. */ + if (uniquely_grouped.xpath) { + dojo.forEach( + uniquely_grouped.xpath, + function(xpath) { + var obj = new uvus(); + + obj.session(session_id); + obj.xpath(xpath); + uvus_list.push(obj); + } + ); + delete uniquely_grouped.xpath; + } + + /* Now handle anything entered by hand. */ for (var tag in uniquely_grouped) { var obj = new uvus(); obj.session(session_id); - /* XXX TODO handle control fields (no subfields) */ + /* XXX TODO Handle control fields (No subfields. but would + * control fields ever contain URLs? Don't know.) */ obj.xpath( "//*[@tag='" + tag + "']/*[" + uniquely_grouped[tag].split("").map( diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/ReviewAttempt.js b/Open-ILS/web/js/dojo/openils/URLVerify/ReviewAttempt.js new file mode 100644 index 0000000000..d9a1cf516d --- /dev/null +++ b/Open-ILS/web/js/dojo/openils/URLVerify/ReviewAttempt.js @@ -0,0 +1,67 @@ +if (!dojo._hasResource["openils.URLVerify.ReviewAttempt"]) { + dojo.require("dojo.string"); + dojo.require("openils.CGI"); + dojo.require("openils.PermaCrud"); + dojo.require("dijit.Tooltip"); + + dojo.requireLocalization("openils.URLVerify", "URLVerify"); + + dojo._hasResource["openils.URLVerify.ReviewAttempt"] = true; + dojo.provide("openils.URLVerify.ReviewAttempt"); + + dojo.declare("openils.URLVerify.ReviewAttempt", null, {}); + + /* Take care that we add nothing to the global namespace. + * This is not an OO module so much as a container for + * functions needed by a specific interface. */ + +(function() { + var module = openils.URLVerify.ReviewAttempt; + var localeStrings = + dojo.i18n.getLocalization("openils.URLVerify", "URLVerify"); + + module._display_session_name = function() { + var pcrud = new openils.PermaCrud(); + + var attempt = pcrud.retrieve( + "uvva", module.attempt_id, { + "flesh": 1, "flesh_fields": {"uvva": ["session"]} + } + ); + + dojo.byId("session-link-here").innerHTML = + "" + + dojo.string.substitute( + localeStrings.SESSION_NAME, [attempt.session().name()] + ) + ""; + + pcrud.disconnect(); + + new dijit.Tooltip({ + "connectId": "session-link-here", + "label": localeStrings.SELECT_MORE + }); + }; + + module.setup = function(grid, progress_dialog) { + module.progress_dialog = progress_dialog; + module.progress_dialog.attr("title", localeStrings.INTERFACE_SETUP); + module.progress_dialog.show(true); + + var cgi = new openils.CGI(); + module.attempt_id = cgi.param("attempt_id"); + + module.grid = grid; + + module.grid.setBaseQuery({"attempt_id": module.attempt_id}); + + module.grid.refresh(); + + module._display_session_name(); + + module.progress_dialog.hide(); + }; + +}()); + +} diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/SelectURLs.js b/Open-ILS/web/js/dojo/openils/URLVerify/SelectURLs.js index b8992ac3f8..83e5fdf49b 100644 --- a/Open-ILS/web/js/dojo/openils/URLVerify/SelectURLs.js +++ b/Open-ILS/web/js/dojo/openils/URLVerify/SelectURLs.js @@ -2,6 +2,8 @@ if (!dojo._hasResource["openils.URLVerify.SelectURLs"]) { dojo.require("dojo.string"); dojo.require("openils.CGI"); dojo.require("openils.Util"); + dojo.require("openils.PermaCrud"); + dojo.require("openils.URLVerify.Verify"); dojo.requireLocalization("openils.URLVerify", "URLVerify"); @@ -20,84 +22,78 @@ if (!dojo._hasResource["openils.URLVerify.SelectURLs"]) { dojo.i18n.getLocalization("openils.URLVerify", "URLVerify"); module.setup = function(grid, progress_dialog) { + module.progress_dialog = progress_dialog; + module.progress_dialog.attr("title", localeStrings.INTERFACE_SETUP); + module.progress_dialog.show(true); + var cgi = new openils.CGI(); module.session_id = cgi.param("session_id"); module.grid = grid; - module.grid.attr("query", {"session_id": module.session_id}); + module.grid.setBaseQuery({"session_id": module.session_id}); + module.grid.refresh(); // Alternative to grid.refresh() once filter is set up //module.grid.fetchLock = false; //module.grid.filterUi.doApply(); + + module._display_session_name(); + + module.progress_dialog.hide(); }; - module.verify_selected = function() { - var really_everything = false; - - if (module.grid.everythingSeemsSelected()) - really_everything = confirm(localeStrings.VERIFY_ALL); - - module.clear_attempt_display(); - progress_dialog.attr("title", localeStrings.VERIFICATION_BEGIN); - progress_dialog.show(); - - fieldmapper.standardRequest( - ["open-ils.url_verify", "open-ils.url_verify.session.verify"], { - "params": [ - openils.User.authtoken, - module.session_id, - really_everything ? null : module.grid.getSelectedIDs() - ], + module._display_session_name = function() { + var pcrud = new openils.PermaCrud(); + + pcrud.retrieve( + "uvs", module.session_id, { "async": true, - "onresponse": function(r) { + "oncomplete": function(r) { if (r = openils.Util.readResponse(r)) { - progress_dialog.attr( - "title", + dojo.byId("session-name-here").innerHTML = dojo.string.substitute( - localeStrings.VERIFICATION_PROGRESS, - [r.total_processed] - ) - ); - progress_dialog.update({ - "maximum": r.url_count, - "progress": r.total_excluding_redirects - }); - - if (r.attempt) - module.update_attempt_display(r.attempt); + localeStrings.SESSION_NAME, [r.name()] + ); + + pcrud.disconnect(); } } } - ) - - module.grid.getSelectedIDs(); + ); }; - module.clear_attempt_display = function() { - dojo.empty(dojo.byId("url-verify-attempt-id")); - dojo.empty(dojo.byId("url-verify-attempt-start")); - dojo.empty(dojo.byId("url-verify-attempt-finish")); - }; + module.verify_selected = function() { + if (module.grid.getSelectedItems().length < 1) { + alert(localeStrings.NOTHING_SELECTED); + return; + } - module.update_attempt_display = function(attempt) { - dojo.byId("url-verify-attempt-id").innerHTML = - dojo.string.substitute( - localeStrings.VERIFICATION_ATTEMPT_ID, - [attempt.id()] - ); - dojo.byId("url-verify-attempt-start").innerHTML = - dojo.string.substitute( - localeStrings.VERIFICATION_ATTEMPT_START, - [attempt.start_time()] + if (module.grid.everythingSeemsSelected() && + confirm(localeStrings.VERIFY_ALL)) { + /* If we're here, the user wants to verify all URLs matching + * the grid's current filters. We need to reach down to the + * grid's store to do a special fetch to get all those IDs. */ + + module.grid.store.fetch({ + "query": dojo.clone(module.grid.query), + "queryOptions": {"all": true}, + "onComplete": function(rows) { + openils.URLVerify.Verify.go( + module.session_id, + dojo.map(rows, function(row) { return row.id; }), + module.progress_dialog + ); + } + }); + } else { + /* If we're here, the user wants to verify just the rows he + * specifically selected with the checkboxes. */ + openils.URLVerify.Verify.go( + module.session_id, + module.grid.getSelectedIDs(), + module.progress_dialog ); - - if (attempt.finish_time()) { - dojo.byId("url-verify-attempt-finish").innerHTML = - dojo.string.substitute( - localeStrings.VERIFICATION_ATTEMPT_FINISH, - [attempt.finish_time()] - ); } }; diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/Sessions.js b/Open-ILS/web/js/dojo/openils/URLVerify/Sessions.js new file mode 100644 index 0000000000..a2283e5187 --- /dev/null +++ b/Open-ILS/web/js/dojo/openils/URLVerify/Sessions.js @@ -0,0 +1,74 @@ +if (!dojo._hasResource["openils.URLVerify.Sessions"]) { + dojo.require("dojo.string"); + dojo.require("openils.Util"); + dojo.require("openils.URLVerify.Verify"); + + dojo.requireLocalization("openils.URLVerify", "URLVerify"); + + dojo._hasResource["openils.URLVerify.Sessions"] = true; + dojo.provide("openils.URLVerify.Sessions"); + + dojo.declare("openils.URLVerify.Sessions", null, {}); + + /* Take care that we add nothing to the global namespace. + * This is not an OO module so much as a container for + * functions needed by a specific interface. */ + +(function() { + var module = openils.URLVerify.Sessions; + var localeStrings = + dojo.i18n.getLocalization("openils.URLVerify", "URLVerify"); + + module.setup = function(grid, org_selector) { + module.grid = grid; + + module.setup_org_selector_for_grid(org_selector); + }; + + module.setup_org_selector_for_grid = function(org_selector) { + function filter_grid_by_selected_org() { + module.grid.query = { + "owning_lib": org_selector.attr("value") + }; + module.grid.refresh(); + } + + new openils.User().buildPermOrgSelector( + "URL_VERIFY", org_selector, null, + function() { + dojo.connect( + org_selector, "onChange", filter_grid_by_selected_org + ); + filter_grid_by_selected_org(); + } + ); + }; + + module.format_id = function(str) { + if (!str) + return ""; + + return "" + str + + " " + + localeStrings.CLONE_SESSION + ""; + }; + + module.format_attempts = function(list) { + if (!dojo.isArray(list)) return ""; + + return dojo.map( + list, function(id) { + if (isNaN(id)) + return ""; + return "" + + id + ""; + } + ).join(", "); + }; + +}()); + +} diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/Verify.js b/Open-ILS/web/js/dojo/openils/URLVerify/Verify.js new file mode 100644 index 0000000000..1839af8569 --- /dev/null +++ b/Open-ILS/web/js/dojo/openils/URLVerify/Verify.js @@ -0,0 +1,59 @@ +if (!dojo._hasResource["openils.URLVerify.Verify"]) { + + dojo.requireLocalization("openils.URLVerify", "URLVerify"); + + dojo._hasResource["openils.URLVerify.Verify"] = true; + dojo.provide("openils.URLVerify.Verify"); + + dojo.declare("openils.URLVerify.Verify", null, {}); + + /* Take care that we add nothing to the global namespace. + * This is not an OO module so much as a container for + * functions needed by specific interfaces. */ + +(function() { + var module = openils.URLVerify.Verify; + var localeStrings = + dojo.i18n.getLocalization("openils.URLVerify", "URLVerify"); + + module.go = function(session_id, url_id_list, progress_dialog) { + progress_dialog.attr("title", localeStrings.VERIFICATION_DIALOG); + progress_dialog.show(); + + fieldmapper.standardRequest( + ["open-ils.url_verify", "open-ils.url_verify.session.verify"], { + "params": [openils.User.authtoken, session_id, url_id_list], + "async": true, + "onresponse": function(r) { + if (r = openils.Util.readResponse(r)) { + progress_dialog.update({ + "maximum": r.url_count, + "progress": r.total_excluding_redirects + }); + + if (r.attempt) { + module.attempt = r.attempt; + progress_dialog.show( + false, + dojo.string.substitute( + localeStrings.VERIFICATION_ATTEMPT_ID, + [r.attempt.id()] + ) + ); + } + } + }, + "oncomplete": function() { + progress_dialog.show(true); + progress_dialog.attr("title", localeStrings.REDIRECTING); + location.href = oilsBasePath + + "/url_verify/review_attempt?attempt_id=" + + module.attempt.id(); + } + } + ); + }; + +}()); + +} diff --git a/Open-ILS/web/js/dojo/openils/URLVerify/nls/URLVerify.js b/Open-ILS/web/js/dojo/openils/URLVerify/nls/URLVerify.js index d0b6f3de7d..d2128dbde9 100644 --- a/Open-ILS/web/js/dojo/openils/URLVerify/nls/URLVerify.js +++ b/Open-ILS/web/js/dojo/openils/URLVerify/nls/URLVerify.js @@ -3,13 +3,19 @@ "SAVING_TAGS": "Saving tag/subfield selectors ...", "PERFORMING_SEARCH": "Performing search ...", "EXTRACTING_URLS": "Extracting URLs ...", - "NEED_UVUS": "You must add some tag and subfield combinations", + "NEED_UVUS": "You must add some tag and subfield combinations.", "REDIRECTING": "Loading next interface ...", "INTERFACE_SETUP": "Setting up interface ...", - "VERIFY_ALL": "Click 'OK' to verify ALL the URLs found in this session. Click 'Cancel' if the selected, visible URLs are the only ones you want verified.", - "VERIFICATION_BEGIN": "Verifying URLs. This can take a moment ...", - "VERIFICATION_PROGRESS": "Verifying URLs: ${0} URLs processed ...", - "VERIFICATION_ATTEMPT_ID": "Last verification attempt ID: ${0}", - "VERIFICATION_ATTEMPT_START": "Attempt start time: ${0}", - "VERIFICATION_ATTEMPT_FINISH": "Attempt finish time: ${0}" + "VERIFY_ALL": "Click 'OK' to verify ALL the URLs that belong to this session and match all your search terms. Click 'Cancel' if the selected, visible URLs are the only ones you want verified.", + "VERIFICATION_DIALOG": "Verifying URLs ...", + "VERIFICATION_ATTEMPT_ID": "Verification attempt ID: ${0}", + "NOTHING_SELECTED": "No rows are selected, so no action will be taken.", + "REVIEW_ATTEMPT": "Review this verification attempt", + "CLONE_SESSION": "Clone", + "REREVIEW": "Review / Verify", + "CLONING": "Cloning existing session ...", + "CLONE_SESSION_NAME": "Copy of ${0}", + "XPATH": "XPath", + "SESSION_NAME": "Session '${0}'", + "SELECT_MORE": "Click here to review all session URLs and/or select other URLs to verify" } diff --git a/Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js b/Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js index 187b69f00f..bad2762fc2 100644 --- a/Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js +++ b/Open-ILS/web/js/dojo/openils/widget/FlattenerFilterDialog.js @@ -6,6 +6,24 @@ if (!dojo._hasResource["openils.widget.FlattenerFilterDialog"]) { dojo.declare( "openils.widget.FlattenerFilterDialog", [ dijit.Dialog, openils.widget.FlattenerFilterPane - ] + ], { + "constructor": function() { + dojo.connect( + this, "postCreate", this, + function() { + /* Of course I don't *want* to hardcode 400px below, + * but without some kind of maxHeight, this dialog + * can just grow forever, until it's no longer + * possible to access the close button on the top or + * the buttons at the bottom. */ + dojo.style( + this.domNode, { + "maxHeight": "400px", "overflow": "auto" + } + ); + } + ); + } + } ); } diff --git a/Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js b/Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js index e83fca97df..d66ee66448 100644 --- a/Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js +++ b/Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js @@ -18,7 +18,9 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { "columnPersistKey": null, "autoCoreFields": false, "autoCoreFieldsUnsorted": false, + "autoCoreFieldsFilter": false, "autoFieldFields": null, + "autoFieldFieldsUnsorted": null, /* array, subset of autoFieldFields */ "showLoadFilter": false, /* use FlattenerFilter(Dialog|Pane) */ "filterAlwaysInDiv": null, /* use FlattenerFilterPane and put its content in this HTML element */ @@ -31,6 +33,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { OU selectors so that it should get mixed correctly with the generated query from the filter dialog. */ + "savedFiltersInterface": null, /* These potential constructor arguments may be useful to * FlattenerGrid in their own right, and are passed to @@ -286,16 +289,23 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { return {"labels": labels, "columns": columns}; }, - "_getAutoFieldFields": function(fmclass) { - return dojo.clone( + "_getAutoFieldFields": function(fmclass, path) { + var field_list = 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; } + function(f) { return !f.virtual && f.datatype != "link"; } ); + + /* Sort fields unless the path is named in grid property + * 'autoFieldFieldsUnsorted' (array). */ + if (!dojo.isArray(this.autoFieldFieldsUnsorted) || + this.autoFieldFieldsUnsorted.indexOf(path) == -1) { + field_list = field_list.sort( + function(a, b) { return a.label > b.label ? 1 : -1; } + ); + } + + return field_list; }, /* Take our core class (this.fmClass) and add table columns for @@ -314,7 +324,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { } dojo.forEach( - fields, function(f) { + fields, dojo.hitch(this, function(f) { if (f.datatype == "link" || f.virtual) return; @@ -329,10 +339,10 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { cell_list.push({ "field": f.name, "name": f.label, - "fsort": true /*, - "_visible": false */ + "fsort": true, + "ffilter": this.autoCoreFieldsFilter }); - } + }) ); }, @@ -350,7 +360,9 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { return; } else { dojo.forEach( - self._getAutoFieldFields(beginning.fmClass), + self._getAutoFieldFields( + beginning.fmClass, path + ), function(field) { var would_be_path = path + "." + field.name; @@ -506,10 +518,10 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { "fmClass": this.fmClass, "mapTerminii": this.mapTerminii, "useDiv": this.filterAlwaysInDiv, - "compact": true, "initializers": this.filterInitializers, "widgetBuilders": this.filterWidgetBuilders, - "suppressFilterFields": this.suppressFilterFields + "suppressFilterFields": this.suppressFilterFields, + "savedFiltersInterface": this.savedFiltersInterface }); this.filterUi.onApply = dojo.hitch( @@ -922,6 +934,13 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) { this.getSelectedIDs(); this.print(null, null, id_blob); + }, + + "setBaseQuery": function(query) { /* sets a persistent query + that always gets mixed in + with whatever you do in the + filter dialog */ + this._baseQuery = dojo.clone(this.query = query); } } ); diff --git a/Open-ILS/web/js/dojo/openils/widget/PCrudFilterPane.js b/Open-ILS/web/js/dojo/openils/widget/PCrudFilterPane.js index 205deb4374..4c6af87b6b 100644 --- a/Open-ILS/web/js/dojo/openils/widget/PCrudFilterPane.js +++ b/Open-ILS/web/js/dojo/openils/widget/PCrudFilterPane.js @@ -35,6 +35,8 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { dojo.require('openils.widget.AutoFieldWidget'); dojo.require('dijit.form.FilteringSelect'); dojo.require('dijit.form.Button'); + dojo.require('dijit.form.DropDownButton'); + dojo.require('dijit.TooltipDialog'); dojo.require('dojo.data.ItemFileReadStore'); dojo.require('openils.Util'); @@ -98,6 +100,18 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { "label": pcFilterLocaleStrings.OPERATOR_LTE, "param_count": 1, "strict": true + }, { + "name": "in", + "label": pcFilterLocaleStrings.OPERATOR_IN, + "param_count": null, /* arbitrary number, special */ + "strict": true, + "minimal": true + }, { + "name": "not in", + "label": pcFilterLocaleStrings.OPERATOR_NOT_IN, + "param_count": null, /* arbitrary number, special */ + "strict": true, + "minimal": true }, { "name": "between", "label": pcFilterLocaleStrings.OPERATOR_BETWEEN, @@ -151,7 +165,7 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { var ops = openils.Util.objectProperties(clause); var op = ops.pop(); - var matches = op.match(/^not (\w+)$/); + var matches = op.match(/^not [lb].+$/); /* "not in" needs no change */ if (matches) { clause[matches[1]] = clause[op]; delete clause[op]; @@ -160,6 +174,30 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { return false; } + /* Given a value, add it to selector options if it's not already there, + * and select it. */ + function _add_or_at_least_select(value, selector) { + var found = false; + + for (var i = 0; i < selector.options.length; i++) { + var option = selector.options[i]; + if (option.value == value) { + found = true; + option.selected = true; + } + } + + if (!found) { + dojo.create( + "option", { + "innerHTML": value, + "value": value, + "selected": "selected" + }, selector + ); + } + } + /* This is not the dijit per se. Search further in this file for * "dojo.declare" for the beginning of the dijit. * @@ -249,20 +287,58 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { return result; }; - this.add_row = function(initializer) { + this._validate_initializer = function(initializer, onsuccess) { + this.field_store.fetchItemByIdentity({ + "identity": initializer.field, + "onItem": dojo.hitch(this, function(item) { + if (item) { + onsuccess(); + } else { + console.debug( + "skipping initializer for field " + + initializer.field + " not present here" + ); + } + }) + }); + }; + + this._proceed_add_row = function(initializer) { + var row_id_list = openils.Util.objectProperties(this.rows); + + /* Kill initial empty row when adding pre-initialized rows. */ + if (row_id_list.length == 1 && initializer) { + var existing_row_id = row_id_list.shift(); + if (this.rows[existing_row_id].is_unset()) + this.remove_row(existing_row_id, true /* no_apply */); + } + 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.add_row = function(initializer) { + if (initializer) { + this._validate_initializer( + initializer, + dojo.hitch(this, function() { + this._proceed_add_row(initializer); + }) + ); + } else { + this._proceed_add_row(initializer); + } + }; + + this.remove_row = function(row_id, no_apply) { 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) + if (this.compact && !no_apply) this.do_apply(); }; @@ -320,6 +396,20 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { } }; + /* This is for generating a data structure so that we can store + * a representation of the state of the filter rows. Not for + * generating a query to be used in search. You want compile() for + * that. */ + this.serialize = function() { + var serialized = []; + for (var rowkey in this.rows) { /* row order doesn't matter */ + var row_ser = this.rows[rowkey].serialize(); + if (row_ser) + serialized.push(row_ser); + } + return dojo.toJson(serialized); + }; + this._init.apply(this, arguments); } @@ -383,6 +473,11 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { "scrollOnFocus": false, "onChange": function(value) { self.update_selected_field(value); + if (this.and_then) { /* ugh. also, self != this. */ + var once = this.and_then; + delete this.and_then; + once(); + } }, "store": this.filter_row_manager.field_store }, dojo.create("span", {}, td) @@ -413,12 +508,12 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { }; this._create_value_slot = function(use_element) { + var how = {"innerHTML": "-"}; + if (use_element) - this.value_slot = dojo.create( - "span", {"innerHTML": "-"}, use_element - ); + this.value_slot = dojo.create("span", how, use_element); else - this.value_slot = dojo.create("td",{"innerHTML":"-"},this.tr); + this.value_slot = dojo.create("td", how, this.tr); }; this._create_remover = function(use_element) { @@ -438,7 +533,10 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { this._clear_value_slot = function() { if (this.value_widgets) { this.value_widgets.forEach( - function(autowidg) { autowidg.widget.destroy(); } + function(autowidg) { + if (autowidg.widget) + autowidg.widget.destroy(); + } ); delete this.value_widgets; } @@ -454,28 +552,112 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { this.value_widgets = []; - var param_count = this.operator_selector.item.param_count; - - /* This is where find and deploy custom widget builders. */ + /* This is where find custom widget builders to deploy shortly. */ 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} - }); + /* How many value widgets do we need for this operator? */ + var param_count = + this.operator_selector.store.getValue( + this.operator_selector.item, "param_count" + ); - widg.build(); - this.value_widgets.push(widg); + if (param_count === null) { + /* When param_count is null, we invoke the special case of + * preparing widgets for building a dynamic set of values. + * All other cases are handled by the else branch. */ + this._build_set_value_widgets(constr); + } else { + for (var i = 0; i < param_count; i++) { + this.value_widgets.push( + this._build_one_value_widget(constr) + ); + } } }; + this._build_set_value_widgets = function(constr) { + var value_widget = dojo.create( + "select", { + "multiple": "multiple", + "size": 4, + "style": { + "width": "6em", + "verticalAlign": "middle", + "margin": "0 0.75em" + } + }, + this.value_slot + ); + var entry_widget = this._build_one_value_widget(constr); + var adder = dojo.create( + "a", { + "href": "javascript:void(0);", + "style": {"verticalAlign": "middle", "margin": "0 0.75em"}, + "innerHTML": "[+]", /* XXX i18n? */ + "onclick": dojo.hitch(this, function() { + _add_or_at_least_select( + this._value_for_compile(entry_widget), + value_widget + ); + entry_widget.widget.attr("value", ""); /* clear */ + }) + }, this.value_slot + ); + this.value_widgets.push(value_widget); + }; + + + /* Create just one value widget (used by higher-level functions + * that worry about how many are needed). */ + this._build_one_value_widget = function(constr) { + var widg = new constr({ + "fmClass": this.selected_field_fm_class, + "fmField": this.selected_field_fm_field, + "noDisablePkey": true, + "parentNode": dojo.create( + "span", { + "style": {"verticalAlign": "middle"} + }, this.value_slot + ), + "dijitArgs": {"scrollOnFocus": false} + }); + + widg.build(); + return widg; + }; + + this._value_for_serialize = function(widg) { + if (!widg.widget) /* widg is */ + return dojo.filter( + widg.options, + function(o) { return o.selected; } + ).map( + function(o) { return o.value; } + ); + else if (widg.useCorrectly) + return widg.widget.attr("value"); + else if (this.selected_field_is_indirect) + return widg.widget.attr("displayedValue"); + else + return widg.getFormattedValue(); + } + /* for ugly special cases in compilation */ this._null_clause = function() { var opname = this.get_selected_operator_name(); @@ -498,8 +680,15 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { }; this.get_selected_operator_name = function() { - var op = this.get_selected_operator(); - return op ? op.name : null; + var item = this.get_selected_operator(); + if (item) { + return this.operator_selector.store.getValue(item, "name"); + } else { + console.warn( + "Could not determine selected operator. " + + "Something is about to break." + ); + } }; this.update_selected_operator = function(value) { @@ -526,17 +715,46 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { } }; + this.serialize = function() { + if (!this.selected_field) + return; + + var serialized = { + "field": this.selected_field, + "operator": this.get_selected_operator_name() + }; + + var values; + + if (this.value_widgets) { + values = this.value_widgets.map( + dojo.hitch( + this, function(w) { + return this._value_for_serialize(w); + } + ) + ); + } + + /* The following grew organically to be very silly and confusing. + * Could use a rethink (PCrudFilterRow.initialize() would also need + * matching changes). */ + if (values.length == 1) { + if (dojo.isArray(values[0])) + serialized.values = values[0]; + else + serialized.value = values[0]; + } else if (values.length > 1) { + serialized.values = values; + } + + return serialized; + }; + 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(); - } + dojo.hitch(this, this._value_for_compile) ); if (!values.length) { @@ -545,7 +763,13 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { var clause = {}; var op = this.get_selected_operator_name(); - var prep_function = function(o) { return o; /* no-op */ }; + var prep_function = function(o) { + if (dojo.isArray(o) && !o.length) + throw new Error(pcFilterLocaleStrings.EMPTY_LIST); + + return o; + }; + if (String(op).match(/like/)) prep_function = this._add_like_wildcards; @@ -570,19 +794,43 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { }; this.initialize = function(initializer) { + /* and_then is a nasty kludge callback called once at onChange */ + this.field_selector.and_then = dojo.hitch( + this, function() { + this.operator_selector.attr("value", initializer.operator); + + /* Caller supplies value for one value, values (array) for + * multiple. */ + if (typeof initializer.value !== "undefined" && + !initializer.values) { + initializer.values = [initializer.value]; + } + initializer.values = initializer.values || []; + + if (initializer.operator.match(/^(not ?)in$/)) { + /* "in" and "not in" need special treatement */ + dojo.forEach( + initializer.values, dojo.hitch(this, function(v) { + _add_or_at_least_select( + v, this.value_widgets[0] + ); + }) + ); + } else { + /* other operators work this way: */ + for (var i = 0; i < initializer.values.length; i++) { + this.value_widgets[i].widget.attr( + "value", initializer.values[i] + ); + } + } + } + ); 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.is_unset = function() { + return !Boolean(this.field_selector.attr("value")); }; this._init.apply(this, arguments); @@ -593,15 +841,16 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { { "useDiv": null, /* should always be null for subclass dialogs */ "initializers": null, - "compact": false, "widgetBuilders": null, "suppressFilterFields": null, + "savedFiltersInterface": null, "constructor": function(args) { for(var k in args) this[k] = args[k]; this.widgetIndex = 0; this.widgetCache = {}; + this.compact = Boolean(this.useDiv); /* Meaningless in a pane, but better here than in * PCrudFilterDialog so that we don't need to load i18n @@ -609,6 +858,124 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { this.title = this.title || pcFilterLocaleStrings.DEFAULT_DIALOG_TITLE; }, + "_buildSavedFilterControlsIfPerms": function(holder) { + (new openils.User()).getPermOrgList( + "SAVED_FILTER_DIALOG_FILTERS", + dojo.hitch(this, function(id_list) { + this._buildSavedFilterControls(id_list, holder); + }), + true, true + ); + }, + + "_buildSavedFilterControls": function(id_list, holder) { + if (!id_list || !id_list.length) { + console.info("Not showing saved filter controls; no perm"); + return; + } + + var fs_list = (new openils.PermaCrud()).search( + "cfdfs", { + "owning_lib": id_list, + "interface": this.savedFiltersInterface + }, { + "order_by": [ + {"class": "cfdfs", "field": "owning_lib"}, + {"class": "cfdfs", "field": "name"} + ], + "async": true, + "oncomplete": dojo.hitch(this, function(r) { + if (r = openils.Util.readResponse(r)) { + this._buildSavedFilterLoader(r, holder); + } + }) + } + ); + + this._buildSavedFilterSaver(holder); + }, + + "_buildSavedFilterLoader": function(fs_list, holder) { + var self = this; + var load_content = dojo.create( + "div", { + "innerHTML": pcFilterLocaleStrings.CHOOSE_FILTER_TO_LOAD + } + ); + + var selector = dojo.create( + "select", { + "multiple": "multiple", + "size": 4, + "style": { + "verticalAlign": "middle", "margin": "0 0.75em" + } + }, load_content, "last" + ); + + dojo.forEach( + fs_list, function(fs) { + dojo.create( + "option", { + "innerHTML": fs.name(), + "value": dojo.toJson([fs.id(), + dojo.fromJson(fs.filters())]) + }, selector + ); + } + ); + + var applicator = dojo.create( + "a", { + "href": "javascript:void(0);", + "onclick": function() { + dojo.filter( + selector.options, + function(o){return o.selected;} + ).map( + function(o){return dojo.fromJson(o.value)[1];} + ).forEach( + function(o){ + o.forEach( + function(p) { + self.filter_row_manager.add_row(p); + } + ); + } + ); + dijit.popup.close(self.filter_set_loader.dropDown); + }, + "innerHTML": pcFilterLocaleStrings.APPLY + }, load_content, "last" + ); + + this.filter_set_loader = new dijit.form.DropDownButton({ + "dropDown": new dijit.TooltipDialog({ + "content": load_content + }), + "label": pcFilterLocaleStrings.LOAD_FILTERS + }, dojo.create("span", {}, holder)); + }, + + "_buildSavedFilterSaver": function(holder) { + this.filter_set_loader = new dijit.form.Button({ + "onClick": dojo.hitch( + this, function() { + this.saveFilters( + /* XXX I know some find prompt() objectionable + * somehow, but I can't seem to type into any + * text inputs that I put inside TooltipDialog + * instances, so meh. */ + prompt( + pcFilterLocaleStrings.NAME_SAVED_FILTER_SET + ) + ); + } + ), + "label": pcFilterLocaleStrings.SAVE_FILTERS + }, dojo.create("span", {}, holder)); + }, + "_buildButtons": function() { var self = this; @@ -649,6 +1016,9 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { }, dojo.create("span", {}, button_holder) ); } + + if (this.savedFiltersInterface) + this._buildSavedFilterControlsIfPerms(button_holder); }, "_buildFieldStore": function() { @@ -692,6 +1062,35 @@ if (!dojo._hasResource['openils.widget.PCrudFilterPane']) { }); }, + "saveFilters": function(name, oncomplete) { + var filters_value = this.filter_row_manager.serialize(); + var filter_set = new cfdfs(); + filter_set.name(name); + filter_set.interface(this.savedFiltersInterface); + filter_set.owning_lib(openils.User.user.ws_ou()); + filter_set.creator(openils.User.user.id()); /* not reliable */ + filter_set.filters(filters_value); + + (new openils.PermaCrud()).create( + filter_set, { + "oncomplete": dojo.hitch(this, function() { + var selector = dojo.query( + "select[multiple]", + this.filter_set_loader.dropDown.domNode + )[0]; + dojo.create( + "option", { + "innerHTML": name, + "value": dojo.toJson([-1, + dojo.fromJson(filters_value)]) + }, selector + ); + if (oncomplete) oncomplete(); + }) + } + ); + }, + "hide": function() { try { this.inherited(arguments); diff --git a/Open-ILS/web/js/dojo/openils/widget/nls/PCrudFilterPane.js b/Open-ILS/web/js/dojo/openils/widget/nls/PCrudFilterPane.js index 8e76dd59de..761c1d3c4d 100644 --- a/Open-ILS/web/js/dojo/openils/widget/nls/PCrudFilterPane.js +++ b/Open-ILS/web/js/dojo/openils/widget/nls/PCrudFilterPane.js @@ -7,6 +7,8 @@ "OPERATOR_GT": "is greater than", "OPERATOR_LTE": "is less than or equal to", "OPERATOR_GTE": "is greater than or equal to", + "OPERATOR_IN": "is in the set", + "OPERATOR_NOT_IN": "is not in the set", "OPERATOR_BETWEEN": "is between", "OPERATOR_NOT_BETWEEN": "is not between", "OPERATOR_LIKE": "is like", @@ -16,5 +18,11 @@ "DEFAULT_DIALOG_TITLE": "Filter Results", "ADD_ROW": "Add Row", "APPLY": "Apply", - "CANCEL": "Cancel" + "CANCEL": "Cancel", + "LOAD_FILTERS": "Load Filters", + "SAVE_FILTERS": "Save Filters", + "CHOOSE_FILTER_TO_LOAD": "Choose filter sets to load", + "NAME_SAVED_FILTER_SET": "Enter a name for your saved filter set:", + "NEED_NAME": "You must enter a name for the saved filters.", + "EMPTY_LIST": "Cannot compile search filter. Empty lists not allowed." } diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index 2721d4048a..b700232bc7 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -964,6 +964,8 @@ + + diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js index 54320d47b0..322ca8ab55 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js +++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js @@ -181,7 +181,7 @@ main.menu.prototype = { } - function open_eg_web_page(path, labelKey, event) { + function open_eg_web_page(path, labelKey, event, content_params) { // tab label labelKey = labelKey || 'menu.cmd_open_conify.tab'; @@ -190,11 +190,14 @@ main.menu.prototype = { // URL var loc = urls.XUL_BROWSER + '?url=' + window.escape(obj.url_prefix('EG_WEB_BASE/') + path); + content_params = content_params || { + 'no_xulG': false, + 'show_print_button': true, + 'show_nav_buttons': true + }; + obj.command_tab( - event, - loc, - {tab_name : label, browser : false }, - {no_xulG : false, show_print_button : true, show_nav_buttons : true } + event, loc, {tab_name: label, browser: false}, content_params ); } @@ -1193,6 +1196,21 @@ main.menu.prototype = { obj.command_tab( event, url, {}, { 'id' : obj.data.last_patron } ); } ], + + 'cmd_url_verify' : [ + ['oncommand'], + function(event) { + open_eg_web_page( + "/eg/url_verify/sessions", + "menu.cmd_url_verify.tab", + event, { + 'no_xulG': false, + 'show_print_button': false, + 'show_nav_buttons': true + } + ); + } + ], 'cmd_retrieve_last_record' : [ ['oncommand'], diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul index 0d2126c398..7fe88a2483 100644 --- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul +++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul @@ -24,6 +24,7 @@ + @@ -427,6 +428,8 @@ + + diff --git a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties index 2405b95450..c2f6212c72 100644 --- a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties +++ b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties @@ -250,6 +250,7 @@ menu.cmd_acq_po.tab=Purchase Orders menu.cmd_acq_user_requests.tab=Patron Requests menu.cmd_acq_claim_eligible.tab=Claim-Ready Items menu.cmd_serial_batch_receive.tab=Batch Receive +menu.cmd_url_verify.tab=Link Checker menu.cmd_booking_resource.tab=Resources menu.cmd_booking_reservation.tab=Reservations menu.cmd_booking_reservation_pickup.tab=Reservation Pickup -- 2.43.2