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

(-)a/koha-tmpl/opac-tmpl/bootstrap/css/src/_common.scss (-1 / +1 lines)
Lines 44-50 $sci-heading-color: #727272; Link Here
44
@import "../../../../../node_modules/bootstrap/scss/breadcrumb";
44
@import "../../../../../node_modules/bootstrap/scss/breadcrumb";
45
@import "../../../../../node_modules/bootstrap/scss/button-group";
45
@import "../../../../../node_modules/bootstrap/scss/button-group";
46
@import "../../../../../node_modules/bootstrap/scss/buttons";
46
@import "../../../../../node_modules/bootstrap/scss/buttons";
47
// @import "../../../../../node_modules/bootstrap/scss/card";
47
@import "../../../../../node_modules/bootstrap/scss/card";
48
// @import "../../../../../node_modules/bootstrap/scss/carousel";
48
// @import "../../../../../node_modules/bootstrap/scss/carousel";
49
@import "../../../../../node_modules/bootstrap/scss/close";
49
@import "../../../../../node_modules/bootstrap/scss/close";
50
// @import "../../../../../node_modules/bootstrap/scss/code";
50
// @import "../../../../../node_modules/bootstrap/scss/code";
(-)a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss (-82 / +57 lines)
Lines 2513-2606 $star-selected: #EDB867; Link Here
2513
}
2513
}
2514
2514
2515
/*opac browse search*/
2515
/*opac browse search*/
2516
#browse-search {
2517
2518
    form {
2519
        label {
2520
            display: inline-block;
2521
            margin-right:5px;
2522
        }
2523
2524
        [type=submit] {
2525
            margin-top: 10px;
2526
        }
2527
    }
2528
2529
    #browse-resultswrapper {
2530
       margin-top: 4em;
2531
2532
        @media (min-width: 768px) and (max-width: 984px) {
2533
            margin-top: 2em;
2534
        }
2535
2536
        @media (max-width: 767px) {
2537
            margin-top: 1em;
2538
        }
2539
    }
2540
    #browse-searchresults, #browse-selectionsearch {
2541
        border: 1px solid #E3E3E3;
2542
        border-radius: 4px;
2543
        padding: 0;
2544
        overflow-y: auto;
2545
        max-height: 31em;
2546
        margin-bottom: 2em;
2547
    }
2548
    #browse-searchresults {
2549
        max-height: 31em;
2550
        list-style: none;
2551
        padding: 10px;
2552
2516
2553
        a {
2517
#browse-resultswrapper {
2554
            display: block;
2518
    margin-top: 15px;
2555
            margin-bottom: 5px;
2519
}
2556
2520
#browse-searchfuzziness {
2557
            &.selected {
2521
    padding: 15px 0;
2558
                background-color:#EEE;
2522
}
2559
            }
2560
        }
2561
2523
2562
        li:last-child a {
2524
#browse-searchresults, #browse-selectionsearch {
2563
            margin-bottom: 0;
2525
    border: 1px solid #E3E3E3;
2564
        }
2526
    border-radius: 4px;
2527
    padding: 0;
2528
    margin-bottom: 2em;
2529
}
2565
2530
2566
        @media (max-width: 767px) {
2531
#browse-selectionsearch p.subjects {
2567
            max-height: 13em;
2532
    font-size: 0.9em;
2568
        }
2533
    margin-bottom: 0;
2569
    }
2534
}
2570
    #browse-selection {
2571
        margin-top: -40px;
2572
        padding-top: 0;
2573
2535
2574
        @media (max-width: 767px) {
2536
#browse-selectionsearch h4 {
2575
            margin-top: 0;
2537
    margin: 0;
2576
        }
2538
}
2577
    }
2578
    #browse-selectionsearch ol {
2579
        list-style: none;
2580
        margin: 0;
2581
2539
2582
        li {
2540
#browse-suggestionserror {
2583
            padding: 1em;
2541
    margin-top: 1rem;
2542
}
2584
2543
2585
            &:nth-child(odd) {
2544
#browse-search {
2586
                background-color: #F4F4F4;
2587
            }
2588
        }
2589
    }
2590
   #browse-selectionsearch p.subjects {
2591
        font-size: 0.9em;
2592
        margin-bottom: 0;
2593
    }
2594
    #browse-selectionsearch h4 {
2595
        margin: 0;
2596
    }
2597
    .error, .no-results {
2598
        background-color: #EEE;
2599
        border: 1px solid #E8E8E8;
2600
        text-align: left;
2601
        padding: 0.5em;
2602
        border-radius: 3px;
2603
    }
