Link checker: Some UI tweaks suggested by George Duimovich
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Wed, 21 Nov 2012 18:49:59 +0000 (13:49 -0500)
committerMike Rylander <mrylander@gmail.com>
Thu, 14 Feb 2013 19:19:17 +0000 (14:19 -0500)
  - Make the "Filter" link above FlattenerGrids a button and not a link.

  - Instead of IDs as links in some grid columns, have the ID show up in
    plain text and have links with a more descriptive name sit next to the
    ID.

  - Correct the settings for saving grid columns on the Select URLs and
    Review Attempt interfaces.

  - Tiny i18n fixes (page titles)

  - Fix lack of horizontal scrollbar on Select URLs interface, and also
    fix the way that if you clicked on said scrollbar in a case where
    your grid was taller than your browser window, the page would
    automatically scroll up to focus on your grid header row, and you
    couldn't actually manipulate the horizontal scrollbar.  We sadly
    pay for our horiz scrollbar with a doubled vertical scrollbar, but
    possibly someone can figure the Right way to fix such layout
    problems, which actually occur widely in similar interfaces in
    Evergreen.

  - Add buttons to download CSV on Select URLs and Review Attempt
    interfaces.

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
13 files changed:
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/ZZZZ.data.url_verify.sql
Open-ILS/src/templates/url_verify/create_session.tt2
Open-ILS/src/templates/url_verify/review_attempt.tt2
Open-ILS/src/templates/url_verify/select_urls.tt2
Open-ILS/src/templates/url_verify/sessions.tt2
Open-ILS/web/js/dojo/openils/FlattenerStore.js
Open-ILS/web/js/dojo/openils/URLVerify/ReviewAttempt.js
Open-ILS/web/js/dojo/openils/URLVerify/Sessions.js
Open-ILS/web/js/dojo/openils/URLVerify/nls/URLVerify.js
Open-ILS/web/js/dojo/openils/XUL.js
Open-ILS/web/js/dojo/openils/widget/FlattenerGrid.js
Open-ILS/web/js/dojo/openils/widget/nls/FlattenerGrid.js [new file with mode: 0644]

index 120120e..d4272a5 100644 (file)
@@ -12093,17 +12093,17 @@ INSERT INTO config.filter_dialog_interface (key, description) VALUES (
 );
 
 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
-    'url_verify.select_urls',
-    'url_verify',
+    'ui.grid_columns.url_verify.select_urls',
+    'gui',
     FALSE,
     oils_i18n_gettext(
-        'url_verify.select_urls',
+        'ui.grid_columns.url_verify.select_urls',
         'Link Checker''s URL Selection interface''s saved columns',
         'cust',
         'label'
     ),
     oils_i18n_gettext(
-        'url_verify.select_urls',
+        'ui.grid_columns.url_verify.select_urls',
         'Link Checker''s URL Selection interface''s saved columns',
         'cust',
         'description'
@@ -12112,17 +12112,17 @@ INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,dat
 );
 
 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
