View | Details | Raw Unified | Return to bug 14567
Collapse All | Expand All

(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt (+106 lines)
Line 0 Link Here
1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2
3
<html>
4
<head>
5
  <meta name="generator" content=
6
  "HTML Tidy for Linux (vers 25 March 2009), see www.w3.org">
7
8
  <title></title>
9
</head>
10
11
<body>
12
  [% USE Koha %] [% INCLUDE 'doc-head-open.inc' %][% IF (
13
  LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha
14
  online[% END %] catalog &rsaquo; Browse our catalog [% INCLUDE
15
  'doc-head-close.inc' %] [% BLOCK cssinclude %][% END %] [%
16
  INCLUDE 'bodytag.inc' bodyid='opac-browser' %] [% INCLUDE
17
  'masthead.inc' %]
18
19
  <div class="main">
20
    <ul class="breadcrumb">
21
      <li><a href="/cgi-bin/koha/opac-main.pl">Home</a>
22
      <span class="divider">&rsaquo;</span></li>
23
24
      <li><a href="#">Browse search</a></li>
25
    </ul>
26
27
    <div class="container-fluid">
28
      <div class="row-fluid">
29
        [% IF ( OpacNav || OpacNavBottom ) %]
30
31
        <div class="span2">
32
          <div id="navigation">
33
            [% INCLUDE 'navigation.inc' %]
34
          </div>
35
        </div>[% END %] [% IF ( OpacNav ) %]
36
37
        <div class="span10">
38
          [% ELSE %]
39
40
          <div class="span12">
41
            [% END %]
42
43
            <div id="browse-search">
44
              <h1>Browse search</h1>
45
46
              <form>
47
                <label for="browse-searchterm">Search for:</label>
48
                <input type="search" id="browse-searchterm" name="searchterm" value="">
49
                    <label for="browse-searchfield" class="hide-text">Search type:</label> <select id="browse-searchfield" name="searchfield">
50
                  <option value="author">
51
                    Author
52
                  </option>
53
54
                  <option value="subject">
55
                    Subject
56
                  </option>
57
58
                  <option value="title">
59
                    Title
60
                  </option>
61
                </select>
62
63
                <div id="browse-searchfuzziness">
64
                  <label for="exact" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="exact" value="0">Exact</label>
65
                  <label for="fuzzy" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="fuzzy" value="1" checked="checked"> Fuzzy</label>
66
                  <label for="reallyfuzzy" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="reallyfuzzy" value="2"> Really Fuzzy</label>
67
                </div><button class="btn btn-success" type="submit" accesskey="s">Search</button>
68
              </form>
69
70
              <p id="browse-suggestionserror" class="error hidden">
71
              An error occured, please try again.</p>
72
73
              <div id="browse-resultswrapper" class="hidden">
74
                <ol id="browse-searchresults" class="span3" start="-1" aria-live="polite">
75
                  <li class="loading hidden"><img src="[%%20interface%20%]/[%%20theme%20%]/images/loading.gif" alt=""> Loading</li>
76
77
                  <li class="no-results hidden">Sorry, there are no results, try a different search term.</li>
78
                </ol>
79
80
                <h3 id="browse-selection" class="span9"></h3>
81
82
                <div id="browse-selectionsearch" class=
83
                "span9 hidden">
84
                  <div class="loading hidden">
85
                    <img src="[%%20interface%20%]/[%%20theme%20%]/images/loading.gif" alt=""> Loading
86
                  </div>
87
88
                  <div class="no-results hidden">
89
                    No results
90
                  </div>
91
92
                  <ol aria-live="polite"></ol>
93
                </div>
94
              </div><!-- / .results-wrapper -->
95
            </div><!-- / .userbrowser -->
96
          </div><!-- / .span10 -->
97
        </div><!-- / .row-fluid -->
98
      </div><!-- / .container-fluid -->
99
    </div><!-- / .main -->
100
    [% INCLUDE 'opac-bottom.inc' %] [% BLOCK jsinclude
101
    %]<script type="text/javascript" src=
102
    "[%%20interface%20%]/[%%20theme%20%]/js/browse.js">
103
</script>[% END %]
104
  </div>
105
</body>
106
</html>
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js (+176 lines)
Line 0 Link Here
1
jQuery.fn.overflowScrollReset = function() {
2
    $(this).scrollTop($(this).scrollTop() - $(this).offset().top);
3
    return this;
4
};
5
6
$(document).ready(function(){
7
    var xhrGetSuggestions, xhrGetResults;
8
9
    $('#browse-search form').submit(function(event) {
10
        // if there is an in progress request, abort it so we
11
        // don't end up with  a race condition
12
        if(xhrGetSuggestions && xhrGetSuggestions.readyState != 4){
13
            xhrGetSuggestions.abort();
14
        }
15
16
        var userInput = $('#browse-searchterm').val().trim();
17
        var userField = $('#browse-searchfield').val();
18
        var userFuzziness = $('input[name=browse-searchfuzziness]:checked', '#browse-searchfuzziness').val();
19
        var leftPaneResults = $('#browse-searchresults li').not('.loading, .no-results');
20
        var rightPaneResults = $('#browse-selectionsearch ol li');
21
22
        event.preventDefault();
23
24
        if(!userInput) {
25
            return;
26
        }
27
28
        // remove any error states and show the results area (except right pane)
29
        $('#browse-suggestionserror').addClass('hidden');
30
        $('#browse-searchresults .no-results').addClass('hidden');
31
        $('#browse-resultswrapper').removeClass('hidden');
32
        $('#browse-selection').addClass('hidden').text("");
33
        $('#browse-selectionsearch').addClass('hidden');
34
35
        // clear any results from left and right panes
36
        leftPaneResults.remove();
37
        rightPaneResults.remove();
38
39
        // show the spinner in the left pane
40
        $('#browse-searchresults .loading').removeClass('hidden');
41
42
        xhrGetSuggestions = $.get(window.location.pathname, {api: "GetSuggestions", field: userField, prefix: userInput, fuzziness: userFuzziness})
43
            .always(function() {
44
                // hide spinner
45
                $('#browse-searchresults .loading').addClass('hidden');
46
            })
47
            .done(function(data) {
48
                var fragment = document.createDocumentFragment();
49
50
                if (data.length === 0) {
51
                    $('#browse-searchresults .no-results').removeClass('hidden');
52
53
                    return;
54
                }
55
56
                // scroll to top of container again
57
                $("#browse-searchresults").overflowScrollReset();
58
59
                // store the type of search that was performed as an attrib
60
                $('#browse-searchresults').data('field', userField);
61
62
                $.each(data, function(index, object) {
63
                    // use a document fragment so we don't need to nest the elems
64
                    // or append during each iteration (which would be slow)
65
                    var elem = document.createElement("li");
66
                    var link = document.createElement("a");
67
                    link.textContent = object.text;
68
                    link.setAttribute("href", "#");
69
                    elem.appendChild(link);
70
                    fragment.appendChild(elem);
71
                });
72
73
                $('#browse-searchresults').append(fragment.cloneNode(true));
74
            })
75
            .fail(function(jqXHR) {
76
                //if 500 or 404 (abort is okay though)
77
                if (jqXHR.statusText !== "abort") {
78
                    $('#browse-resultswrapper').addClass('hidden');
79
                    $('#browse-suggestionserror').removeClass('hidden');
80
                }
81
            });
82
    });
83
84
    $('#browse-searchresults').on("click", 'a', function(event) {
85
        // if there is an in progress request, abort it so we
86
        // don't end up with  a race condition
87
        if(xhrGetResults && xhrGetResults.readyState != 4){
88
            xhrGetResults.abort();
89
        }
90
91
        var term = $(this).text();
92
        var field = $('#browse-searchresults').data('field');
93
        var rightPaneResults = $('#browse-selectionsearch ol li');
94
95
        event.preventDefault();
96
97
        // clear any current selected classes and add a new one
98
        $(this).parent().siblings().children().removeClass('selected');
99
        $(this).addClass('selected');
100
101
        // copy in the clicked text
102
        $('#browse-selection').removeClass('hidden').text(term);
103
104
        // show the right hand pane if it is not shown already
105
        $('#browse-selectionsearch').removeClass('hidden');
106
107
        // hide the no results element
108
        $('#browse-selectionsearch .no-results').addClass('hidden');
109
110
        // clear results
111
        rightPaneResults.remove();
112
113
        // turn the spinner on
114
        $('#browse-selectionsearch .loading').removeClass('hidden');
115
116
        // do the query for the term
117
        xhrGetResults = $.get(window.location.pathname, {api: "GetResults", field: field, term: term})
118
            .always(function() {
119
                // hide spinner
120
                $('#browse-selectionsearch .loading').addClass('hidden');
121
            })
122
            .done(function(data) {
123
                var fragment = document.createDocumentFragment();
124
125
                if (data.length === 0) {
126
                    $('#browse-selectionsearch .no-results').removeClass('hidden');
127
128
                    return;
129
                }
130
131
                // scroll to top of container again
132
                $("#browse-selectionsearch").overflowScrollReset();
133
134
                $.each(data, function(index, object) {
135
                    // use a document fragment so we don't need to nest the elems
136
                    // or append during each iteration (which would be slow)
137
                    var elem = document.createElement("li");
138
                    var title = document.createElement("h4");
139
                    var link = document.createElement("a");
140
                    var author = document.createElement("p");
141
                    var subjects = document.createElement("p");
142
                    var destination = window.location.pathname;
143
144
                    destination = destination.replace("browse", "detail");
145
                    destination = destination + "?biblionumber=" + object.id;
146
147
                    author.className = "author";
148
                    subjects.className = "subjects";
149
150
                    link.setAttribute("href", destination);
151
                    link.setAttribute("target", "_blank");
152
                    link.textContent = object.title;
153
                    title.appendChild(link);
154
155
                    author.textContent = object.authors.join(', ');
156
                    subjects.textContent = object.subjects.join(' | ');
157
158
                    elem.appendChild(title);
159
                    elem.appendChild(author);
160
                    elem.appendChild(subjects);
161
                    fragment.appendChild(elem);
162
                });
163
164
                $('#browse-selectionsearch ol').append(fragment.cloneNode(true));
165
            })
166
            .fail(function(jqXHR) {
167
                //if 500 or 404 (abort is okay though)
168
                if (jqXHR.statusText !== "abort") {
169
                    $('#browse-resultswrapper').addClass('hidden');
170
                    $('#browse-suggestionserror').removeClass('hidden');
171
                }
172
            });
173
174
    });
175
176
});
(-)a/koha-tmpl/opac-tmpl/bootstrap/less/opac.less (+101 lines)
Lines 304-309 td { Link Here
304
    }
304
    }
305
}
305
}
306
306
307
/*opac browse search*/
308
#browse-search {
309
310
    form {
311
        label {
312
            display: inline-block;
313
            margin-right:5px;
314
        }
315
316
        [type=submit] {
317
            margin-top: 10px;
318
        }
319
    }