2604
    .loading {
2545
    .loading {
2605
        text-align: center;
2546
        text-align: center;
2606
2547
Lines 2611-2616 $star-selected: #EDB867; Link Here
2611
        }
2552
        }
2612
    }
2553
    }
2613
}
2554
}
2555
2556
#card_template {
2557
    display: none;
2558
}
2559
2560
.result-title {
2561
    margin-bottom: .4rem;
2562
    margin-left: 1rem;
2563
}
2614
/*end browse search*/
2564
/*end browse search*/
2615
2565
2616
/* Skip to content link. CSS adapted from https://webaim.org/ */
2566
/* Skip to content link. CSS adapted from https://webaim.org/ */
Lines 2638-2641 $star-selected: #EDB867; Link Here
2638
    }
2588
    }
2639
}
2589
}
2640
2590
2591
.accordion {
2592
    .card-header {
2593
        padding: 0;
2594
2595
        a {
2596
            display: block;
2597
            padding: .75rem 1.25rem;
2598
2599
            &[aria-expanded="true"] { font-weight: bold; }
2600
            &[aria-expanded="false"] { font-weight: normal; }
2601
2602
            &:hover {
2603
                background-color: rgba(255, 255, 204, 0.8);
2604
                text-decoration: none;
2605
            }
2606
        }
2607
    }
2608
2609
    .collapse {
2610
        &.show {
2611
            border-top: 1px solid #888;
2612
        }
2613
    }
2614
}
2615
2641
@import "responsive";
2616
@import "responsive";
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt (-31 / +57 lines)
Lines 41-84 Link Here
41
                    <h1>Browse search</h1>
41
                    <h1>Browse search</h1>
42
42
43
                    <form>
43
                    <form>
44
                        <label for="browse-searchterm">Search for:</label>
44
                        <div class="form-row">
45
                        <input type="search" id="browse-searchterm" name="searchterm" value="">
45
                            <div class="col">
46
                        <label for="browse-searchfield" class="hide-text">Search type:</label>
46
                                <label for="browse-searchterm">Search for:</label>
47
                        <select id="browse-searchfield" name="searchfield">
47
                                <input type="search" id="browse-searchterm" class="form-control" name="searchterm" value="" />
48
                            <option value="author">Author</option>
48
                            </div> <!-- /.col-12.col-sm-9 -->
49
                            <option value="subject">Subject</option>
49
                            <div class="col-auto">
50
                            <option value="title">Title</option>
50
                                <label for="browse-searchfield">Search type:</label>
51
                        </select>
51
                                <select id="browse-searchfield" name="searchfield" class="form-control">
52
52
                                    <option value="author">Author</option>
53
                        <div id="browse-searchfuzziness">
53
                                    <option value="subject">Subject</option>
54
                            <label for="exact" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="exact" value="0">Exact</label>
54
                                    <option value="title">Title</option>
55
                            <label for="fuzzy" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="fuzzy" value="1" checked="checked"> Fuzzy</label>
55
                                </select>
56
                            <label for="reallyfuzzy" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="reallyfuzzy" value="2"> Really fuzzy</label>
56
                            </div> <!-- /.col-auto -->
57
                        </div> <!-- /.form-row -->
58
                        <div class="form-row">
59
                            <div class="col">
60
                                <div id="browse-searchfuzziness">
61
                                    <div class="form-check form-check-inline">
62
                                        <label for="exact" class="form-check-label">
63
                                            <input class="form-check-input" type="radio" name="browse-searchfuzziness" id="exact" value="0" />Exact
64
                                        </label>
65
                                    </div> <!-- /.form-check.form-check-inline -->
66
                                    <div class="form-check form-check-inline">
67
                                        <label for="fuzzy" class="form-check-label">
68
                                            <input class="form-check-input" type="radio" name="browse-searchfuzziness" id="fuzzy" value="1" checked="checked" /> Fuzzy
69
                                        </label>
70
                                    </div> <!-- /.form-check.form-check-inline -->
71
                                    <div class="form-check form-check-inline">
72
                                        <label for="reallyfuzzy" class="form-check-label">
73
                                            <input class="form-check-input" type="radio" name="browse-searchfuzziness" id="reallyfuzzy" value="2" /> Really fuzzy
74
                                        </label>
75
                                    </div> <!-- /.form-check.form-check-inline -->
76
                                </div> <!-- /#browse-searchfuzziness -->
77
                            </div> <!-- /.col -->