-    'url_verify.review_attempt',
-    'url_verify',
+    'ui.grid_columns.url_verify.review_attempt',
+    'gui',
     FALSE,
     oils_i18n_gettext(
-        'url_verify.review_attempt',
+        'ui.grid_columns.url_verify.review_attempt',
         'Link Checker''s Review Attempt interface''s saved columns',
         'cust',
         'label'
     ),
     oils_i18n_gettext(
-        'url_verify.review_attempt',
+        'ui.grid_columns.url_verify.review_attempt',
         'Link Checker''s Review Attempt interface''s saved columns',
         'cust',
         'description'
index 4e14754..968361b 100644 (file)
@@ -152,17 +152,17 @@ INSERT INTO config.filter_dialog_interface (key, description) VALUES (
 
 
 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
-    'url_verify.select_urls',
-    'url_verify',
+    'ui.grid_columns.url_verify.select_urls',
+    'gui',
     FALSE,
     oils_i18n_gettext(
-        'url_verify.select_urls',
+        'ui.grid_columns.url_verify.select_urls',
         'Link Checker''s URL Selection interface''s saved columns',
         'cust',
         'label'
     ),
     oils_i18n_gettext(
-        'url_verify.select_urls',
+        'ui.grid_columns.url_verify.select_urls',
         'Link Checker''s URL Selection interface''s saved columns',
         'cust',
         'description'
@@ -171,17 +171,17 @@ INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,dat
 );
 
 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
-    'url_verify.review_attempt',
-    'url_verify',
+    'ui.grid_columns.url_verify.review_attempt',
+    'gui',
     FALSE,
     oils_i18n_gettext(
-        'url_verify.review_attempt',
+        'ui.grid_columns.url_verify.review_attempt',
         'Link Checker''s Review Attempt interface''s saved columns',
         'cust',
         'label'
     ),
     oils_i18n_gettext(
-        'url_verify.review_attempt',
+        'ui.grid_columns.url_verify.review_attempt',
         'Link Checker''s Review Attempt interface''s saved columns',
         'cust',
         'description'
index 72983e9..5c575d0 100644 (file)
@@ -1,5 +1,5 @@
 [% WRAPPER base.tt2 %]
-[% ctx.page_title = "Link Checker - Create Session" %]
+[% ctx.page_title = l("Link Checker - Create Session") %]
 <script type="text/javascript">
     dojo.require("dijit.form.Button");
     dojo.require("dijit.form.CheckBox");
index 79cfd70..fa4f3b5 100644 (file)
@@ -1,5 +1,5 @@
 [% WRAPPER base.tt2 %]
-[% ctx.page_title = "Link Checker - Review Verification Attempt" %]
+[% ctx.page_title = l("Link Checker - Review Verification Attempt") %]
 <script type="text/javascript">
     dojo.require("dijit.form.Button");
     dojo.require("openils.widget.FlattenerGrid");
@@ -29,6 +29,9 @@
             <button dojoType="dijit.form.Button" onClick="grid.print();">
                 [% l("Print verification results") %]
             </button>
+            <button dojoType="dijit.form.Button"
+                onClick="grid.downloadCSV('[% l("link-checker-results") %]',
+                    progress_dialog);">[% l("Download CSV") %]</button>
         </div>
     </div>
     <div class="oils-acq-basic-roomy">
index 8ad683a..e885597 100644 (file)
@@ -1,5 +1,5 @@
-[% WRAPPER base.tt2 no_content_pane=1 %]
-[% ctx.page_title = "Link Checker - Select URLs" %]
+[% WRAPPER base.tt2 %]
+[% ctx.page_title = l("Link Checker - Select URLs") %]
 <script type="text/javascript">
     dojo.require("dijit.form.Button");
     dojo.require("openils.widget.FlattenerGrid");
     .url-verify-attempt-info { font-style: italic; }
     #session-name-here { font-weight: normal; font-size: 90%; }
 </style>
-<div class="oils-header-panel" dojoType="dijit.layout.ContentPane" layoutAlign="top">
-    <div>[% ctx.page_title %] - <span id="session-name-here"></span></div>
-    <div class="url-verify-button">
-        <button dojoType="dijit.form.Button"
-            onClick="grid.print();">[%
-            l("Print URLs")
-        %]</button>
-        <button dojoType="dijit.form.Button"
-            onClick="module.verify_selected();">[%
-            l("Verify Selected URLs")
-        %]</button>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <div dojoType="dijit.layout.ContentPane"
+        layoutAlign="top" class="oils-header-panel">
+        <div>[% ctx.page_title %] - <span id="session-name-here"></span></div>
+        <div class="url-verify-button">
+            <button dojoType="dijit.form.Button"
+                onClick="grid.print();">[%
+                l("Print URLs")
+            %]</button>
+            <button dojoType="dijit.form.Button"
+                onClick="grid.downloadCSV('[% l("link-checker-urls") %]',
+                    progress_dialog);">[% l("Download CSV") %]</button>
+            <button dojoType="dijit.form.Button"
+                onClick="module.verify_selected();">[%
+                l("Verify Selected URLs")
+            %]</button>
+        </div>
     </div>
-</div>
-<div dojoType="dijit.layout.ContentPane" layoutAlign="bottom" style="height: 85%;">
+    <div class="oils-acq-basic-roomy"><!-- XXX keep for layout reasons --></div>
     <table jsid="grid"
         dojoType="openils.widget.FlattenerGrid"
         columnPersistKey="'url_verify.select_urls'"
@@ -53,7 +58,7 @@
                 <th field="author" fpath="item.target_biblio_record_entry.simple_record.author"></th>
                 <th field="isbn" fpath="item.target_biblio_record_entry.simple_record.isbn" _visible="false"></th>
                 <th field="issn" fpath="item.target_biblio_record_entry.simple_record.issn" _visible="false"></th>
-                <th field="bib_id" fpath="item.target_biblio_record_entry.id" _visible="false"></th>
+                <th style="text-align: center;" field="bib_id" fpath="item.target_biblio_record_entry.id" _visible="false"></th>
                 <!-- You do NOT want to add the "verifications" column to this
                 table with ffilter="true".  That introduces a left join to a
                 table linked to the core table in has-many relationship.  When
index 6458705..a9529c6 100644 (file)
@@ -1,5 +1,5 @@
 [% WRAPPER base.tt2 no_content_pane=1 %]
-[% ctx.page_title = "Link Checker" %]
+[% ctx.page_title = l("Link Checker") %]
 <script type="text/javascript">
     dojo.require("dijit.form.Button");
     dojo.require("openils.widget.FlattenerGrid");
index d36100c..d46daca 100644 (file)
@@ -173,9 +173,8 @@ if (!dojo._hasResource["openils.FlattenerStore"]) {
             req.queryOptions = req.queryOptions || {};
             req.abort = function() { console.warn("[unimplemented] abort()"); };
 
-            /* If we were asked to fetch without any sort order specified (as
-             * will happen when coming from fetchToPrint(), try to use the
-             * last cached sort order, if any. */
+            /* If we were asked to fetch without any sort order specified,
+             * try to use the last cached sort order, if any. */
             req.sort = req.sort || this._last_fetch_sort;
             this._last_fetch_sort = req.sort;
 
@@ -200,51 +199,6 @@ if (!dojo._hasResource["openils.FlattenerStore"]) {
             });
         },
 
