@@ -, +, @@
interface
---
.../opac-tmpl/bootstrap/en/modules/opac-browse.tt | 106 +++++++++++++
koha-tmpl/opac-tmpl/bootstrap/js/browse.js | 176 +++++++++++++++++++++
koha-tmpl/opac-tmpl/bootstrap/less/opac.less | 101 ++++++++++++
opac/opac-browse.pl | 12 +-
4 files changed, 394 insertions(+), 1 deletion(-)
create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt
create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/browse.js
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt
+++ a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+ [% IF ( OpacNav || OpacNavBottom ) %]
+
+
+
+ [% INCLUDE 'navigation.inc' %]
+
+
[% END %] [% IF ( OpacNav ) %]
+
+
+ [% ELSE %]
+
+
+ [% END %]
+
+
+
Browse search
+
+
+
+
+ An error occured, please try again.
+
+
+
+ - Loading
+
+ - Sorry, there are no results, try a different search term.
+
+
+
+
+
+
+
Loading
+
+
+
+ No results
+
+
+
+
+
+
+
+
+
+
+ [% INCLUDE 'opac-bottom.inc' %] [% BLOCK jsinclude
+ %][% END %]
+
+
+
--- a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js
+++ a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js
@@ -0,0 +1,176 @@
+jQuery.fn.overflowScrollReset = function() {
+ $(this).scrollTop($(this).scrollTop() - $(this).offset().top);
+ return this;
+};
+
+$(document).ready(function(){
+ var xhrGetSuggestions, xhrGetResults;
+
+ $('#browse-search form').submit(function(event) {
+ // if there is an in progress request, abort it so we
+ // don't end up with a race condition
+ if(xhrGetSuggestions && xhrGetSuggestions.readyState != 4){
+ xhrGetSuggestions.abort();
+ }
+
+ 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');
+
+ event.preventDefault();
+
+ if(!userInput) {
+ return;
+ }
+
+ // remove any error states and show the results area (except right pane)
+ $('#browse-suggestionserror').addClass('hidden');
+ $('#browse-searchresults .no-results').addClass('hidden');
+ $('#browse-resultswrapper').removeClass('hidden');
+ $('#browse-selection').addClass('hidden').text("");
+ $('#browse-selectionsearch').addClass('hidden');
+
+ // clear any results from left and right panes
+ leftPaneResults.remove();
+ rightPaneResults.remove();
+
+ // show the spinner in the left pane
+ $('#browse-searchresults .loading').removeClass('hidden');
+
+ xhrGetSuggestions = $.get(window.location.pathname, {api: "GetSuggestions", field: userField, prefix: userInput, fuzziness: userFuzziness})
+ .always(function() {
+ // hide spinner
+ $('#browse-searchresults .loading').addClass('hidden');
+ })
+ .done(function(data) {
+ var fragment = document.createDocumentFragment();
+
+ if (data.length === 0) {
+ $('#browse-searchresults .no-results').removeClass('hidden');
+
+ 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);
+ });
+
+ $('#browse-searchresults').append(fragment.cloneNode(true));
+ })
+ .fail(function(jqXHR) {
+ //if 500 or 404 (abort is okay though)
+ if (jqXHR.statusText !== "abort") {
+ $('#browse-resultswrapper').addClass('hidden');
+ $('#browse-suggestionserror').removeClass('hidden');
+ }
+ });
+ });
+
+ $('#browse-searchresults').on("click", 'a', 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 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('hidden').text(term);
+
+ // show the right hand pane if it is not shown already
+ $('#browse-selectionsearch').removeClass('hidden');
+
+ // hide the no results element
+ $('#browse-selectionsearch .no-results').addClass('hidden');
+
+ // clear results
+ rightPaneResults.remove();
+
+ // turn the spinner on
+ $('#browse-selectionsearch .loading').removeClass('hidden');
+
+ // 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('hidden');
+ })
+ .done(function(data) {
+ var fragment = document.createDocumentFragment();
+
+ if (data.length === 0) {
+ $('#browse-selectionsearch .no-results').removeClass('hidden');
+
+ 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 subjects = document.createElement("p");
+ var destination = window.location.pathname;
+
+ destination = destination.replace("browse", "detail");
+ destination = destination + "?biblionumber=" + object.id;
+
+ author.className = "author";
+ subjects.className = "subjects";
+
+ link.setAttribute("href", destination);
+ link.setAttribute("target", "_blank");
+ link.textContent = object.title;
+ title.appendChild(link);
+
+ author.textContent = object.authors.join(', ');
+ subjects.textContent = object.subjects.join(' | ');
+
+ elem.appendChild(title);
+ elem.appendChild(author);
+ elem.appendChild(subjects);
+ fragment.appendChild(elem);
+ });
+
+ $('#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('hidden');
+ $('#browse-suggestionserror').removeClass('hidden');
+ }
+ });
+
+ });
+
+});
--- a/koha-tmpl/opac-tmpl/bootstrap/less/opac.less
+++ a/koha-tmpl/opac-tmpl/bootstrap/less/opac.less
@@ -304,6 +304,107 @@ td {
}
}
+/*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-all(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;
+ }
+ }
+
+ li:last-child a {
+ margin-bottom: 0;
+ }
+
+ @media (max-width: 767px) {
+ max-height: 13em;
+ }
+ }
+ #browse-selection {
+ margin-top: -40px;
+ padding-top: 0;
+
+ @media (max-width: 767px) {
+ margin-top: 0;
+ }
+ }
+ #browse-selectionsearch ol {
+ list-style: none;
+ margin: 0;
+
+ li {
+ padding: 1em;
+
+ &: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-all(3px);
+ }
+ .loading {
+ text-align: center;
+
+ img {
+ margin:0.5em 0;
+ position: relative;
+ left: -5px;
+ }
+ }
+}
+/*end browse search*/
+
/* Override Bootstrap alert */
.alert {
background: #fffbe5; /* Old browsers */
--- a/opac/opac-browse.pl
+++ a/opac/opac-browse.pl
@@ -24,6 +24,7 @@ use CGI qw( -utf8 :standard );
use C4::Auth;
use C4::Context;
+use C4::Output;
use Koha::ElasticSearch;
use Koha::SearchEngine::Elasticsearch::Browse;
@@ -41,8 +42,17 @@ binmode STDOUT, ':utf8';
my $api = $query->param('api');
if ( !$api ) {
+ my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+ {
+ template_name => "opac-browse.tt",
+ query => $query,
+ type => "opac",
+ authnotrequired => ( C4::Context->preference("OpacPublic") ? 1 : 0 ),
+ }
+ );
+ $template->param();
+ output_html_with_http_headers $query, $cookie, $template->output;
- # No parameters, so we just render the template.
}
elsif ( $api eq 'GetSuggestions' ) {
--