320
321
    #browse-resultswrapper {
322
       margin-top: 4em;
323
324
        @media (min-width: 768px) and (max-width: 984px) {
325
            margin-top: 2em;
326
        }
327
328
        @media (max-width: 767px) {
329
            margin-top: 1em;
330
        }
331
    }
332
    #browse-searchresults, #browse-selectionsearch {
333
        border: 1px solid #E3E3E3;
334
        .border-radius-all(4px);
335
        padding: 0;
336
        overflow-y: auto;
337
        max-height: 31em;
338
        margin-bottom: 2em;
339
    }
340
    #browse-searchresults {
341
        max-height: 31em;
342
        list-style: none;
343
        padding: 10px;
344
345
        a {
346
            display: block;
347
            margin-bottom: 5px;
348
349
            &.selected {
350
                background-color:#EEE;
351
            }
352
        }
353
354
        li:last-child a {
355
            margin-bottom: 0;
356
        }
357
358
        @media (max-width: 767px) {
359
            max-height: 13em;
360
        }
361
    }
362
    #browse-selection {
363
        margin-top: -40px;
364
        padding-top: 0;
365
366
        @media (max-width: 767px) {
367
            margin-top: 0;
368
        }
369
    }
370
    #browse-selectionsearch ol {
371
        list-style: none;