-        /* *** 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(
@@ -359,6 +313,16 @@ if (!dojo._hasResource["openils.FlattenerStore"]) {
             var callback_scope = req.scope || dojo.global;
             var post_params;
 
+            /* Special options to support special operations (print and csv): */
+            req.flattenerOptions = dojo.mixin(
+                {}, /* target object */
+                {   /* default values */
+                    "handleAs": "json",
+                    "contentType": "application/json"
+                },
+                req.flattenerOptions /* optional input */
+            );
+
             try {
                 post_params = this._fetch_prepare(req);
             } catch (E) {
@@ -399,20 +363,18 @@ if (!dojo._hasResource["openils.FlattenerStore"]) {
                     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) {
-                        /* Cache items internally. */
-                        self._current_items[item[self.fmIdentifier]] = item;
-
-                        if (typeof req.onItem == "function")
-                            req.onItem.call(callback_scope, item, req);
-                    }
-                );
+                if (req.flattenerOptions.handleAs == "json") {
+                    dojo.forEach(
+                        obj,
+                        function(item) {
+                            /* Cache items internally. */
+                            self._current_items[item[self.fmIdentifier]] = item;
+
+                            if (typeof req.onItem == "function")
+                                req.onItem.call(callback_scope, item, req);
+                        }
+                    );
+                }
 
                 if (typeof req.onComplete == "function")
                     req.onComplete.call(callback_scope, obj, req);
@@ -428,8 +390,8 @@ if (!dojo._hasResource["openils.FlattenerStore"]) {
 
             this._fetch_execute(
                 post_params,
-                "json",
-                "application/json",
+                req.flattenerOptions.handleAs,
+                req.flattenerOptions.contentType,
                 function(obj) { process_fetch(obj, fetch_time); },
                 process_error
             );
index 33162be..c22b8c9 100644 (file)
@@ -96,10 +96,10 @@ if (!dojo._hasResource["openils.URLVerify.ReviewAttempt"]) {
     module.format_bib_id = function(id) {
         if (!id) return "";
 
-        return "<a title='" + localeStrings.MARC_EDITOR_LINK +
+        return id + " [<a title='" + localeStrings.MARC_EDITOR_LINK +
             "' href='javascript:void(0);' " +
             "onclick='openils.URLVerify.ReviewAttempt.open_marc_editor(" +
-            id + "); return false;'>" + id + "</a>";
+            id + "); return false;'>" + localeStrings.EDIT + "</a>]";
     };
 
 }());
