Bug 25031: Improve handling of multiple covers on the biblio detail page in the staff...
authorOwen Leonard <oleonard@myacpl.org>
Tue, 31 Mar 2020 21:37:18 +0000 (21:37 +0000)
committerJonathan Druart <jonathan.druart@bugs.koha-community.org>
Fri, 24 Jul 2020 12:09:30 +0000 (14:09 +0200)
This patch modifies the template, JS, and CSS for the bibliographic
detail page in order to gracefully handle multiple cover images.

The changed version loops through any cover images which might be
embedded and checks that they are successfully loaded. Only
successfully-loaded images are shown. Only the first image is shown, and
the others can be "paged through" using generated navigation controls.

To test, apply the page and rebuild the staff client CSS
(https://wiki.koha-community.org/wiki/Working_with_SCSS_in_the_OPAC_and_staff_client).

Enable multiple cover image services. The patch was developed with these
services available:

 - Amazon
 - Local cover images (including multiple local cover images)
 - Coce (serving up Amazon, Google, and OpenLibrary images)
 - Images from the CustomCoverImages preference

View a variety of titles and confirm that the cover images are
displaying correctly, whether there be 0, 1, 2, or more covers
available.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>

koha-tmpl/intranet-tmpl/js/coce.js
koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
koha-tmpl/intranet-tmpl/prog/en/includes/js_includes.inc
koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt
koha-tmpl/intranet-tmpl/prog/js/localcovers.js

index 930d7c1..f6815ac 100644 (file)
@@ -6,45 +6,45 @@ if (KOHA === undefined || !KOHA) { var KOHA = {}; }
  */
 KOHA.coce = {
 
-  /**
-   * Search all:
-   *    <div title="biblionumber" id="isbn" class="coce-thumbnail"></div>
-   * or
-   *    <div title="biblionumber" id="isbn" class="coce-thumbnail-preview"></div>
-   * and run a search with all collected isbns to coce cover service.
-   * The result is asynchronously returned, and used to append <img>.
-   */
-  getURL: function(host,provider,newWindow) {
-    var ids = [];
-    $("[id^=coce-thumbnail]").each(function(i) {
-        var id = $(this).attr("class"); // id=isbn
-        if ( id !== '' ) { ids.push(id); }
-    });
-    if (ids.length == 0) return;
-    ids = ids.join(',');
-    var coceURL = host + '/cover?id=' + ids + '&provider=' + provider;
-    $.ajax({
-      url: coceURL,
-      dataType: 'jsonp',
-      success: function(urlPerID){
-        for (var id in urlPerID) {
-          var url = urlPerID[id];
-          $("[id^=coce-thumbnail]."+id).each(function() {
-            var img = document.createElement("img");
-            img.src = url;
-            img.classList.add("thumbnail");
-            img.alt = "Cover image";
-            img.onload = function(){
-                // image dimensions can't be known until image has loaded
-                if( img.height == 1 && img.width == 1 ){
-                    $(this).remove();
+    /**
+     * Search all:
+     *    <div title="biblionumber" id="isbn" class="coce-thumbnail"></div>
+     * or
+     *    <div title="biblionumber" id="isbn" class="coce-thumbnail-preview"></div>
+     * and run a search with all collected isbns to coce cover service.
+     * The result is asynchronously returned, and used to append <img>.
+     */
+    getURL: function(host, provider, newWindow) {
+        var ids = [];
+        $("[id^=coce-thumbnail]").each(function(i) {
+            var id = $(this).attr("class"); // id=isbn
+            if (id !== '') { ids.push(id); }
+        });
+        if (ids.length == 0) return;
+        ids = ids.join(',');
+        var coceURL = host + '/cover?id=' + ids + '&provider=' + provider;
+        $.ajax({
+            url: coceURL,
+            dataType: 'jsonp',
+            success: function(urlPerID) {
+                for (var id in urlPerID) {
+                    var url = urlPerID[id];
+                    $("[id^=coce-thumbnail]." + id).each(function() {
+                        var img = document.createElement("img");
+                        img.src = url;
+                        img.alt = "Cover image";
+                        img.onload = function() {
+                            // image dimensions can't be known until image has loaded
+                            if (img.height == 1 && img.width == 1) {
+                                $(this).remove();
+                            }
+                        };
+                        $(this).html(img);
+                    });
                 }
-            }
-            $(this).html(img);
-         });
-        }
-      }
-    });
-  }
+            },
+
+        });
+    }
 
 };
index 2f65e0f..5390f25 100644 (file)
@@ -2095,6 +2095,43 @@ li {
     }
 }
 
+#cover-slides {
+    background: #FFF url("[% interface | html %]/[% theme | html %]/img/spinner-small.gif") center center no-repeat;
+    border: 1px solid #b9d8d9;
+    border-radius: 3px;
+    margin: 5px;
+    padding: 10px 5px 5px 5px;
+    min-height: 175px;
+
+    .hint {
+        font-size: 90%;
+        padding: .5em 0;
+    }
+
+    a {
+        &.nav-active {
+            &:link,
+            &:visited {
+                color: #85ca11;
+            }
+        }
+    }
+}
+
+.cover-image {
+    display: none;
+
+    img {
+        height: auto;
+        max-width: 100%;
+    }
+}
+
+.cover-nav {
+    display: inline-block;
+    padding: 3px 4px;
+}
+
 .searchhighlightblob {
     font-size: 75%;
     font-style: italic;
index 6bc7558..f56faeb 100644 (file)
 
 [% IF LocalCoverImages %]
     [% Asset.js("js/localcovers.js") | $raw %]
-    <script>
-        // LocalCoverImages
-        var NO_LOCAL_JACKET = _("No cover image available");
-    </script>
 [% END %]
 
 [% IF Koha.Preference('AudioAlerts') || AudioAlertsPage %]
index 3315883..724ff28 100644 (file)
         [% END %]
 
         [% IF ( AmazonCoverImages  || LocalCoverImages || AdlibrisEnabled || IntranetCoce || (Koha.Preference('CustomCoverImages') && Koha.Preference('CustomCoverImagesURL')) ) %]
-        </div><div class="col-xs-3" id="bookcoverimg">
-        [% IF ( LocalCoverImages ) %]
-            <div title="[% biblionumber |url %]" class="[% biblionumber | html %]" id="local-thumbnail-preview"></div>
-        [% END %]
-        [% IF ( AdlibrisEnabled && normalized_isbn ) %]
-            <a href="[% AdlibrisURL | url %]?isbn=[% normalized_isbn | uri %]"><img src="[% AdlibrisURL | url %]?isbn=[% normalized_isbn | uri %]" class="adlibris-cover-big" alt="Adlibris cover image" /></a>
-        [% END %]
-        [% IF ( AmazonCoverImages && normalized_isbn) %]
-            <div id="amazon-bookcoverimg">
-            <a href="http://www.amazon[% AmazonTld | uri %]/gp/reader/[% normalized_isbn | uri %][% AmazonAssocTag | uri %]#reader-link">
-                <img src="https://images-na.ssl-images-amazon.com/images/P/[% normalized_isbn | html %].01.MZZZZZZZ.jpg" alt="" />
-        </a></div>
-        [% END %]
-        [% IF ( IntranetCoce && CoceProviders ) %]
-          [% coce_id = normalized_ean || normalized_isbn %]
-            <a class="p1" href="/cgi-bin/koha/catalogue/[% DetailPage | html %]?biblionumber=[% biblionumber | url %]">
-          [% IF ( coce_id ) %]
-            <span style="block" title="[% biblionumber | url %]" class="[% coce_id | html %]" id="coce-thumbnail-preview"></span>
-          [% ELSE %]
-            <span class="no-image">No cover image available</span>
-          [% END %]
-            </a>
-        [% END %]
+            </div>
+            <div class="col-xs-3" id="bookcoverimg">
+                <div id="cover-slides">
+                    [% IF ( LocalCoverImages ) %]
+                        [% IF ( localimages.0 ) %]
+                            [% FOREACH image IN localimages %]
+                                [% IF image %]
+                                    <div class="cover-image local-coverimg">
+                                        <a href="/cgi-bin/koha/catalogue/imageviewer.pl?biblionumber=[% biblionumber | uri %]&amp;imagenumber=[% image | uri %]">
+                                            <img src="/cgi-bin/koha/catalogue/image.pl?thumbnail=1&amp;imagenumber=[% image | uri %]" alt="Local cover image" />
+                                        </a>
+                                        <div class="hint">Local cover image</div>
+                                    </div>
+                                [% END %]
+                            [% END %]
+                        [% END %]
+                    [% END %]
 
-        [% IF Koha.Preference('CustomCoverImages') && Koha.Preference('CustomCoverImagesURL') %]
-            <a class="custom_cover_image" href="[% biblio.custom_cover_image_url | url %]"><img alt="Cover image" src="[% biblio.custom_cover_image_url | url %]" /></a>
-        [% END %]
-        [% END %]
+                    [% IF ( AdlibrisEnabled && normalized_isbn ) %]
+                        <div class="cover-image" id="adlibris-coverimg">
+                            <a href="[% AdlibrisURL | url %]?isbn=[% normalized_isbn | uri %]">
+                                <img src="[% AdlibrisURL | url %]?isbn=[% normalized_isbn | uri %]" class="adlibris-cover-big" alt="Adlibris cover image" />
+                            </a>
+                            <div class="hint">Image from Adlibris</div>
+                        </div>
+                    [% END %]
+                    [% IF ( AmazonCoverImages && normalized_isbn) %]
+                        <div class="cover-image" id="amazon-bookcoverimg">
+                            <a href="http://www.amazon[% AmazonTld | uri %]/gp/reader/[% normalized_isbn | uri %][% AmazonAssocTag | uri %]#reader-link">
+                                <img src="https://images-na.ssl-images-amazon.com/images/P/[% normalized_isbn | html %].01.MZZZZZZZ.jpg" alt="Amazon cover image" />
+                            </a>
+                            <div class="hint">Image from Amazon.com</div>
+                        </div>
+                    [% END %]
+                    [% IF ( IntranetCoce && CoceProviders && normalized_isbn ) %]
+                        [% coce_id = normalized_ean || normalized_isbn %]
+                        <div class="cover-image" id="coce-coverimg">
+                            [% IF ( coce_id ) %]
+                                <span title="[% biblionumber | url %]" class="[% coce_id | html %]" id="coce-thumbnail-preview"></span>
+                            [% ELSE %]
+                                <span class="no-image">No cover image available</span>
+                            [% END %]
+                            <div class="hint">Image from Coce</div>
+                        </div>
+                    [% END %]
 
-</div>
+                    [% IF Koha.Preference('CustomCoverImages') && Koha.Preference('CustomCoverImagesURL') %]
+                        <div class="cover-image" id="custom-coverimg">
+                            <a class="custom_cover_image" href="[% biblio.custom_cover_image_url | url %]">
+                                <img id="custom-img" alt="Custom cover image" src="[% biblio.custom_cover_image_url | url %]" />
+                            </a>
+                            <div class="hint">Custom cover image</div>
+                        </div>
+                    [% END %]
+                </div>
+            </div> <!-- /#bookcoverimg -->
+        [% END %]
 </div>
 <div id="bibliodetails" class="toptabs">
 
@@ -908,30 +935,73 @@ Note that permanent location is a code, and location may be an authval.
         var theme = "[% theme | html %]";
         // http://www.oreillynet.com/pub/a/javascript/2003/10/21/amazonhacks.html
         function verify_images() {
-            $("#bookcoverimg").each(function(i){
-                $(this).find('img').each(function(i){
-                   if ((this.src.indexOf('images.amazon.com') >= 0) || (this.src.indexOf('g-images.amazon.com') >=0) || (this.src.indexOf('images-na.ssl-images-amazon.com'))) {
-                        w = this.width;
-                        h = this.height;
-                        if ((w == 1) || (h == 1)) {
-                            $("#amazon-bookcoverimg").remove();
-                        } else if ((this.complete != null) && (!this.complete)) {
-                            $("#amazon-bookcoverimg").remove();
+            // Loop over each container in the template which contains covers
+            var coverSlides = $(".cover-image");
+            coverSlides.each( function( index ){
+                var div = $(this);
+                // Find the image in the container
+                var img = div.find("img")[0];
+                if( $(img).length > 0 ){
+                    if( (img.complete != null) && (!img.complete) || img.naturalHeight == 0 ){
+                        // No image loaded in the container. Remove the slide
+                        div.remove();
+                    } else {
+                        // All slides start hidden. If this is the first one, show it.
+                        if( index == 0 ){
+                            div.show();
+                        }
+                        // Check if Amazon image is present
+                        if ( div.attr("id") == "amazon-bookcoverimg"  ) {
+                            w = img.width;
+                            h = img.height;
+                            if ((w == 1) || (h == 1)) {
+                                // Amazon returned single-pixel placeholder
+                                // Remove the container
+                                div.remove();
+                            }
+                        }
+                        if( div.attr("id") == "custom-img" ){
+                            if ( (img.complete != null) && (!img.complete) || img.naturalHeight == 0 ) {
+                                // No image was loaded via the CustomCoverImages system preference
+                                // Remove the container
+                                div.remove();
+                            }
+                        }
+                        if( div.attr("id") == "coce-coverimg" ){
+                            // Identify which service's image is being loaded by Coce
+                            if( $(img).attr("src").indexOf('amazon.com') >= 0 ){
+                                div.find(".hint").html(_("Coce image from Amazon.com"));
+                            } else if( $(img).attr("src").indexOf('google.com') >= 0 ){
+                                div.find(".hint").html(_("Coce image from Google Books"));
+                            } else if( $(img).attr("src").indexOf('openlibrary.org') >= 0 ){
+                                div.find(".hint").html(_("Coce image from Open Library"));
+                            }
+                        }
+                        // If more that one slide is present, add a navigation link
+                        // for activating the slide
+                        if( coverSlides.length > 1 ){
+                            var covernav = $("<a href=\"#\" data-num=\"" + index + "\" class=\"cover-nav\"></a>");
+                            if( index == 0 ){
+                                // Set the first navigation link as active
+                                $(covernav).addClass("nav-active");
+                            }
+                            $(covernav).html("<i class=\"fa fa-circle\"></i>");
+                            $("#cover-slides").append( covernav );
                         }
                     }
-                });
-                if( $(this).find('img').length < 1 ){
-                    $(this).remove();
-                    $("#catalogue_detail_biblio").attr("class","col-xs-12");
                 }
             });
+            if( $(".cover-image:visible").length < 1 ){
+                $("#cover-slides").remove();
+            }
+
             $("#editions img").each(function(i){
-                if ((this.src.indexOf('images.amazon.com') >= 0) || (this.src.indexOf('g-images.amazon.com') >=0) || (this.src.indexOf('images-na.ssl-images-amazon.com'))) {
+                if ( this.src.indexOf('amazon.com') >= 0 ) {
                     w = this.width;
                     h = this.height;
                     if ((w == 1) || (h == 1)) {
                         this.src = 'https://images-na.ssl-images-amazon.com/images/G/01/x-site/icons/no-img-sm.gif';
-                    } else if ((this.complete != null) && (!this.complete)) {
+                    } else if ( (this.complete != null) && (!this.complete) || this.naturalHeight == 0 ) {
                         this.src = 'https://images-na.ssl-images-amazon.com/images/G/01/x-site/icons/no-img-sm.gif';
                     }
                 }
@@ -1064,10 +1134,6 @@ Note that permanent location is a code, and location may be an authval.
 
                 return false;
             });
-            [%# inject no images message %]
-            [% IF LocalCoverImages %]
-                KOHA.LocalCover.GetCoverFromBibnumber(true);
-            [% END %]
             [% IF ( IntranetCoce && CoceProviders ) %]
                 KOHA.coce.getURL('[% CoceHost | html %]', '[% CoceProviders | html %]');
             [% END %]
@@ -1100,11 +1166,19 @@ Note that permanent location is a code, and location may be an authval.
                 link = $(this).attr("href");
                 openWindow(link,"Print spine label",400,400);
              });
+             $("#cover-slides").on("click",".cover-nav", function(){
+                // Adding click handler for cover image navigation links
+                var num = $(this).data("num");
+                $(".cover-nav").removeClass("nav-active");
+                $(this).addClass("nav-active");
+                $(".cover-image").hide();
+                $(".cover-image").eq( num ).show();
+             });
         });
 
-        [% IF ( AmazonCoverImages || LocalCoverImages ) %]$(window).load(function() {
+        $(window).load(function() {
             verify_images();
-        });[% END %]
+        });
     </script>
     [% IF ( Koha.Preference('NovelistSelectStaffEnabled') && Koha.Preference('NovelistSelectStaffProfile') && ( normalized_isbn || normalized_upc ) ) %]
         <script src="https://imageserver.ebscohost.com/novelistselect/ns2init.js"></script>
index fe6aeac..a25d7c7 100644 (file)
@@ -1,3 +1,5 @@
+/* global _ */
+
 if (typeof KOHA == "undefined" || !KOHA) {
     var KOHA = {};
 }
@@ -18,42 +20,30 @@ KOHA.LocalCover = {
      * olCallBack().
      */
     GetCoverFromBibnumber: function(uselink) {
-        $("div [id^=local-thumbnail]").each(function(i) {
-            var mydiv = this;
-            var message = document.createElement("span");
-            $(message).attr("class","no-image");
-            $(message).html(NO_LOCAL_JACKET);
-            $(mydiv).parent().find('.no-image').remove();
-            $(mydiv).append(message);
-            var img = $("<img />").attr('src',
-                '/cgi-bin/koha/catalogue/image.pl?thumbnail=1&biblionumber=' + $(mydiv).attr("class"))
-                .load(function () {
-                    if (!this.complete || typeof this.naturalWidth == "undefined" || this.naturalWidth <= 1) {
-                        //IE HACK
-                        try {
-                            $(mydiv).remove();
-                        }
-                        catch(err){
-                        }
-                    } else {
-                        if (uselink) {
-                            var a = $("<a />").attr('href', '/cgi-bin/koha/catalogue/imageviewer.pl?biblionumber=' + $(mydiv).attr("class"));
-                            $(a).append(img);
-                            $(mydiv).append(a);
-                        } else {
-                            $(mydiv).append(img);
-                        }
-                        $(mydiv).children('.no-image').remove();
-                    }
-                });
-        });
+        var mydiv = $("#local-thumbnail-preview");
+        var biblionumber = mydiv.data("biblionumber");
+        var img = document.createElement("img");
+        img.src = "/cgi-bin/koha/catalogue/image.pl?thumbnail=1&biblionumber=" + biblionumber;
+        img.onload = function() {
+            // image dimensions can't be known until image has loaded
+            if ( (img.complete != null) && (!img.complete) ) {
+                mydiv.remove();
+            }
+        };
+        if (uselink) {
+            var a = $("<a />").attr('href', '/cgi-bin/koha/catalogue/imageviewer.pl?biblionumber=' + $(mydiv).attr("class"));
+            $(a).append(img);
+            mydiv.append(a);
+        } else {
+            mydiv.append(img);
+        }
     },
     LoadResultsCovers: function(){
         $("div [id^=local-thumbnail]").each(function(i) {
             var mydiv = this;
             var message = document.createElement("span");
             $(message).attr("class","no-image thumbnail");
-            $(message).html(NO_LOCAL_JACKET);
+            $(message).html( _("No cover image available") );
             $(mydiv).append(message);
             var img = $("<img />");
             img.attr('src','/cgi-bin/koha/catalogue/image.pl?thumbnail=1&biblionumber=' + $(mydiv).attr("class"))