372
        margin: 0;
373
374
        li {
375
            padding: 1em;
376
377
            &:nth-child(odd) {
378
                background-color: #F4F4F4;
379
            }
380
        }
381
    }
382
   #browse-selectionsearch p.subjects {
383
        font-size: 0.9em;
384
        margin-bottom: 0;
385
    }
386
    #browse-selectionsearch h4 {
387
        margin: 0;
388
    }
389
    .error, .no-results {
390
        background-color: #EEE;
391
        border: 1px solid #E8E8E8;
392
        text-align: left;
393
        padding: 0.5em;
394
        .border-radius-all(3px);
395
    }
396
    .loading {
397
        text-align: center;
398
399
        img {
400
            margin:0.5em 0;
401
            position: relative;
402
            left: -5px;
403
        }
404
    }
405
}
406
/*end browse search*/
407
307
/* Override Bootstrap alert */
408
/* Override Bootstrap alert */
308
.alert {
409
.alert {
309
    background: #fffbe5; /* Old browsers */
410
    background: #fffbe5; /* Old browsers */
(-)a/opac/opac-browse.pl (-2 / +11 lines)
Lines 24-29 use CGI qw( -utf8 :standard ); Link Here
24
24
25
use C4::Auth;
25
use C4::Auth;
26
use C4::Context;
26
use C4::Context;
27
use C4::Output;
27
28
28
use Koha::ElasticSearch;
29
use Koha::ElasticSearch;
29
use Koha::SearchEngine::Elasticsearch::Browse;
30
use Koha::SearchEngine::Elasticsearch::Browse;
Lines 41-48 binmode STDOUT, ':utf8'; Link Here
41
my $api = $query->param('api');
42
my $api = $query->param('api');
42
43
43
if ( !$api ) {
44
if ( !$api ) {
45
    my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
46
        {
47
            template_name   => "opac-browse.tt",
48
           query           => $query,
49
            type            => "opac",
50
            authnotrequired => ( C4::Context->preference("OpacPublic") ? 1 : 0 ),
51
        }
52
    );
53
   $template->param();
54
    output_html_with_http_headers $query, $cookie, $template->output;
44
55
45
    # No parameters, so we just render the template.
46
56
47
}
57
}
48
elsif ( $api eq 'GetSuggestions' ) {
58
elsif ( $api eq 'GetSuggestions' ) {
49
- 

Return to bug 14567