@@ -, +, @@ - 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. --- koha-tmpl/opac-tmpl/bootstrap/css/src/_common.scss | 2 +- koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss | 139 +++++++--------- .../opac-tmpl/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(-) --- a/koha-tmpl/opac-tmpl/bootstrap/css/src/_common.scss +++ a/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"; --- a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss +++ a/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"; --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt +++ a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt @@ -41,44 +41,70 @@

Browse search

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

Results

-

+
Loading
+ -
- - - - -
    -
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    [% ELSE %] --- a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js +++ a/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'); - } - }); - + } }); - }); --- a/opac/opac-browse.pl +++ a/opac/opac-browse.pl @@ -104,6 +104,7 @@ sub _filter_for_output { { id => $biblionumber, title => $biblio->title, + subtitle => $biblio->subtitle, author => $biblio->author, }; }; --