diff --git a/themes/finc-accessibility/js/account_ajax.js b/themes/finc-accessibility/js/account_ajax.js new file mode 100644 index 0000000000000000000000000000000000000000..9d657d9f5d95421a30dd8782a76fa4205947137f --- /dev/null +++ b/themes/finc-accessibility/js/account_ajax.js @@ -0,0 +1,256 @@ +/*global userIsLoggedIn, VuFind */ +VuFind.register('account', function Account() { + // Retrieved statuses + var LOADING = -1 * Math.PI; // waiting for request + var MISSING = -2 * Math.PI; // no data available + var INACTIVE = -3 * Math.PI; // status element missing + var _statuses = {}; + + // Account Icons + var ICON_LEVELS = { + "NONE": 0, + "GOOD": 1, + "WARNING": 2, + "DANGER": 3 + }; + var _accountIcons = {}; + _accountIcons[ICON_LEVELS.NONE] = "fa fa-user-circle"; + _accountIcons[ICON_LEVELS.GOOD] = "fa fa-bell text-success"; + _accountIcons[ICON_LEVELS.WARNING] = "fa fa-bell text-warning"; + _accountIcons[ICON_LEVELS.DANGER] = "fa fa-exclamation-triangle text-danger"; + + var _submodules = []; + + var _sessionDataPrefix = "vf-account-status-"; + var _save = function _save(module) { + sessionStorage.setItem( + _sessionDataPrefix + module, + JSON.stringify(_statuses[module]) + ); + }; + + // Clearing save forces AJAX update next page load + var clearCache = function clearCache(name) { + if (typeof name === "undefined") { + for (var sub in _submodules) { + if (Object.prototype.hasOwnProperty.call(_submodules, sub)) { + clearCache(sub); + } + } + } else { + sessionStorage.removeItem(_sessionDataPrefix + name); + } + }; + + var _getStatus = function _getStatus(module) { + return (typeof _statuses[module] === "undefined") ? LOADING : _statuses[module]; + }; + + var _render = function _render() { + var accountStatus = ICON_LEVELS.NONE; + for (var sub in _submodules) { + if (Object.prototype.hasOwnProperty.call(_submodules, sub)) { + var $element = $(_submodules[sub].selector); + if (!$element) { + _statuses[sub] = INACTIVE; + continue; + } + var status = _getStatus(sub); + if (status === MISSING) { + $element.addClass('hidden'); + } else { + $element.removeClass('hidden'); + if (status === LOADING) { + $element.html('<i class="fa fa-spin fa-spinner"></i>'); + } else { + var moduleStatus = _submodules[sub].render($element, _statuses[sub], ICON_LEVELS); + if (moduleStatus > accountStatus) { + accountStatus = moduleStatus; + } + } + } + } + } + $("#account-icon").attr("class", _accountIcons[accountStatus]); + if (accountStatus > ICON_LEVELS.NONE) { + $("#account-icon") + .attr("data-toggle", "tooltip") + .attr("data-placement", "bottom") + .attr("title", VuFind.translate("account_has_alerts")) + ; + } else { + $("#account-icon").tooltip("destroy"); + } + }; + var _ajaxLookup = function _ajaxLookup(module) { + $.ajax({ + url: VuFind.path + '/AJAX/JSON?method=' + _submodules[module].ajaxMethod, + dataType: 'json' + }) + .done(function ajaxLookupDone(response) { + _statuses[module] = response.data; + }) + .fail(function ajaxLookupFail() { + _statuses[module] = MISSING; + }) + .always(function ajaxLookupAlways() { + _save(module); + _render(); + }); + }; + + var _load = function _load(module) { + var $element = $(_submodules[module].selector); + if (!$element) { + _statuses[module] = INACTIVE; + } else { + var json = sessionStorage.getItem(_sessionDataPrefix + module); + var session = typeof json === "undefined" ? null : JSON.parse(json); + if ( + session === null || + session === LOADING || + session === MISSING + ) { + _statuses[module] = LOADING; + _ajaxLookup(module); + } else { + _statuses[module] = session; + } + _render(); + } + }; + + var init = function init() { + // Update information when certain actions are performed + $("#renewals").submit(function clearCheckedOut() { + clearCache("checkedOut"); + }); + $('#cancelHold, [name="placeHold"]').submit(function clearHolds() { + clearCache("holds"); + }); + $('#ILLRequestForm, #cancelILLRequest').submit(function clearHolds() { + clearCache("illRequests"); + }); + $('[name="placeStorageRetrievalRequest"], #cancelStorageRetrievalRequest').submit(function clearStorageRetrievals() { + clearCache("storageRetrievalRequests"); + }); + $("#library_card").change(function clearChangeLibraryCard() { + clearCache(/* all */); + }); + }; + + var register = function register(name, module) { + if (typeof _submodules[name] === "undefined") { + _submodules[name] = typeof module == 'function' ? module() : module; + } + var $el = $(_submodules[name].selector); + if ($el.length > 0) { + $el.removeClass("hidden"); + _statuses[name] = LOADING; + _load(name); + } else { + _statuses[name] = INACTIVE; + } + }; + + return { + init: init, + clearCache: clearCache, + // if user is logged out, clear cache instead of register + register: userIsLoggedIn ? register : clearCache + }; +}); + +$(document).ready(function registerAccountAjax() { + + VuFind.account.register("fines", { + selector: ".fines-status", + ajaxMethod: "getUserFines", + render: function render($element, status, ICON_LEVELS) { + if (status.value === 0) { + $element.addClass("hidden"); + return ICON_LEVELS.NONE; + } + $element.html('<span class="badge overdue">' + status.display + '</span>'); + return ICON_LEVELS.DANGER; + } + }); + + VuFind.account.register("checkedOut", { + selector: ".checkedout-status", + ajaxMethod: "getUserTransactions", + render: function render($element, status, ICON_LEVELS) { + var html = ''; + var level = ICON_LEVELS.NONE; + if (status.ok > 0) { + html += '<span class="badge ok" data-toggle="tooltip" title="' + VuFind.translate('Checked Out Items') + '">' + status.ok + '</span>'; + } + if (status.warn > 0) { + html += '<span class="badge warn" data-toggle="tooltip" title="' + VuFind.translate('renew_item_due_tooltip') + '">' + status.warn + '</span>'; + level = ICON_LEVELS.WARNING; + } + if (status.overdue > 0) { + html += '<span class="badge overdue" data-toggle="tooltip" title="' + VuFind.translate('renew_item_overdue_tooltip') + '">' + status.overdue + '</span>'; + level = ICON_LEVELS.DANGER; + } + $element.html(html); + $('[data-toggle="tooltip"]', $element).tooltip(); + return level; + } + }); + + VuFind.account.register("holds", { + selector: ".holds-status", + ajaxMethod: "getUserHolds", + render: function render($element, status, ICON_LEVELS) { + var level = ICON_LEVELS.NONE; + if (status.available > 0) { + $element.html('<i class="fa fa-bell text-success" data-toggle="tooltip" title="' + VuFind.translate('hold_available') + '"></i>'); + level = ICON_LEVELS.GOOD; + } else if (status.in_transit > 0) { + $element.html('<i class="fa fa-clock-o text-warning" data-toggle="tooltip" title="' + VuFind.translate('request_in_transit') + '"></i>'); + } else { + $element.addClass("holds-status hidden"); + } + $('[data-toggle="tooltip"]', $element).tooltip(); + return level; + } + }); + + VuFind.account.register("illRequests", { + selector: ".illrequests-status", + ajaxMethod: "getUserILLRequests", + render: function render($element, status, ICON_LEVELS) { + var level = ICON_LEVELS.NONE; + if (status.available > 0) { + $element.html('<i class="fa fa-bell text-success" data-toggle="tooltip" title="' + VuFind.translate('ill_request_available') + '"></i>'); + level = ICON_LEVELS.GOOD; + } else if (status.in_transit > 0) { + $element.html('<i class="fa fa-clock-o text-warning" data-toggle="tooltip" title="' + VuFind.translate('request_in_transit') + '"></i>'); + } else { + $element.addClass("holds-status hidden"); + } + $('[data-toggle="tooltip"]', $element).tooltip(); + return level; + } + }); + + VuFind.account.register("storageRetrievalRequests", { + selector: ".storageretrievalrequests-status", + ajaxMethod: "getUserStorageRetrievalRequests", + render: function render($element, status, ICON_LEVELS) { + var level = ICON_LEVELS.NONE; + if (status.available > 0) { + $element.html('<i class="fa fa-bell text-success" data-toggle="tooltip" title="' + VuFind.translate('storage_retrieval_request_available') + '"></i>'); + level = ICON_LEVELS.GOOD; + } else if (status.in_transit > 0) { + $element.html('<i class="fa fa-clock-o text-warning" data-toggle="tooltip" title="' + VuFind.translate('request_in_transit') + '"></i>'); + } else { + $element.addClass("holds-status hidden"); + } + $('[data-toggle="tooltip"]', $element).tooltip(); + return level; + } + }); + +}); diff --git a/themes/finc/js/hierarchyTree.js b/themes/finc/js/hierarchyTree.js index d3db7ac718c594542dc9ee4c75afa837c372bc56..a0afca2660ebf68789605be36946e3f171791a0a 100644 --- a/themes/finc/js/hierarchyTree.js +++ b/themes/finc/js/hierarchyTree.js @@ -212,6 +212,11 @@ $(document).ready(function hierarchyTreeReady() { scrollToClicked(); }) + + .on('after_open.jstree', function (e, data) { + $(".jstree-anchor").removeAttr("title"); + }) + .jstree({ plugins: ['search', 'types'], core: { diff --git a/themes/finc/scss/compiled.scss b/themes/finc/scss/compiled.scss index 5e7e39fd4fb2dfb0d9a5b422b2a691dc31b3e123..91c61145434bd4598a9d808516cd808adde03d9e 100644 --- a/themes/finc/scss/compiled.scss +++ b/themes/finc/scss/compiled.scss @@ -1846,12 +1846,28 @@ footer { .limit { margin-right: ($grid-gutter-width / 2); +} - @media only screen and (max-width: $screen-xs-max) { - float: right; - margin-bottom: 7px; - margin-right: 0; +.limit, +.sort { + margin-bottom: 5px; + + label { + line-height: normal; + margin-bottom: 0; } + + select { + margin-bottom: 5px; + } +} + +.search-controls-div { + display: inline-block; +} + +.search-sort { + display: block; } // limit the width of the select field if necessary - for more select details, see FORMS section above @@ -1859,6 +1875,43 @@ footer { max-width: 12em; } +@media only screen and (max-width: $screen-xs-max) { + .limit, + .sort { + float: left; + + button, + label, + select { + display: block; + } + + select { + margin-bottom: 0; + margin-right: .2rem; + } + } + + .limit { + margin-right: 1rem; + } + + // Place the refresh button near to the select box + .search-controls-div { + display: flex; + } + + // Force the label for floating the text left + .search-sort.text-right { + text-align: left; + } + + // Force the search-sort content to being floated left. Otherwise the refresh button is not visible during user zoom 200%. + .sort.right { + float: left !important; + } +} + //// Off-Canvas @media only screen and (max-width: $screen-sm-max) { .close-offcanvas { diff --git a/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml b/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml index 23ac31912b26e37d9c68fb16cd0984123776dc09..25c28d3b4118de527446d7216f83a413a90ea57c 100644 --- a/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml +++ b/themes/finc/templates/RecordDriver/SolrAI/result-list.phtml @@ -224,8 +224,7 @@ if ($cover): <?php if ($this->userlist()->getMode() !== 'disabled'): ?> <?php /* if ($this->permission()->allowDisplay('feature.Favorites')): */ ?> <?php /* Add to favorites; finc: keep Icon inside link - CK */ ?> - <a href="<?=$this->recordLink()->getActionUrl($this->driver, 'Save')?>" data-lightbox class="save-record result-link-label" data-id="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>" - title="<?=$this->transEsc('Add to favorites')?>"> + <a href="<?=$this->recordLink()->getActionUrl($this->driver, 'Save')?>" data-lightbox class="save-record result-link-label" data-id="<?=$this->escapeHtmlAttr($this->driver->getUniqueId())?>"> <i class="result-link-icon fa fa-fw fa-star" aria-hidden="true"></i> <?=$this->transEsc('Add to favorites')?> </a><br/> <?php elseif ($block = $this->permission()->getAlternateContent('feature.Favorites')): ?> diff --git a/themes/finc/templates/RecordTab/holdingsils/standard.phtml b/themes/finc/templates/RecordTab/holdingsils/standard.phtml index 561d2f01b1e944b7cbb1223556f6624f76eaedb6..5c1c993c792ce7817738e48ab2ff6f808a0f5156 100644 --- a/themes/finc/templates/RecordTab/holdingsils/standard.phtml +++ b/themes/finc/templates/RecordTab/holdingsils/standard.phtml @@ -47,7 +47,7 @@ <?php endif; ?> <?php /* finc-specific additional insert, newspaper orders via mail - #6096 - CK */ ?> <?php if (isset($holding['emailHoldLink']) && $holding['emailHoldLink']): ?> - <a class="<?= $checkEmailHold ? 'checkEmailHold ' : '' ?>placeEmailHold " data-lightbox href="<?= $this->recordLink()->getRequestUrl($holding['emailHoldLink']) ?>" title="<?= $this->transEsc($checkEmailHold ? "EmailHold::email_hold_check_text" : "EmailHold::email_hold_place_text") ?>"> + <a class="<?= $checkEmailHold ? 'checkEmailHold ' : '' ?>placeEmailHold " data-lightbox href="<?= $this->recordLink()->getRequestUrl($holding['emailHoldLink']) ?>"> <i class="fa fa-flag"></i> <?= $this->transEsc($checkEmailHold ? "EmailHold::email_hold_check_text" : "EmailHold::email_hold_place_text") ?> </a> <?php endif; ?> diff --git a/themes/finc/templates/header.phtml b/themes/finc/templates/header.phtml index b2019c53f1fe2c15e6e4d3b2e757cce73be8f037..fc370fe70a5bb8367eab7727d972fb0dd3969b42 100644 --- a/themes/finc/templates/header.phtml +++ b/themes/finc/templates/header.phtml @@ -102,21 +102,19 @@ <li class="language dropdown"> <form method="post" name="langForm" id="langForm"> <input type="hidden" name="mylang"/> - </form> - <a href="#" class="btn dropdown-toggle <?=(count($this->layout()->allLangs) == 2) ? ' hidden' : ''?>" data-toggle="dropdown" aria-controls="langmenu" aria-expanded="false"> <?=$this->transEsc("Language")?> <strong class="caret"></strong> </a> - - <ul id="langmenu" class="dropdown-menu <?=(count($this->layout()->allLangs) == 2) ? ' oneLanguage' : ''?>"> - <?php foreach ($this->layout()->allLangs as $langCode => $langName): ?> - <?php if ($langCode !== $this->layout()->userLang) : ?> - <li> - <a class="btn <?=(count($this->layout()->allLangs) == 2) ? ' btn-secondary' : ''?>" href="#" onClick="document.langForm.mylang.value='<?=$langCode?>';document.langForm.submit()"><?=$this->displayLanguageOption($langName)?></a> - </li> - <?php endif; ?> - <?php endforeach; ?> - </ul> + <ul id="langmenu" class="dropdown-menu <?=(count($this->layout()->allLangs) == 2) ? ' oneLanguage' : ''?>"> + <?php foreach ($this->layout()->allLangs as $langCode => $langName): ?> + <?php if ($langCode !== $this->layout()->userLang) : ?> + <li> + <button type="submit" class="btn <?=(count($this->layout()->allLangs) == 2) ? ' btn-secondary' : ''?>" href="#" onClick="document.langForm.mylang.value='<?=$langCode?>';document.langForm.submit()"><?=$this->displayLanguageOption($langName)?></button> + </li> + <?php endif; ?> + <?php endforeach; ?> + </ul> + </form> </li> <?php endif; ?> </ul> diff --git a/themes/finc/templates/record/cart-buttons.phtml b/themes/finc/templates/record/cart-buttons.phtml index 1e8ba70357f029b5abb411755a070380dec49d3e..edb7d83ea32988fbf3970619cf617a6174abcc5b 100644 --- a/themes/finc/templates/record/cart-buttons.phtml +++ b/themes/finc/templates/record/cart-buttons.phtml @@ -6,10 +6,10 @@ <span class="btn-bookbag-toggle" data-cart-id="<?=$this->escapeHtmlAttr($this->id)?>" data-cart-source="<?=$this->escapeHtmlAttr($this->source)?>"> <?php /* Make add-to/remove-from bookbag accessible for keyboard navigation - CK */ ?> <a class="cart-add hidden<?php if (!$cart->contains($cartId)): ?> correct<?php endif ?>" href="javascript:" tabindex="0"> - <i class="cart-link-icon fa fa-plus" aria-hidden="true" title="<?=$this->transEsc('Add to Book Bag')?>"></i><span class="cart-link-label"><?=$this->transEsc('Add to Book Bag')?></span> + <i class="cart-link-icon fa fa-plus" aria-hidden="true"></i><span class="cart-link-label"><?=$this->transEsc('Add to Book Bag')?></span> </a> <a class="cart-remove hidden<?php if ($cart->contains($cartId)): ?> correct<?php endif ?>" href="javascript:" tabindex="0"> - <i class="cart-link-icon fa fa-minus-circle" aria-hidden="true" title="<?=$this->transEsc('Remove from Book Bag')?>"></i> <span class="cart-link-label"><?=$this->transEsc('Remove from Book Bag')?></span> + <i class="cart-link-icon fa fa-minus-circle" aria-hidden="true"></i> <span class="cart-link-label"><?=$this->transEsc('Remove from Book Bag')?></span> </a> <noscript> <form method="post" name="addForm" action="<?=$this->url('cart-processor')?>"> diff --git a/themes/finc/templates/search/controls/limit.phtml b/themes/finc/templates/search/controls/limit.phtml index 9284b184791a9838154e3c8a3e277b6c4af9a76e..8c34fc5bc7c59e9b45cfec87ffa22e854c485ad9 100644 --- a/themes/finc/templates/search/controls/limit.phtml +++ b/themes/finc/templates/search/controls/limit.phtml @@ -1,16 +1,21 @@ <!-- finc: search - controls - limit --> +<?php /* Add div #18016 - HR */ ?> <?php $limitList = $this->params->getLimitList(); ?> <?php if (count($limitList) > 1): ?> <?php /* finc: DO NOT use class 'form-inline' as it messes up the select box */ ?> <form class="limit" action="<?=$this->currentPath() . $this->results->getUrlQuery()->setLimit(null)?>" method="post"> <label for="limit"><?=$this->transEsc('Results per page')?></label> - <?php /* finc: DO NOT use class 'form-control' as it messes up the select box */ ?> - <select id="limit" name="limit" class="jumpMenu"> - <?php foreach ($limitList as $limitVal => $limitData): ?> - <option value="<?=$this->escapeHtmlAttr($limitVal)?>" <?=$limitData['selected']? ' selected="selected" ':'' ?>><?=$this->escapeHtml($limitData['desc'])?></option> - <?php endforeach; ?> - </select> - <noscript><input type="submit" value="<?=$this->transEsc("Set")?>" /></noscript> + <div class="search-controls-div"> + <?php /* finc: DO NOT use class 'form-control' as it messes up the select box */ ?> + <select id="limit" name="limit"> + <?php foreach ($limitList as $limitVal => $limitData): ?> + <option value="<?=$this->escapeHtmlAttr($limitVal)?>" <?=$limitData['selected']? ' selected="selected" ':'' ?>><?=$this->escapeHtml($limitData['desc'])?></option> + <?php endforeach; ?> + </select> + <button type="submit" class="btn btn-primary" aria-label="<?=$this->transEsc("Set")?>"> + <i class="fa fa-refresh" aria-hidden="true"></i> + </button> + </div> </form> <?php endif; ?> <!-- finc: search - controls - limit - END --> diff --git a/themes/finc/templates/search/controls/sort.phtml b/themes/finc/templates/search/controls/sort.phtml index c8a8186475864d0fe27206dd387136eb5a82ea4f..557a995a4dc38021cd6812228a1263145acd9e0b 100644 --- a/themes/finc/templates/search/controls/sort.phtml +++ b/themes/finc/templates/search/controls/sort.phtml @@ -1,16 +1,21 @@ <!-- finc: search - controls - sort --> +<?php /* Add div #18016 - HR */ ?> <?php $list = $this->params->getSortList(); if (!empty($list)): ?> <?php /* finc: we use class 'text-right' */ ?> <form class="search-sort text-right" action="<?=$this->currentPath()?>" method="get" name="sort"> <?=$this->results->getUrlQuery()->asHiddenFields(['sort' => '/.*/']);?> <label for="sort_options_1"><?=$this->transEsc('Sort')?></label> - <?php /* finc: DO NOT use class 'form-control' as it messes up the select box */ ?> - <select id="sort_options_1" name="sort" class="jumpMenu"> - <?php foreach ($list as $sortType => $sortData): ?> - <option value="<?=$this->escapeHtmlAttr($sortType)?>" <?=$sortData['selected']?' selected="selected"':''?>><?=$this->transEsc($sortData['desc'])?></option> - <?php endforeach; ?> - </select> - <noscript><input type="submit" class="btn btn-primary" value="<?=$this->transEsc("Set")?>" /></noscript> + <div class="search-controls-div"> + <?php /* finc: DO NOT use class 'form-control' as it messes up the select box */ ?> + <select id="sort_options_1" name="sort"> + <?php foreach ($list as $sortType => $sortData): ?> + <option value="<?=$this->escapeHtmlAttr($sortType)?>" <?=$sortData['selected']?' selected="selected"':''?>><?=$this->transEsc($sortData['desc'])?></option> + <?php endforeach; ?> + </select> + <button type="submit" class="btn btn-primary" aria-label="<?=$this->transEsc("Set")?>"> + <i class="fa fa-refresh" aria-hidden="true"></i> + </button> + </div> </form> <?php endif; ?> <!-- finc: search - controls - sort - END --> diff --git a/themes/finc/templates/search/searchbox.phtml b/themes/finc/templates/search/searchbox.phtml index 0b308f0212f9d935458d87b116de2738ea5d8fd7..3e8963def57269b1389a5f9cf8727fabd3a67044 100644 --- a/themes/finc/templates/search/searchbox.phtml +++ b/themes/finc/templates/search/searchbox.phtml @@ -44,7 +44,7 @@ $hiddenFilterParams = $this->searchTabs()->getCurrentHiddenFilterParams($this->s <?php if (!empty($tabs)): ?></div><?php endif; ?> </div> <?php else: ?> - <form id="searchForm" class="searchForm navbar-form navbar-left flip" method="get" action="<?=$this->url($basicSearch)?>" name="searchForm" autocomplete="off"> + <form id="searchForm" class="searchForm navbar-form navbar-left flip" method="get" action="<?=$this->url($basicSearch)?>" name="searchForm" autocomplete="off" role="search"> <?= $this->context($this)->renderInContext('search/searchTabs', ['searchTabs' => $tabConfig['tabs']]); ?> <?php $placeholder = $this->searchbox()->getPlaceholderText($tabConfig['selected']['id'] ?? null); ?> <?php /* finc: keep "required" */ ?>