78
                        </div> <!-- /.form-row -->
79
                        <div class="form-row">
80
                            <div class="col">
81
                                <button class="btn btn-primary" type="submit" accesskey="s">Search</button>
82
                            </div>
57
                        </div>
83
                        </div>
58
                        <button class="btn btn-success" type="submit" accesskey="s">Search</button>
59
                    </form>
84
                    </form>
60
85
61
                    <p id="browse-suggestionserror" class="error hidden">
86
                    <div id="browse-suggestionserror" class="alert alert-warning d-none" role="alert">
62
                    An error occurred, please try again.</p>
87
                        An error occurred, please try again.
88
                    </div>
63
89
64
                    <div id="browse-resultswrapper" class="d-none">
90
                    <div id="browse-resultswrapper" class="d-none">
65
                        <ul id="browse-searchresults" start="-1" aria-live="polite">
91
                        <h2>Results</h2>
66
                            <li class="loading hidden"><img src="[% interface | html %]/[% theme |html %]/images/loading.gif" alt=""> Loading</li>
67
68
                            <li class="no-results hidden">Sorry, there are no results, try a different search term.</li>
69
                        </ul>
70
92
71
                        <h3 id="browse-selection"></h3>
93
                        <div class="loading d-none"><img src="[% interface | html %]/[% theme |html %]/images/loading.gif" alt=""> Loading</div>
94
                        <div class="alert alert-warning no-results d-none" role="alert">Sorry, there are no results. Try a different search term.</div>
72
95
73
                        <div id="browse-selectionsearch" class="d-none">
96
                        <div class="accordion" id="browse-searchresults">
74
                            <div class="loading hidden">
97
                            <div id="card_template" class="card">
75
                                <img src="[% interface | html %]/[% theme | html %]/images/loading.gif" alt=""> Loading
98
                                <div class="card-header" id="heading">
76
                            </div>
99
                                    <a class="expand-result" href="#" data-toggle="collapse" aria-expanded="false" aria-controls="collapse">
77
100
                                    </a>
78
                            <div class="no-results hidden">No results</div>
101
                                </div> <!-- /#heading.card-header -->
79
102
                                <div id="collapse" class="collapse" aria-labelledby="heading" data-parent="#browse-searchresults">
80
                            <ol aria-live="polite"></ol>
103
                                    <div class="card-body">
81
                        </div>
104
                                    </div>
105
                                </div> <!-- /#collapse.collapse -->
106
                            </div> <!-- /#card_template.card -->
107
                        </div> <!-- /#browse-searchresults.accordion -->
82
                    </div><!-- / #browse-resultswrapper -->
108
                    </div><!-- / #browse-resultswrapper -->
83
                </div><!-- /#browse-search -->
109
                </div><!-- /#browse-search -->
84
            [% ELSE %]
