lp1849212 Course Browse
authorKyle Huckins <khuckins@catalyte.io>
Tue, 11 Feb 2020 20:08:57 +0000 (20:08 +0000)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 14 Sep 2020 22:17:06 +0000 (18:17 -0400)
- Add Course Browse UI
- Allow browsing courses by Course Number and Title
- Add YAOUS to allow/disallow browsing by Instructor.
- Minor code cleanup

Signed-off-by: Kyle Huckins <khuckins@catalyte.io>
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>

Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.course-materials-module.sql
Open-ILS/src/templates/opac/course_browse.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/course_search/qtype_selector.tt2

index c252552..8479fab 100644 (file)
@@ -157,6 +157,7 @@ sub load {
     return $self->load_record if $path =~ m|opac/record/\d|;
     return $self->load_cnbrowse if $path =~ m|opac/cnbrowse|;
     return $self->load_browse if $path =~ m|opac/browse|;
+    return $self->load_course_browse if $path =~ m|opac/course_browse|;
     return $self->load_course if $path =~ m|opac/course|;
 
     return $self->load_mylist_add if $path =~ m|opac/mylist/add|;
index 496c043..42160c1 100644 (file)
@@ -41,6 +41,165 @@ sub load_course {
     return Apache2::Const::OK;
 }
 
+sub load_course_browse {
+    my $self = shift;
+    my $cgi = $self->cgi;
+    my $ctx = $self->ctx;
+    my $e = $self->editor;
+
+    my $browse_results = [];
+
+    # Are we searching? Cool, let's generate some links
+    if ($cgi->param('bterm')) {
+        my $bterm = $cgi->param('bterm');
+        my $qtype = $cgi->param('qtype');
+        # Search term is optional. If it's empty, start at the
+        # beginning. Otherwise, center results on a match.
+        # Regardless, we're listing everything, so retrieve all.
+        my $results;
+        my $instructors;
+        if ($qtype eq 'instructor') {
+            $instructors = $e->json_query({
+                "from" => "acmcu",
+                "select" => {"acmcu" => [
+                    'id',
+                    'usr',
+                    'is_public'
+                ]},
+                # TODO: We need to support the chosen library as well...
+                "where" => {'+acmcu' => 'is_public'}
+            });
+            $results = $e->json_query({
+                "from" => "au",
+                "select" => {"au" => [
+                    'id',
+                    'pref_first_given_name',
+                    'first_given_name',
+                    'pref_second_given_name',
+                    'second_given_name',
+                    'pref_family_name',
+                    'family_name'
+                ]},
+                "order_by" => {'au' => ['pref_family_name', 'family_name']},
+                "where" => {'-and' => [{
+                    "id" => { "in" => {
+                        "from" => "acmcu",
+                        "select" => {
+                            "acmcu" => ['usr']
+                        },
+                        "where" => {'-and' => [
+                            {'+acmcu' => 'is_public'},
+                            {"course" => { "in" =>{
+                                "from" => "acmc",
+                                "select" => {
+                                    "acmc" => ['id']
+                                },
+                                "where" => {'-not' => [{'+acmc' => 'is_archived'}]}
+                            }}}
+                        ]}
+                    }}
+                }]}
+            });
+        } else {
+            $results = $e->json_query({
+                "from" => "acmc",
+                "select" => {"acmc" => [
+                    'id',
+                    'name',
+                    'course_number',
+                    'is_archived',
+                    'owning_lib'
+                ]},
+                "order_by" => {"acmc" => [$qtype]},
+                # TODO: We need to support the chosen library as well...
+                "where" => {'-not' => {'+acmc' => 'is_archived'}}
+            });
+        }
+        my $bterm_match = 0;
+        for my $result(@$results) {
+            my $value_exists = 0;
+            my $rqtype = $qtype;
+            my $entry = {
+                'value' => '',
+                'results_count' => 0,
+                'match' => 0
+            };
+
+            if ($qtype eq 'instructor') {
+                # Put together the name
+                my $name_str = '';
+                if ($result->{'pref_family_name'}) {
+                    $name_str = $result->{'pref_family_name'} . ", ";
+                } elsif ($result->{'family_name'}) {
+                    $name_str = $result->{'family_name'} . ", ";
+                }
+
+                if ($result->{'pref_first_given_name'}) {
+                    $name_str .= $result->{'pref_first_given_name'};
+                } elsif ($result->{'first_given_name'}) {
+                    $name_str .= $result->{'first_given_name'};
+                }
+
+                if ($result->{'pref_second_given_name'}) {
+                    $name_str .= " " . $result->{'pref_second_given_name'};
+                } elsif ($result->{'second_given_name'}) {
+                    $name_str .= " " . $result->{'second_given_name'};
+                }
+
+                $result->{$rqtype} = $name_str;
+
+                # Get an accurate count of matching courses
+                for my $instructor(@$instructors) {
+                    if ($instructor->{'usr'} eq $result->{'id'}) {
+                        $entry->{'results_count'} += 1;
+                        last;
+                    }
+                }
+            } else {
+                $entry->{'results_count'} += 1;
+            }
+
+            for my $existing_entry(@$browse_results) {
+                if ($existing_entry->{'value'} eq $result->{$rqtype} && $value_exists eq 0) {
+                    $value_exists = 1;
+                    $existing_entry->{'results_count'} += 1;
+                    last;
+                }
+            }
+
+            if ($value_exists eq 0) {
+                # For Name/Course Number browse queries...
+                if ($bterm_match eq 0) {
+                    if ($result->{$qtype} =~ m/^$bterm./ || $result->{$qtype} eq $bterm) {
+                        $bterm_match = 1;
+                        $entry->{'match'} = 1;
+                    }
+                }
+                $entry->{'value'} = $result->{$rqtype};
+                push @$browse_results, $entry;
+            }
+        }
+        # Feels a bit hacky, but we need the index of the matching entry
+        my $match_idx = 0;
+        if ($bterm_match) {
+            for my $i (0..$#$browse_results) {
+                if ($browse_results->[$i]->{'match'}) {
+                    $match_idx = $i;
+                    last;
+                }
+            }
+        }
+
+        for my $i(0..$#$browse_results) {
+            $browse_results->[$i]->{'browse_index'} = $i;
+        }
+        $ctx->{match_idx} = $match_idx;
+        $ctx->{browse_results} = $browse_results;
+    }
+
+    return Apache2::Const::OK;
+}
+
 sub load_cresults {
     my $self = shift;
     my %args = @_;
index d4e0d52..b934d09 100644 (file)
@@ -71,6 +71,21 @@ VALUES (
         'coust',
         'description'
     )
+), (
+    'circ',
+    'circ.course_materials_browse_by_instructor', 'bool',
+    oils_i18n_gettext(
+        'circ.course_materials_browse_by_instructor',
+        'Allow users to browse Courses by Instructor',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'circ.course_materials_browse_by_instructor',
+        'If enabled, the Org Unit will allow OPAC users to browse Courses by instructor name.'
+        'coust',
+        'description'
+    )
 );
 
 COMMIT;
diff --git a/Open-ILS/src/templates/opac/course_browse.tt2 b/Open-ILS/src/templates/opac/course_browse.tt2
new file mode 100644 (file)
index 0000000..5caabdc
--- /dev/null
@@ -0,0 +1,161 @@
+[%-
+
+    PROCESS "opac/parts/header.tt2";
+    PROCESS "opac/parts/misc_util.tt2";
+    PROCESS "opac/parts/org_selector.tt2";
+    WRAPPER "opac/parts/base.tt2";
+    INCLUDE "opac/parts/topnav.tt2";
+
+    ctx.page_title = l("Browse Courses");
+    blimit = CGI.param('blimit') || ctx.opac_hits_per_page || 10;
+    display_idx = CGI.param('didx') || ctx.match_idx || 0;
+
+    upper_limit = 0;
+    lower_limit = 0;
+    depart_list = ['blimit', 'bterm', 'bpivot'];
+    ctx.metalinks.push('<meta name="robots" content="noindex,follow">');
+%]
+
+<h2 class="sr-only">[% l('Course Browse') %]</h2>
+<div id="search-wrapper">
+  <div id="search-box">
+    <div id="search-box">
+      <span class="search_catalog_lbl"><a href="[% mkurl(ctx.opac_root _ '/home', {}, depart_list) %]">[% l('Basic Search') %]</a></span>
+      <span class="adv_search_catalog_lbl"><a href="[% mkurl(ctx.opac_root _ '/advanced', {}, depart_list) %]"
+        id="home_adv_search_link">[%l('Advanced Search')%]</a></span>
+      <span class="browse_the_catalog_lbl mobile_hide">[% l('Browse the Catalog') %]</span>
+      <span class="browse_the_catalog_lbl mobile_hide">[% l('Search Courses') %]</span>
+      <span class="browse_the_catalog_lbl mobile_hide">[% l('Browse for Courses') %]</span>
+      [% INCLUDE 'opac/parts/cart.tt2' %]
+    </div>
+  </div>
+</div>
+
+<div id="content-wrapper">
+  <div id="main-content">
+    <div id="browse-for-courses">
+      <div id="browse-controls" class='searchbar'>
+        <form method="get" onsubmit="$('browse-submit-spinner').className = ''; return true">
+          <input type="hidden" name="blimit"
+            value="[% blimit | html %]" />
+          [% control_qtype = INCLUDE "opac/parts/course_search/qtype_selector.tt2"
+            id="browse-search-class" browse_only=1 plural=1 %]
+
+          [% control_bterm = BLOCK %]
+            <input type="text" name="bterm" id="browse-term" 
+              aria-label="[% l('Browse term') %]"
+              value="[% CGI.param('bterm') | html %]">
+          [% END; #control_bterm BLOCK %]
+
+          [% control_locg = INCLUDE build_org_selector id='browse-context'
+            show_log_groups=1 arialabel=l('Select owning Library') %]
+
+          [% l('Browse for [_1] starting with [_2] in [_3]',
+              control_qtype, control_bterm, control_locg) %]
+
+          <input id='search-submit-go' type="submit" value="[% l('Browse') %]"
+            alt="[% l('Browse') %]" class='opac-button' />
+          <img id="browse-submit-spinner" class="hidden"
+            style="width: 16px; height: 16px;"
+            alt="[% l('Search in progress icon') %]"
+            src="[% ctx.media_prefix %]/opac/images/progressbar_green.gif[% ctx.cache_key %]" />
+        </form>
+      </div>
+
+      [% BLOCK browse_pager %]
+        [%
+            pivot_lower = CGI.param('didx') - blimit;
+            pivot_higher = CGI.param('didx') + blimit;
+            IF pivot_lower < 0;
+                pivot_lower = 0;
+            END;
+            IF CGI.param('didx') <= 0;
+                lower_limit = 1;
+            ELSE;
+                lower_limit = 0;
+            END;
+
+            IF pivot_higher > ctx.browse_results.size;
+                pivot_higher = ctx.browse_results.size - (blimit + 1);
+                upper_limit = 1;
+            ELSE;
+                upper_limit = 0;
+            END;
+        %]
+        [% IF ctx.browse_results.size >= blimit %]
+        <div class="browse-pager">
+            [% IF lower_limit == 0 %]
+            <a class="opac-button" href="[% mkurl('', {didx => pivot_lower}) %]" 
+              onclick="$('browse-pager-spinner-[% id %]').className = '';"
+            >&larr; [%l ('Back') %]</a>
+            [% END %]
+            [% IF upper_limit == 0 %]
+            <a class="opac-button" href="[% mkurl('', {didx => pivot_higher}) %]"
+              onclick="$('browse-pager-spinner-[% id %]').className = '';"
+            >[%l ('Next') %] &rarr;</a>
+            [% END %]
+          <img id="browse-pager-spinner-[% id %]" 
+            src="[% ctx.media_prefix %]/opac/images/progressbar_green.gif[% ctx.cache_key %]"
+            class="hidden" style="width: 16px; height: 16px;" 
+            alt="[% l('Search in progress icon') %]" />
+        </div>
+        [% END %]
+      [% END %]
+
+      [% PROCESS browse_pager id=0 %]
+
+      <div id="browse-results">
+        [% IF ctx.browse_error %]
+          <span class="browse-error">
+            [% l("An error occurred browsing records. " _
+              "Please try again in a moment or report the issue " _
+              "to library staff.") %]
+          </span>
+        [% ELSE %]
+          [% IF ctx.browse_leading_article_warning %]
+            <div class="browse-leading-article-warning">
+              [% l("Your browse term seems to begin with an article (a, an, the). You might get better results by omitting the article.") %]
+              [% IF ctx.browse_leading_article_alternative %]
+                <p>[% alternative_link = BLOCK %]
+                  <a href="[% mkurl('', {bterm => ctx.browse_leading_article_alternative}, ['bpivot']) %]">[% ctx.browse_leading_article_alternative | html %]</a>
+                [%-  END; # alternative_link BLOCK
+                l("Did you mean [_1]?", alternative_link);
+              END # IF %]
+              </p>
+            </div>
+          [% END # IF browse leading article warning %]
+
+          <ul class="browse-result-list">
+          [% FOR result IN ctx.browse_results %]
+            [% IF result.browse_index >= CGI.param('didx') &&
+              result.browse_index <= (CGI.param('didx') + blimit - 1) %]
+            <li class="browse-result">
+              <span class="browse-result-value[% result.match == 1 ? ' browse-result-best-match' : '' %]">
+                [% IF result.results_count > 0 %]
+                  <a href="[% mkurl(ctx.opac_root _ '/course/results', {
+                    'query' => result.value, 'bool' => 'and'}
+                  )%]">[% result.value | html %]</a>
+                  <span class="browse-result-sources">([%
+                    IF result.accurate == 'f';
+                      l("At least"); " ";
+                    END; #result.accurate IF
+                  result.results_count %])</span>
+                [% ELSE %]
+                  [% result.value | html %]
+                [% END; #result.sources IF %]
+              </span>
+            </li>
+            [% END %]
+          [% END; #result in browse_results %]
+          </ul>
+        [% END; #browse error ELSE %]
+      </div>
+
+      [% PROCESS browse_pager id=1 %]
+    </div>
+
+    <div class="common-full-pad"></div>
+  </div>
+</div>
+
+[% END %]
\ No newline at end of file
index a9770b1..279b1c8 100644 (file)
@@ -1,8 +1,17 @@
-[%  query_types = [
+[% query_types = [
     {value => "name", label => l("Title"), plural_label => l("Titles"), browse => 1},
-    {value => "instructor", label => l("Instructor"), plural_label => l('Instructors')},
-    {value => "course_number", label => l("Course Number")}
+    {value => "course_number", label => l("Course Number"), plural_label => l('Course Numbers'), browse => 1}
 ];
+
+IF ctx.get_org_setting(
+    CGI.param('locg'), 'circ.course_materials_browse_by_instructor');
+query_types.push({
+    value => "instructor", 
+    label => l("Instructor"),
+    plural_label => l('Instructors'), 
+    browse => 1},
+);
+END;
 -%]
 <select name="[% name || 'qtype' %]"[% IF id; ' id="'; id ; '"' ; END -%]
     title="[% l('Select query type:') %]">