From e12e0ef016247003a7a97916aa760277b56782cf Mon Sep 17 00:00:00 2001 From: Owen Leonard Date: Wed, 16 Dec 2020 18:38:11 +0000 Subject: [PATCH] Bug 27200: (follow-up) Browse search interface update This patch updates the browse search interface to confirm to new interface patterns in the Bootstrap 4 framework, especially in the form markup. The results list has been changed to a Bootstrap "Collapse" component configured as an accordion (https://getbootstrap.com/docs/4.5/components/collapse/#accordion-example). Instead of loading the bibliographic record results below the list of terms returned, the bibliographic results are now displayed in the "panel" expanded below the selected term. Subtitle has been added to the information displayed about the bibliographic record. To test you must be using ElasticSearch and the OpacBrowseSearch preference must be enabled. Apply the patch and rebuild the OPAC CSS (https://wiki.koha-community.org/wiki/Working_with_SCSS_in_the_OPAC_and_staff_client). - Open the "Browse search" page in the OPAC. - Test various searches: Author, Subject, and Title. - When results are found, the should be displayed as a Bootstrap-styled accordion widget. Clicking any individual result should expand the panel containing the corresponding records. - Clicking the record link should open the bibliographic detail page in a new window. - When no results are found, a Bootstrap-style "alert" box should appear. Signed-off-by: Victor Grousset/tuxayo Signed-off-by: Jonathan Druart --- .../opac-tmpl/bootstrap/css/src/_common.scss | 2 +- .../opac-tmpl/bootstrap/css/src/opac.scss | 139 ++++++-------- .../bootstrap/en/modules/opac-browse.tt | 88 ++++++--- koha-tmpl/opac-tmpl/bootstrap/js/browse.js | 179 ++++++++---------- opac/opac-browse.pl | 1 + 5 files changed, 195 insertions(+), 214 deletions(-) diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/_common.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/_common.scss index 3df3488625..f0a0fd2be3 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/css/src/_common.scss +++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/_common.scss @@ -44,7 +44,7 @@ $sci-heading-color: #727272; @import "../../../../../node_modules/bootstrap/scss/breadcrumb"; @import "../../../../../node_modules/bootstrap/scss/button-group"; @import "../../../../../node_modules/bootstrap/scss/buttons"; -// @import "../../../../../node_modules/bootstrap/scss/card"; +@import "../../../../../node_modules/bootstrap/scss/card"; // @import "../../../../../node_modules/bootstrap/scss/carousel"; @import "../../../../../node_modules/bootstrap/scss/close"; // @import "../../../../../node_modules/bootstrap/scss/code"; diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss index 24f095c819..b946cc193b 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss +++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss @@ -2513,94 +2513,35 @@ $star-selected: #EDB867; } /*opac browse search*/ -#browse-search { - - form { - label { - display: inline-block; - margin-right:5px; - } - - [type=submit] { - margin-top: 10px; - } - } - - #browse-resultswrapper { - margin-top: 4em; - - @media (min-width: 768px) and (max-width: 984px) { - margin-top: 2em; - } - - @media (max-width: 767px) { - margin-top: 1em; - } - } - #browse-searchresults, #browse-selectionsearch { - border: 1px solid #E3E3E3; - border-radius: 4px; - padding: 0; - overflow-y: auto; - max-height: 31em; - margin-bottom: 2em; - } - #browse-searchresults { - max-height: 31em; - list-style: none; - padding: 10px; - a { - display: block; - margin-bottom: 5px; - - &.selected { - background-color:#EEE; - } - } +#browse-resultswrapper { + margin-top: 15px; +} +#browse-searchfuzziness { + padding: 15px 0; +} - li:last-child a { - margin-bottom: 0; - } +#browse-searchresults, #browse-selectionsearch { + border: 1px solid #E3E3E3; + border-radius: 4px; + padding: 0; + margin-bottom: 2em; +} - @media (max-width: 767px) { - max-height: 13em; - } - } - #browse-selection { - margin-top: -40px; - padding-top: 0; +#browse-selectionsearch p.subjects { + font-size: 0.9em; + margin-bottom: 0; +} - @media (max-width: 767px) { - margin-top: 0; - } - } - #browse-selectionsearch ol { - list-style: none; - margin: 0; +#browse-selectionsearch h4 { + margin: 0; +} - li { - padding: 1em; +#browse-suggestionserror { + margin-top: 1rem; +} - &:nth-child(odd) { - background-color: #F4F4F4; - } - } - } - #browse-selectionsearch p.subjects { - font-size: 0.9em; - margin-bottom: 0; - } - #browse-selectionsearch h4 { - margin: 0; - } - .error, .no-results { - background-color: #EEE; - border: 1px solid #E8E8E8; - text-align: left; - padding: 0.5em; - border-radius: 3px; - } +#browse-search { .loading { text-align: center; @@ -2611,6 +2552,15 @@ $star-selected: #EDB867; } } } + +#card_template { + display: none; +} + +.result-title { + margin-bottom: .4rem; + margin-left: 1rem; +} /*end browse search*/ /* Skip to content link. CSS adapted from https://webaim.org/ */ @@ -2638,4 +2588,29 @@ $star-selected: #EDB867; } } +.accordion { + .card-header { + padding: 0; + + a { + display: block; + padding: .75rem 1.25rem; + + &[aria-expanded="true"] { font-weight: bold; } + &[aria-expanded="false"] { font-weight: normal; } + + &:hover { + background-color: rgba(255, 255, 204, 0.8); + text-decoration: none; + } + } + } + + .collapse { + &.show { + border-top: 1px solid #888; + } + } +} + @import "responsive"; diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt index d8ad1b5921..11ff7f1efc 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt @@ -41,44 +41,70 @@

Browse search

- - - - - -
- - - +
+
+ + +
+
+ + +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
- - +
-
    - - - -
+

Results

-

+
Loading
+ -
- - - - -
    -
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    [% ELSE %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js b/koha-tmpl/opac-tmpl/bootstrap/js/browse.js index e755e748b5..3a3bcc26a3 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js +++ b/koha-tmpl/opac-tmpl/bootstrap/js/browse.js @@ -16,8 +16,7 @@ $(document).ready(function(){ var userInput = $('#browse-searchterm').val().trim(); var userField = $('#browse-searchfield').val(); var userFuzziness = $('input[name=browse-searchfuzziness]:checked', '#browse-searchfuzziness').val(); - var leftPaneResults = $('#browse-searchresults li').not('.loading, .no-results'); - var rightPaneResults = $('#browse-selectionsearch ol li'); + var card_template = $("#card_template"); event.preventDefault(); @@ -25,49 +24,50 @@ $(document).ready(function(){ return; } - // remove any error states and show the results area (except right pane) + /* return the browsing results to empty state in case previous results have been loaded */ + $("#browse-searchresults").empty().append( card_template ); + + // remove any error states and show the results area $('#browse-suggestionserror').addClass('d-none'); - $('#browse-searchresults .no-results').addClass('d-none'); + $('.no-results').addClass('d-none'); $('#browse-resultswrapper').removeClass('d-none'); - $('#browse-selection').addClass('d-none').text(""); - $('#browse-selectionsearch').addClass('d-none'); - - // clear any results from left and right panes - leftPaneResults.remove(); - rightPaneResults.remove(); + /* Reset results browser to default state */ - // show the spinner in the left pane - $('#browse-searchresults .loading').removeClass('d-none'); + // show the spinner + $('.loading').removeClass('d-none'); xhrGetSuggestions = $.get(window.location.pathname, {api: "GetSuggestions", field: userField, prefix: userInput, fuzziness: userFuzziness}) .always(function() { // hide spinner - $('#browse-searchresults .loading').addClass('d-none'); + $('.loading').addClass('d-none'); }) .done(function(data) { var fragment = document.createDocumentFragment(); if (data.length === 0) { - $('#browse-searchresults .no-results').removeClass('d-none'); - + $('.no-results').removeClass('d-none'); return; } - // scroll to top of container again - $("#browse-searchresults").overflowScrollReset(); - // store the type of search that was performed as an attrib $('#browse-searchresults').data('field', userField); $.each(data, function(index, object) { // use a document fragment so we don't need to nest the elems // or append during each iteration (which would be slow) - var elem = document.createElement("li"); - var link = document.createElement("a"); - link.textContent = object.text; - link.setAttribute("href", "#"); - elem.appendChild(link); - fragment.appendChild(elem); + var card = card_template.clone().removeAttr("id"); + // change card-header id + card + .find(".card-header") + .attr("id", "heading" + index) + .find("a").attr("data-target", "#collapse" + index) + .attr("aria-controls", "collapse" + index) + .text(object.text); + card + .find(".collapse") + .attr("id", "collapse" + index) + .attr("aria-labelledby", "heading" + index); + $(fragment).append(card); }); $('#browse-searchresults').append(fragment.cloneNode(true)); @@ -81,92 +81,71 @@ $(document).ready(function(){ }); }); - $('#browse-searchresults').on("click", 'a', function(event) { + $('#browse-searchresults').on("click", 'a.expand-result', function(event) { // if there is an in progress request, abort it so we // don't end up with a race condition if(xhrGetResults && xhrGetResults.readyState != 4){ xhrGetResults.abort(); } - var term = $(this).text(); + var link = $(this); + var target = link.data("target"); + var term = link.text(); + var field = $('#browse-searchresults').data('field'); - var rightPaneResults = $('#browse-selectionsearch ol li'); event.preventDefault(); - // clear any current selected classes and add a new one - $(this).parent().siblings().children().removeClass('selected'); - $(this).addClass('selected'); - - // copy in the clicked text - $('#browse-selection').removeClass('d-none').text(term); - - // show the right hand pane if it is not shown already - $('#browse-selectionsearch').removeClass('d-none'); - - // hide the no results element - $('#browse-selectionsearch .no-results').addClass('d-none'); - - // clear results - rightPaneResults.remove(); - - // turn the spinner on - $('#browse-selectionsearch .loading').removeClass('d-none'); - - // do the query for the term - xhrGetResults = $.get(window.location.pathname, {api: "GetResults", field: field, term: term}) - .always(function() { - // hide spinner - $('#browse-selectionsearch .loading').addClass('d-none'); - }) - .done(function(data) { - var fragment = document.createDocumentFragment(); - - if (data.length === 0) { - $('#browse-selectionsearch .no-results').removeClass('d-none'); - - return; - } - - // scroll to top of container again - $("#browse-selectionsearch").overflowScrollReset(); - - $.each(data, function(index, object) { - // use a document fragment so we don't need to nest the elems - // or append during each iteration (which would be slow) - var elem = document.createElement("li"); - var title = document.createElement("h4"); - var link = document.createElement("a"); - var author = document.createElement("p"); - var destination = window.location.pathname; - - destination = destination.replace("browse", "detail"); - destination = destination + "?biblionumber=" + object.id; - - author.className = "author"; - - link.setAttribute("href", destination); - link.setAttribute("target", "_blank"); - link.textContent = object.title; - title.appendChild(link); - - author.textContent = object.author; - - elem.appendChild(title); - elem.appendChild(author); - fragment.appendChild(elem); + /* Don't load data via AJAX if it has already been loaded */ + if ($(target).find(".result-title").length == 0) { + // do the query for the term + xhrGetResults = $.get(window.location.pathname, {api: "GetResults", field: field, term: term}) + .done(function(data) { + var fragment = document.createDocumentFragment(); + + if (data.length === 0) { + $('#browse-selectionsearch .no-results').removeClass('d-none'); + return; + } + + + $.each(data, function(index, object) { + // use a document fragment so we don't need to nest the elems + // or append during each iteration (which would be slow) + var elem = document.createElement("div"); + elem.className = "result-title"; + + var destination = window.location.pathname; + destination = destination.replace("browse", "detail"); + destination = destination + "?biblionumber=" + object.id; + + var link = document.createElement("a"); + link.setAttribute("href", destination); + link.setAttribute("target", "_blank"); + link.textContent = object.title; + if( object.subtitle ){ + link.textContent += " " + object.subtitle; + } + elem.appendChild(link); + + if( object.author ){ + var author = document.createElement("span"); + author.className = "author"; + author.textContent = " " + object.author; + elem.appendChild(author); + } + fragment.appendChild(elem); + }); + + $( target ).find(".card-body").append(fragment.cloneNode(true)); + }) + .fail(function(jqXHR) { + //if 500 or 404 (abort is okay though) + if (jqXHR.statusText !== "abort") { + $('#browse-resultswrapper').addClass('d-none'); + $('#browse-suggestionserror').removeClass('d-none'); + } }); - - $('#browse-selectionsearch ol').append(fragment.cloneNode(true)); - }) - .fail(function(jqXHR) { - //if 500 or 404 (abort is okay though) - if (jqXHR.statusText !== "abort") { - $('#browse-resultswrapper').addClass('d-none'); - $('#browse-suggestionserror').removeClass('d-none'); - } - }); - + } }); - }); diff --git a/opac/opac-browse.pl b/opac/opac-browse.pl index bbad0d4f51..84df8c83ed 100755 --- a/opac/opac-browse.pl +++ b/opac/opac-browse.pl @@ -104,6 +104,7 @@ sub _filter_for_output { { id => $biblionumber, title => $biblio->title, + subtitle => $biblio->subtitle, author => $biblio->author, }; }; -- 2.39.5