110
            [% ELSE %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js (-100 / +79 lines)
Lines 16-23 $(document).ready(function(){ Link Here
16
        var userInput = $('#browse-searchterm').val().trim();
16
        var userInput = $('#browse-searchterm').val().trim();
17
        var userField = $('#browse-searchfield').val();
17
        var userField = $('#browse-searchfield').val();
18
        var userFuzziness = $('input[name=browse-searchfuzziness]:checked', '#browse-searchfuzziness').val();
18
        var userFuzziness = $('input[name=browse-searchfuzziness]:checked', '#browse-searchfuzziness').val();
19
        var leftPaneResults = $('#browse-searchresults li').not('.loading, .no-results');
19
        var card_template = $("#card_template");
20
        var rightPaneResults = $('#browse-selectionsearch ol li');
21
20
22
        event.preventDefault();
21
        event.preventDefault();
23
22
Lines 25-73 $(document).ready(function(){ Link Here
25
            return;
24
            return;
26
        }
25
        }
27
26
28
        // remove any error states and show the results area (except right pane)
27
        /* return the browsing results to empty state in case previous results have been loaded */
28
        $("#browse-searchresults").empty().append( card_template );
29
30
        // remove any error states and show the results area
29
        $('#browse-suggestionserror').addClass('d-none');
31
        $('#browse-suggestionserror').addClass('d-none');
30
        $('#browse-searchresults .no-results').addClass('d-none');
32
        $('.no-results').addClass('d-none');
31
        $('#browse-resultswrapper').removeClass('d-none');
33
        $('#browse-resultswrapper').removeClass('d-none');
32
        $('#browse-selection').addClass('d-none').text("");
34
        /* Reset results browser to default state */
33
        $('#browse-selectionsearch').addClass('d-none');
34
35
        // clear any results from left and right panes
36
        leftPaneResults.remove();
37
        rightPaneResults.remove();
38
35
39
        // show the spinner in the left pane
36
        // show the spinner
40
        $('#browse-searchresults .loading').removeClass('d-none');
37
        $('.loading').removeClass('d-none');
41
38
42
        xhrGetSuggestions = $.get(window.location.pathname, {api: "GetSuggestions", field: userField, prefix: userInput, fuzziness: userFuzziness})
39
        xhrGetSuggestions = $.get(window.location.pathname, {api: "GetSuggestions", field: userField, prefix: userInput, fuzziness: userFuzziness})
43
            .always(function() {
40
            .always(function() {
44
                // hide spinner
41
                // hide spinner
45
                $('#browse-searchresults .loading').addClass('d-none');
42
                $('.loading').addClass('d-none');
46
            })
43
            })
47
            .done(function(data) {
44
            .done(function(data) {
48
                var fragment = document.createDocumentFragment();
45
                var fragment = document.createDocumentFragment();
49
46
50
                if (data.length === 0) {
47
                if (data.length === 0) {
51
                    $('#browse-searchresults .no-results').removeClass('d-none');
48
                    $('.no-results').removeClass('d-none');
52
53
                    return;
49
                    return;
54
                }
50
                }
55
51
56
                // scroll to top of container again
57
                $("#browse-searchresults").overflowScrollReset();
58
59
                // store the type of search that was performed as an attrib
52
                // store the type of search that was performed as an attrib
60
                $('#browse-searchresults').data('field', userField);
53
                $('#browse-searchresults').data('field', userField);
61
54
62
                $.each(data, function(index, object) {
55
                $.each(data, function(index, object) {
63
                    // use a document fragment so we don't need to nest the elems
56
                    // use a document fragment so we don't need to nest the elems
64
                    // or append during each iteration (which would be slow)
57
                    // or append during each iteration (which would be slow)
65
                    var elem = document.createElement("li");
58
                    var card = card_template.clone().removeAttr("id");
66
                    var link = document.createElement("a");
59
                    // change card-header id
67
                    link.textContent = object.text;
60
                    card
68
                    link.setAttribute("href", "#");
61
                        .find(".card-header")
69
                    elem.appendChild(link);
62
                        .attr("id", "heading" + index)
70
                    fragment.appendChild(elem);
63
                        .find("a").attr("data-target", "#collapse" + index)
64
                        .attr("aria-controls", "collapse" + index)
65
                        .text(object.text);
66
                    card
67
                        .find(".collapse")
68
                        .attr("id", "collapse" + index)
69
                        .attr("aria-labelledby", "heading" + index);
70
                    $(fragment).append(card);
71
                });
71
                });
72
72
73
                $('#browse-searchresults').append(fragment.cloneNode(true));
73
                $('#browse-searchresults').append(fragment.cloneNode(true));
Lines 81-172 $(document).ready(function(){ Link Here
81
            });
81
            });
82
    });
82
    });
83
83
84
    $('#browse-searchresults').on("click", 'a', function(event) {
84
    $('#browse-searchresults').on("click", 'a.expand-result', function(event) {
85
        // if there is an in progress request, abort it so we
85
        // if there is an in progress request, abort it so we
86
        // don't end up with  a race condition
86
        // don't end up with  a race condition
87
        if(xhrGetResults && xhrGetResults.readyState != 4){
87
        if(xhrGetResults && xhrGetResults.readyState != 4){
88
            xhrGetResults.abort();
88
            xhrGetResults.abort();
89
        }
89
        }
90
90
91
        var term = $(this).text();
91
        var link = $(this);
92
        var target =  link.data("target");
93
        var term = link.text();
94
92
        var field = $('#browse-searchresults').data('field');
95
        var field = $('#browse-searchresults').data('field');
93
        var rightPaneResults = $('#browse-selectionsearch ol li');
94
96
95
        event.preventDefault();
97
        event.preventDefault();
96
98
97
        // clear any current selected classes and add a new one
99
        /* Don't load data via AJAX if it has already been loaded */
98
        $(this).parent().siblings().children().removeClass('selected');
100
        if ($(target).find(".result-title").length == 0) {
99
        $(this).addClass('selected');
101
            // do the query for the term
100
102
            xhrGetResults = $.get(window.location.pathname, {api: "GetResults", field: field, term: term})
101
        // copy in the clicked text
103
                .done(function(data) {
102
        $('#browse-selection').removeClass('d-none').text(term);
104
                    var fragment = document.createDocumentFragment();
103
105
104
        // show the right hand pane if it is not shown already
106
                    if (data.length === 0) {
105
        $('#browse-selectionsearch').removeClass('d-none');
107
                        $('#browse-selectionsearch .no-results').removeClass('d-none');
106
108
                        return;
107
        // hide the no results element
109
                    }
108
        $('#browse-selectionsearch .no-results').addClass('d-none');
110
109
111
110
        // clear results
112
                    $.each(data, function(index, object) {
111
        rightPaneResults.remove();
113
                        // use a document fragment so we don't need to nest the elems
112
114
                        // or append during each iteration (which would be slow)
113
        // turn the spinner on
115
                        var elem = document.createElement("div");
114
        $('#browse-selectionsearch .loading').removeClass('d-none');
116
                        elem.className = "result-title";
115
117
116
        // do the query for the term
118
                        var destination = window.location.pathname;
117
        xhrGetResults = $.get(window.location.pathname, {api: "GetResults", field: field, term: term})
119
                        destination = destination.replace("browse", "detail");
118
            .always(function() {
120
                        destination = destination + "?biblionumber=" + object.id;
119
                // hide spinner
121
120
                $('#browse-selectionsearch .loading').addClass('d-none');
122
                        var link = document.createElement("a");
121
            })
123
                        link.setAttribute("href", destination);
122
            .done(function(data) {
124
                        link.setAttribute("target", "_blank");
123
                var fragment = document.createDocumentFragment();
125
                        link.textContent = object.title;
124
126
                        if( object.subtitle ){
125
                if (data.length === 0) {
127
                            link.textContent += " " + object.subtitle;
126
                    $('#browse-selectionsearch .no-results').removeClass('d-none');
128
                        }
127
129
                        elem.appendChild(link);
128
                    return;
130
129
                }
131
                        if( object.author ){
130
132
                            var author = document.createElement("span");
131
                // scroll to top of container again
133
                            author.className = "author";
132
                $("#browse-selectionsearch").overflowScrollReset();
134
                            author.textContent = " " + object.author;
133
135
                            elem.appendChild(author);
134
                $.each(data, function(index, object) {
136
                        }
135
                    // use a document fragment so we don't need to nest the elems
137
                        fragment.appendChild(elem);
136
                    // or append during each iteration (which would be slow)
138
                    });
137
                    var elem = document.createElement("li");
139
138
                    var title = document.createElement("h4");
140
                    $( target ).find(".card-body").append(fragment.cloneNode(true));
139
                    var link = document.createElement("a");
141
                })
140
                    var author = document.createElement("p");
142
                .fail(function(jqXHR) {
141
                    var destination = window.location.pathname;
143
                    //if 500 or 404 (abort is okay though)
142
144
                    if (jqXHR.statusText !== "abort") {
143
                    destination = destination.replace("browse", "detail");
145
                        $('#browse-resultswrapper').addClass('d-none');
144
                    destination = destination + "?biblionumber=" + object.id;
146
                        $('#browse-suggestionserror').removeClass('d-none');
145
147
                    }
146
                    author.className = "author";
147
148
                    link.setAttribute("href", destination);
149
                    link.setAttribute("target", "_blank");
150
                    link.textContent = object.title;
151
                    title.appendChild(link);
152
153
                    author.textContent = object.author;
154
155
                    elem.appendChild(title);
156
                    elem.appendChild(author);
157
                    fragment.appendChild(elem);
158
                });
148
                });
159
149
        }
160
                $('#browse-selectionsearch ol').append(fragment.cloneNode(true));
161
            })
162
            .fail(function(jqXHR) {
163
                //if 500 or 404 (abort is okay though)
164
                if (jqXHR.statusText !== "abort") {
165
                    $('#browse-resultswrapper').addClass('d-none');
166
                    $('#browse-suggestionserror').removeClass('d-none');
167
                }
168
            });
169
170
    });
150
    });
171
172
});
151
});
(-)a/opac/opac-browse.pl (-1 / +1 lines)
Lines 104-109 sub _filter_for_output { Link Here
104
          {
104
          {
105
            id => $biblionumber,
105
            id => $biblionumber,
106
            title    => $biblio->title,
106
            title    => $biblio->title,
107
            subtitle => $biblio->subtitle,
107
            author  => $biblio->author,
108
            author  => $biblio->author,
108
          };
109
          };
109
    };
110
    };
110
- 

Return to bug 27200