index a2283e5..0e35206 100644 (file)
@@ -48,11 +48,11 @@ if (!dojo._hasResource["openils.URLVerify.Sessions"]) {
         if (!str)
             return "";
 
-        return "<a href='select_urls?session_id=" + str + "' title='" +
-            localeStrings.REREVIEW + "'>" + str +
-            "</a> <a href='create_session?clone=" + str + "' title='" +
+        return str + " [<a href='select_urls?session_id=" + str + "' title='" +
+            localeStrings.REREVIEW + "'>" + localeStrings.REREVIEW +
+            "</a>] [<a href='create_session?clone=" + str + "' title='" +
             localeStrings.CLONE_SESSION + "'>" +
-            localeStrings.CLONE_SESSION + "</a>";
+            localeStrings.CLONE_SESSION + "</a>]";
     };
 
     module.format_attempts = function(list) {
@@ -62,11 +62,11 @@ if (!dojo._hasResource["openils.URLVerify.Sessions"]) {
             list, function(id) {
                 if (isNaN(id))
                     return "";
-                return "<a title='" + localeStrings.REVIEW_ATTEMPT +
+                return id + " [<a title='" + localeStrings.REVIEW_ATTEMPT +
                     "' href='review_attempt?attempt_id=" + id + "'>" +
-                    id + "</a>";
+                    localeStrings.REREVIEW + "</a>]";
             }
-        ).join(", ");
+        ).join(" / ");
     };
 
 }());
index c9302db..e36cd24 100644 (file)
@@ -12,7 +12,7 @@
     "NOTHING_SELECTED": "No rows are selected, so no action will be taken.",
     "REVIEW_ATTEMPT": "Review this verification attempt",
     "CLONE_SESSION": "Clone",
-    "REREVIEW": "Review / Verify",
+    "REREVIEW": "Open",
     "CLONING": "Cloning existing session ...",
     "CLONE_SESSION_NAME": "Copy of ${0}",
     "XPATH": "XPath",
@@ -20,5 +20,6 @@
     "SELECT_MORE": "Click here to review all session URLs and/or select other URLs to verify",
     "MARC_EDITOR_LINK": "Click to open MARC Editor for this record",
     "MARC_EDITOR_TITLE": "Record ID #${0}",
-    "MARC_EDITOR_SAVE_RECORD": "Save Record"
+    "MARC_EDITOR_SAVE_RECORD": "Save Record",
+    "EDIT": "Edit"
 }
index 9385091..d5f7cc1 100644 (file)
@@ -96,6 +96,10 @@ if(!dojo._hasResource["openils.XUL"]) {
             "iface": Components.interfaces.nsIFileOutputStream,
             "cls": "@mozilla.org/network/file-output-stream;1"
         },
+        "COS": {
+            "iface": Components.interfaces.nsIConverterOutputStream,
+            "cls": "@mozilla.org/intl/converter-output-stream;1"
+        },
         "create": function(key) {
             return Components.classes[this[key].cls].
                 createInstance(this[key].iface);
@@ -158,9 +162,15 @@ if(!dojo._hasResource["openils.XUL"]) {
                     result == api.FP.iface.returnReplace)) {
             if (!picker.file.exists())
                 picker.file.create(0, 0644); /* XXX hardcoded = bad */
+
             var fos = api.create("FOS");
             fos.init(picker.file, 42 /* WRONLY | CREAT | TRUNCATE */, 0644, 0);
-            return fos.write(content, content.length);
+
+            var cos = api.create("COS");
+            cos.init(fos, "UTF-8", 0, 0);   /* It's the 21st century. You don't
+                                                use ISO-8859-*. */
+            cos.writeString(content);
+            return cos.close();
         } else {
             return 0;
         }
index d66ee66..4583a97 100644 (file)
@@ -1,6 +1,8 @@
 if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
     dojo.provide("openils.widget.FlattenerGrid");
 
+    dojo.requireLocalization("openils.widget", "FlattenerGrid");
+
     dojo.require("DojoSRF");
     dojo.require("dojox.grid.DataGrid");
     dojo.require("openils.FlattenerStore");
@@ -8,10 +10,18 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
     dojo.require("openils.widget.GridColumnPicker");
     dojo.require("openils.widget.EditDialog");  /* includes EditPane */
     dojo.require("openils.widget._GridHelperColumns");
+    dojo.require("openils.XUL");
 
     dojo.declare(
         "openils.widget.FlattenerGrid",
         [dojox.grid.DataGrid, openils.widget._GridHelperColumns], {
+            /* Later, might think about whether this should really be an
+             * "object" property like this or a "class" one (in dojo speak,
+             * since those terms don't really apply in pure JS)... */
+            "localeStrings": dojo.i18n.getLocalization(
+                "openils.widget", "FlattenerGrid"
+            ),
+
             /* These potential constructor arguments are useful to
              * FlattenerGrid in their own right */
             "columnReordering": true,
@@ -444,6 +454,20 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 }
 
                 this.inherited(arguments);
+
+                this.focus.focusHeader = function() {
+                    /* This prevents an unwanted automatic scroll of the
+                     * user's browser to the header row of the grid whenever
+                     * you touch the horizontal scrollbar.  The prevented
+                     * behavior was absolutely hateful, since if your grid was
+                     * larger than your window, touching the horizontal scroll-
+                     * bar meant scrolling up so that the same scrollbar was
+                     * now off your screen, and you could not manipulate it.
+                     *
+                     * There may be a more targeted way to fix the problem,
+                     * but this will do.  */
+                    console.log("focusHeader() suppressed");
+                };
             },
 
             "canSort": function(idx, skip_structure /* API abuse */) {
@@ -540,14 +564,14 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                             this.filterSemaphoreCallback();
                     }
                     if (!this.filterAlwaysInDiv) {
-                        dojo.create(
-                            "a", {
-                                "innerHTML": "Filter",  /* XXX i18n */
-                                "href": "javascript:void(0);",
-                                "onclick": dojo.hitch(this, function() {
-                                    this.filterUi.show();
-                                })
-                            }, this.linkHolder.domNode
+                        new dijit.form.Button(
+                            {
+                                "label": "Filter", /* XXX i18n */
+                                "onClick": dojo.hitch(
+                                    this, function() { this.filterUi.show(); }
+                                )
+                            },
+                            dojo.create("span", null, this.linkHolder.domNode)
                         );
                     }
                 }
@@ -901,6 +925,51 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                 ).length == 0;
             },
 
+            "downloadCSV": function(filename_prefix, progress_dialog) {
+                filename_prefix = filename_prefix || "grid";
+                var localeStrings = this.localeStrings;
+
+                var mapkey_for_filename =
+                    this.store.mapKey ? this.store.mapKey.substr(-8, 8) : "X";
+
+                var dispositionArgs = {
+                    "defaultString": filename_prefix + "-" +
+                        mapkey_for_filename + ".csv",
+                    "defaultExtension": ".csv",
+                    "filterName": localeStrings.CSV_FILTER_NAME,
+                    "filterExtension": "*.csv",
+                    "filterAll": true
+                };
+
+                var coal = this._columnOrderingAndLabels();
+                var req = {
+                    "query": this.query,
+                    "queryOptions": {
+                        "columns": coal.columns,
+                        "labels": coal.labels,
+                        "all": true
+                    },
+                    "flattenerOptions": {
+                        "contentType": "text/csv",
+                        "handleAs": "text"
+                    },
+                    "onComplete": function(text) {
+                        if (progress_dialog)
+                            progress_dialog.attr("title", "");
+                            progress_dialog.hide();
+                        openils.XUL.contentToFileSaveDialog(
+                            text, localeStrings.CSV_SAVE_DIALOG, dispositionArgs
+                        );
+                    }
+                };
+
+                if (progress_dialog) {
+                    progress_dialog.attr("title", localeStrings.FETCHING_CSV);
+                    progress_dialog.show(true);
+                }
+                this.store.fetch(req);
+            },
+
             /* 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) unless those are passed in to the print() method.
@@ -913,6 +982,9 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                         "columns": coal.columns,
                         "labels": coal.labels
                     },
+                    "flattenerOptions": {
+                        "handleAs": "text", "contentType": "text/html"
+                    },
                     "onComplete": function(text) {
                         openils.Util.printHtmlString(text);
                     }
@@ -925,7 +997,7 @@ if (!dojo._hasResource["openils.widget.FlattenerGrid"]) {
                     req.queryOptions.all = true;
                 }
 
-                this.store.fetchToPrint(req);
+                this.store.fetch(req);
             },
 
             "printSelected": function() {
diff --git a/Open-ILS/web/js/dojo/openils/widget/nls/FlattenerGrid.js b/Open-ILS/web/js/dojo/openils/widget/nls/FlattenerGrid.js
new file mode 100644 (file)
index 0000000..054350a
--- /dev/null
@@ -0,0 +1,6 @@
+{
+    "FILTER": "Filter",
+    "CSV_SAVE_DIALOG": "Save CSV Output As",
+    "CSV_FILTER_NAME": "CSV Files",
+    "FETCHING_CSV": "Retrieving CSV data from server ..."
+}