diff --git a/doajtest/testbook/public_site/subject_facet.yml b/doajtest/testbook/public_site/subject_facet.yml index 6a36629566..a5dece6f88 100644 --- a/doajtest/testbook/public_site/subject_facet.yml +++ b/doajtest/testbook/public_site/subject_facet.yml @@ -102,7 +102,7 @@ tests: Chemical Technology) results: - The subject and any parents are selected in the facet - - The search box clears + - The facet status hasn't changed - the search query is still visible in the search box and the tree is filtered - step: Enter another search of 3 or more characters, which will also return the values you selected in the previous step (e.g. "Bio") results: @@ -113,14 +113,17 @@ tests: - step: Select another subject from another part of the tree (e.g. Medicine) results: - All your subjects and any parents are selected in the facet - - The search box clears + - The facet status hasn't changed - the last search query is displayed in the input field and the tree is filtered - step: Enter your second search again (e.g. "Bio") - step: Unselect one of the subjects you had previously selected (e.g. Chemical Technology) results: - Your second subject is still present (e.g. Medicine) and any parents of the subject you unselected are still present (e.g. Technology) - - The search box clears + - The facet status hasn't changed - the last search query is displayed in the input field and the tree is filtered - step: Enter a long random string into the search box results: - A message is displayed "No subjects match your search" + - step: Remove a string from the search box + results: + - The facet shows all subjects with all levels folded diff --git a/portality/static/js/doaj.fieldrender.edges.js b/portality/static/js/doaj.fieldrender.edges.js index dc3138e4f8..09d172346d 100644 --- a/portality/static/js/doaj.fieldrender.edges.js +++ b/portality/static/js/doaj.fieldrender.edges.js @@ -1,6 +1,6 @@ $.extend(true, doaj, { - filters : { - noCharges : function() { + filters: { + noCharges: function () { return { id: "no_charges", display: "Without fees", @@ -17,26 +17,26 @@ $.extend(true, doaj, { } } }, - facets : { - inDOAJ : function() { + facets: { + inDOAJ: function () { return edges.newRefiningANDTermSelector({ id: "in_doaj", category: "facet", field: "admin.in_doaj", display: "In DOAJ?", deactivateThreshold: 1, - valueMap : { - 1 : "Yes", - 0 : "No", + valueMap: { + 1: "Yes", + 0: "No", true: "Yes", false: "No" }, - parseSelectedValueString: function(val) { + parseSelectedValueString: function (val) { // this is needed because ES7 doesn't understand "1" or `1` to be `true`, so // we convert the string value of the aggregation back to a boolean return val === "1" }, - filterToAggValue : function(val) { + filterToAggValue: function (val) { return val === true ? 1 : 0; }, renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ @@ -49,15 +49,15 @@ $.extend(true, doaj, { }) }, - openOrClosed: function() { + openOrClosed: function () { return edges.newRefiningANDTermSelector({ id: "application_type", category: "facet", field: "index.application_type.exact", display: "Open or closed?", - deactivateThreshold : 1, + deactivateThreshold: 1, orderDir: "asc", - valueMap : { + valueMap: { "finished application/update": "Closed", "update request": "Open", "new application": "Open" @@ -72,7 +72,7 @@ $.extend(true, doaj, { }) }, - applicationStatus : function() { + applicationStatus: function () { return edges.newRefiningANDTermSelector({ id: "application_status", category: "facet", @@ -89,13 +89,13 @@ $.extend(true, doaj, { }) }) }, - hasEditorGroup : function() { + hasEditorGroup: function () { return edges.newRefiningANDTermSelector({ id: "has_editor_group", category: "facet", field: "index.has_editor_group.exact", display: "Has editor group?", - deactivateThreshold : 1, + deactivateThreshold: 1, renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ controls: true, open: false, @@ -105,7 +105,7 @@ $.extend(true, doaj, { }) }) }, - hasEditor : function() { + hasEditor: function () { return edges.newRefiningANDTermSelector({ id: "has_editor", category: "facet", @@ -121,7 +121,7 @@ $.extend(true, doaj, { }) }) }, - editorGroup : function() { + editorGroup: function () { return edges.newRefiningANDTermSelector({ id: "editor_group", category: "facet", @@ -137,7 +137,7 @@ $.extend(true, doaj, { }) }) }, - editor : function() { + editor: function () { return edges.newRefiningANDTermSelector({ id: "editor", category: "facet", @@ -153,7 +153,7 @@ $.extend(true, doaj, { }) }) }, - hasAPC : function() { + hasAPC: function () { return edges.newRefiningANDTermSelector({ id: "author_pays", category: "facet", @@ -169,7 +169,7 @@ $.extend(true, doaj, { }) }) }, - classification : function() { + classification: function () { return edges.newRefiningANDTermSelector({ id: "classification", category: "facet", @@ -185,7 +185,7 @@ $.extend(true, doaj, { }) }) }, - language : function() { + language: function () { return edges.newRefiningANDTermSelector({ id: "language", category: "facet", @@ -201,7 +201,7 @@ $.extend(true, doaj, { }) }) }, - countryPublisher : function() { + countryPublisher: function () { return edges.newRefiningANDTermSelector({ id: "country_publisher", category: "facet", @@ -217,7 +217,7 @@ $.extend(true, doaj, { }) }) }, - subject : function() { + subject: function () { return edges.newRefiningANDTermSelector({ id: "subject", category: "facet", @@ -233,7 +233,7 @@ $.extend(true, doaj, { }) }) }, - publisher : function() { + publisher: function () { return edges.newRefiningANDTermSelector({ id: "publisher", category: "facet", @@ -249,7 +249,7 @@ $.extend(true, doaj, { }) }) }, - journalLicence : function() { + journalLicence: function () { return edges.newRefiningANDTermSelector({ id: "journal_license", category: "facet", @@ -267,29 +267,29 @@ $.extend(true, doaj, { } }, - valueMaps : { + valueMaps: { // This must be updated in line with the list in formcontext/choices.py - applicationStatus : { - 'update_request' : 'Update Request', - 'revisions_required' : 'Revisions Required', - 'pending' : 'Pending', - 'in progress' : 'In Progress', - 'completed' : 'Completed', - 'on hold' : 'On Hold', - 'ready' : 'Ready', - 'rejected' : 'Rejected', - 'accepted' : 'Accepted', + applicationStatus: { + 'update_request': 'Update Request', + 'revisions_required': 'Revisions Required', + 'pending': 'Pending', + 'in progress': 'In Progress', + 'completed': 'Completed', + 'on hold': 'On Hold', + 'ready': 'Ready', + 'rejected': 'Rejected', + 'accepted': 'Accepted', 'post_submission_review': "Autochecking", }, - adminStatusMap: function(value) { + adminStatusMap: function (value) { if (doaj.valueMaps.applicationStatus.hasOwnProperty(value)) { return doaj.valueMaps.applicationStatus[value]; } return value; }, - displayYearPeriod : function(params) { + displayYearPeriod: function (params) { var from = params.from; var to = params.to; var field = params.field; @@ -297,7 +297,7 @@ $.extend(true, doaj, { return {to: to, toType: "lt", from: from, fromType: "gte", display: display} }, - displayYearMonthPeriod : function(params) { + displayYearMonthPeriod: function (params) { var from = params.from; var to = params.to; var field = params.field; @@ -307,8 +307,9 @@ $.extend(true, doaj, { return {to: to, toType: "lt", from: from, fromType: "gte", display: display} }, - schemaCodeToNameClosure : function(tree) { + schemaCodeToNameClosure: function (tree) { var nameMap = {}; + function recurse(ctx) { for (var i = 0; i < ctx.length; i++) { var child = ctx[i]; @@ -319,9 +320,10 @@ $.extend(true, doaj, { } } } + recurse(tree); - return function(code) { + return function (code) { var name = nameMap[code]; if (name) { return name; @@ -330,7 +332,7 @@ $.extend(true, doaj, { } }, - countFormat : edges.numFormat({ + countFormat: edges.numFormat({ thousandsSeparator: "," }), @@ -338,8 +340,8 @@ $.extend(true, doaj, { zeroPadding: 2 }) }, - components : { - pager : function(id, category) { + components: { + pager: function (id, category) { return edges.newPager({ id: id, category: category, @@ -351,18 +353,18 @@ $.extend(true, doaj, { }) }, - searchingNotification : function() { + searchingNotification: function () { return edges.newSearchingNotification({ id: "searching-notification", category: "searching-notification", finishedEvent: "edges:post-render", - renderer : doaj.renderers.newSearchingNotificationRenderer({ + renderer: doaj.renderers.newSearchingNotificationRenderer({ scrollOnSearch: true }) }) }, - subjectBrowser : function(params) { + subjectBrowser: function (params) { var tree = params.tree; var hideEmpty = edges.getParam(params.hideEmpty, false); @@ -370,7 +372,7 @@ $.extend(true, doaj, { id: "subject", category: "facet", field: "index.schema_codes_tree.exact", - tree: function(tree) { + tree: function (tree) { function recurse(ctx) { var displayTree = []; for (var i = 0; i < ctx.length; i++) { @@ -386,11 +388,12 @@ $.extend(true, doaj, { displayTree.sort((a, b) => a.display > b.display ? 1 : -1); return displayTree; } + return recurse(tree); }(tree), pruneTree: true, size: 9999, - nodeMatch: function(node, match_list) { + nodeMatch: function (node, match_list) { for (var i = 0; i < match_list.length; i++) { var m = match_list[i]; if (node.value === m.key) { @@ -399,10 +402,10 @@ $.extend(true, doaj, { } return -1; }, - filterMatch: function(node, selected) { + filterMatch: function (node, selected) { return $.inArray(node.value, selected) > -1; }, - nodeIndex : function(node) { + nodeIndex: function (node) { return node.display.toLowerCase(); }, renderer: doaj.renderers.newSubjectBrowser({ @@ -415,7 +418,7 @@ $.extend(true, doaj, { } }, - templates : { + templates: { newPublicSearch: function (params) { return edges.instantiate(doaj.templates.PublicSearch, params, edges.newTemplate); }, @@ -543,7 +546,7 @@ $.extend(true, doaj, { } }, - renderers : { + renderers: { newSearchingNotificationRenderer: function (params) { return edges.instantiate(doaj.renderers.SearchingNotificationRenderer, params, edges.newRenderer); }, @@ -584,7 +587,7 @@ $.extend(true, doaj, { }, { duration: 1000, - always: function() { + always: function () { $(idSelector).remove(); } } @@ -593,10 +596,10 @@ $.extend(true, doaj, { } }, - newSubjectBrowser : function(params) { + newSubjectBrowser: function (params) { return edges.instantiate(doaj.renderers.SubjectBrowser, params, edges.newRenderer); }, - SubjectBrowser : function(params) { + SubjectBrowser: function (params) { this.title = edges.getParam(params.title, ""); this.selectMode = edges.getParam(params.selectMode, "multiple"); @@ -611,9 +614,17 @@ $.extend(true, doaj, { this.namespace = "doaj-subject-browser"; + this.viewWindowScrollOffset = 0; this.lastScroll = 0; + this.lastSearch = edges.getParam(params.lastSearch, null); + this.lastClickedEl = edges.getParam(params.lastClickedEl, null); + + this.init = function(component) { + edges.newRenderer().init.call(this, component); + component.edge.context.on("edges:pre-reset", edges.eventClosure(this, "reset")); + } - this.draw = function() { + this.draw = function () { // for convenient short references ... var st = this.component.syncTree; var namespace = this.namespace; @@ -643,7 +654,12 @@ $.extend(true, doaj, { var frag = '

\
\ \ - \ + \ \
'; @@ -655,11 +671,19 @@ $.extend(true, doaj, { this.component.context.html(frag); feather.replace(); + if (this.lastSearch) { + var searchSelector = edges.css_id_selector(namespace, "search", this); + this.filterSubjects($(searchSelector)); + } + + // trigger all the post-render set-up functions this.setUIOpen(); var mainListSelector = edges.css_id_selector(namespace, "main", this); - this.component.jq(mainListSelector).scrollTop(this.lastScroll); + var filterSelector = edges.css_id_selector(this.namespace, "filtered", this); + var selector = this.lastSearch ? filterSelector : mainListSelector; + this.component.jq(selector).scrollTop(this.lastScroll); var checkboxSelector = edges.css_class_selector(namespace, "selector", this); edges.on(checkboxSelector, "change", this, "filterToggle"); @@ -671,7 +695,14 @@ $.extend(true, doaj, { edges.on(searchSelector, "keyup", this, "filterSubjects"); }; - this._renderTree = function(params) { + this.reset = function(edge) { + this.lastSearch = null; + this.viewWindowScrollOffset = 0; + this.lastClickedEl = null; + this.lastScroll = 0; + } + + this._renderTree = function (params) { var st = edges.getParam(params.tree, []); var selectedPathOnly = edges.getParam(params.selectedPathOnly, true); var showOneLevel = edges.getParam(params.showOneLevel, true); @@ -759,7 +790,7 @@ $.extend(true, doaj, { rFrag += entryFrag; rFrag += ''; } - return {frag : rFrag, anySelected: anySelected}; + return {frag: rFrag, anySelected: anySelected}; } return recurse(st); @@ -777,17 +808,20 @@ $.extend(true, doaj, { results.addClass("in").attr("aria-expanded", "true").css({"height": ""}); toggle.removeClass("collapsed").attr("aria-expanded", "true"); } else { - results.removeClass("in").attr("aria-expanded", "false").css({"height" : "0px"}); + results.removeClass("in").attr("aria-expanded", "false").css({"height": "0px"}); toggle.addClass("collapsed").attr("aria-expanded", "false"); } }; - this.filterToggle = function(element) { + this.filterToggle = function (element) { var mainListSelector = edges.css_id_selector(this.namespace, "main", this); - this.lastScroll = this.component.jq(mainListSelector).scrollTop(); + var filterSelector = edges.css_id_selector(this.namespace, "filtered", this); + this.lastScroll = this.lastSearch ? this.component.jq(filterSelector).scrollTop() : this.component.jq(mainListSelector).scrollTop(); var el = this.component.jq(element); - // var filter_id = this.component.jq(element).attr("id"); var checked = el.is(":checked"); + this.lastClickedEl = el[0].id; + let offset = this.lastSearch ? this.component.jq(filterSelector).offset().top : this.component.jq(mainListSelector).offset().top; + this.viewWindowScrollOffset = el.offset().top - offset; var value = el.attr("data-value"); if (checked) { this.component.addFilter({value: value}); @@ -801,7 +835,47 @@ $.extend(true, doaj, { this.setUIOpen(); }; - this.filterSubjects = function(element) { + this._findParentObject = function(st, value) { + // Iterate through the array to find the object with children containing the lastClickedEl value + for (const obj of st) { + if (obj.children && obj.children.some(child => child.value === value)) { + return obj; + } + } + return null; // If no parent object is found + } + + this._findRenderedElement = function(st, value) { + // Step 1: Find HTML element with id=lastClickedEl + const element = document.getElementById(value); + + // Step 2: If it exists, return the element + if (element) { + return element; + } + + // Step 3: If it doesn't exist, find the parent in the st array + const parentObject = this._findParentObject(st, value); + + // Step 4: If no more parents (no elements found), return null + if (!parentObject) { + return null; + } + + // Step 5: Repeat this algorithm for the value of the found parent + return this._findRenderedElement(st, parentObject.value); + } + + this.scrollView = function (view) { + var browser = view[0]; + var st = this.component.syncTree; + var elemToScroll = this._findRenderedElement(st, this.lastClickedEl); + if (elemToScroll) { + browser.scrollTop = elemToScroll.offsetTop - browser.offsetTop - this.viewWindowScrollOffset; + } + } + + this.filterSubjects = function (element) { var st = this.component.syncTree; var term = $(element).val(); var that = this; @@ -815,6 +889,10 @@ $.extend(true, doaj, { filterEl.html(""); filterEl.hide(); mainEl.show(); + this.lastSearch = null; + if (this.lastClickedEl) { + this.scrollView(mainEl); + } return; } if (term.length < 3) { @@ -823,6 +901,7 @@ $.extend(true, doaj, { mainEl.hide(); return; } + this.lastSearch = term; term = term.toLowerCase(); function entryMatch(entry) { @@ -831,7 +910,7 @@ $.extend(true, doaj, { } var matchTerm = entry.index; - var includes = matchTerm.includes(term); + var includes = matchTerm.includes(term); if (includes) { var idx = matchTerm.indexOf(term); var display = entry.display; @@ -866,12 +945,20 @@ $.extend(true, doaj, { var filtered = recurse(st); if (filtered.length > 0) { - var displayReport = this._renderTree({tree: filtered, selectedPathOnly: false, showOneLevel: false}); + var displayReport = this._renderTree({ + tree: filtered, + selectedPathOnly: false, + showOneLevel: false + }); filterEl.html(displayReport.frag); mainEl.hide(); filterEl.show(); + if (this.lastClickedEl) { + this.scrollView(filterEl); + } + var checkboxSelector = edges.css_class_selector(this.namespace, "selector", this); edges.on(checkboxSelector, "change", this, "filterToggle"); } else { @@ -1352,7 +1439,7 @@ $.extend(true, doaj, { ////////////////////////////////////////////////////// // functions for setting UI values - this.toggleShare = function(element) { + this.toggleShare = function (element) { var shareUrlSelector = edges.css_class_selector(this.namespace, "share-url", this); var textarea = this.component.jq(shareUrlSelector); @@ -1368,7 +1455,7 @@ $.extend(true, doaj, { } }; - this.toggleShorten = function(element) { + this.toggleShorten = function (element) { if (!this.component.shortUrl) { var callback = edges.objClosure(this, "updateShortUrl"); this.component.generateShortUrl(callback); @@ -1377,7 +1464,7 @@ $.extend(true, doaj, { } }; - this.updateShortUrl = function() { + this.updateShortUrl = function () { var shareUrlSelector = edges.css_class_selector(this.namespace, "share-url", this); var shortenSelector = edges.css_class_selector(this.namespace, "shorten-url", this); var textarea = this.component.jq(shareUrlSelector); @@ -1678,7 +1765,7 @@ $.extend(true, doaj, { } }; - this.filterToggle = function(element) { + this.filterToggle = function (element) { var filter_id = this.component.jq(element).attr("id"); var checked = this.component.jq(element).is(":checked"); if (checked) { @@ -1892,12 +1979,12 @@ $.extend(true, doaj, { //} //results.hide(); - results.removeClass("in").attr("aria-expanded", "false").css({"height" : "0px"}); + results.removeClass("in").attr("aria-expanded", "false").css({"height": "0px"}); toggle.addClass("collapsed").attr("aria-expanded", "false"); } }; - this.filterToggle = function(element) { + this.filterToggle = function (element) { var term = this.component.jq(element).attr("data-key"); var checked = this.component.jq(element).is(":checked"); if (checked) { @@ -2154,7 +2241,7 @@ $.extend(true, doaj, { //} //results.hide(); - results.removeClass("in").attr("aria-expanded", "false").css({"height" : "0px"}); + results.removeClass("in").attr("aria-expanded", "false").css({"height": "0px"}); toggle.addClass("collapsed").attr("aria-expanded", "false"); } }; @@ -2162,7 +2249,7 @@ $.extend(true, doaj, { ///////////////////////////////////////////////////// // event handlers - this.filterToggle = function(element) { + this.filterToggle = function (element) { var gte = this.component.jq(element).attr("data-gte"); var lt = this.component.jq(element).attr("data-lt"); var checked = this.component.jq(element).is(":checked"); @@ -2375,7 +2462,7 @@ $.extend(true, doaj, { this.component.removeFilter(bool, ft, field, value); }; - this.clearFilters = function() { + this.clearFilters = function () { this.component.clearSearch(); } }, @@ -2455,16 +2542,15 @@ $.extend(true, doaj, { }; }, - newPublicSearchResultRenderer : function(params) { + newPublicSearchResultRenderer: function (params) { return edges.instantiate(doaj.renderers.PublicSearchResultRenderer, params, edges.newRenderer); }, - PublicSearchResultRenderer : function(params) { + PublicSearchResultRenderer: function (params) { this.widget = params.widget; if (params.doaj_url) { this.doaj_url = params.doaj_url; - } - else { + } else { this.doaj_url = "" } @@ -2473,11 +2559,11 @@ $.extend(true, doaj, { this.namespace = "doaj-public-search"; this.selector = edges.getParam(params.selector, null) - this.currentQueryString = ""; + this.currentQueryString = ""; this.draw = function () { - if (this.component.edge.currentQuery){ + if (this.component.edge.currentQuery) { let qs = this.component.edge.currentQuery.getQueryString(); if (qs) { this.currentQueryString = qs.queryString || ""; @@ -2515,7 +2601,7 @@ $.extend(true, doaj, { edges.on(abstractAction, "click", this, "toggleAbstract"); }; - this.toggleAbstract = function(element) { + this.toggleAbstract = function (element) { var el = $(element); var abstractText = edges.css_class_selector(this.namespace, "abstracttext", this); var at = this.component.jq(abstractText).filter('[rel="' + el.attr("rel") + '"]'); @@ -2529,7 +2615,7 @@ $.extend(true, doaj, { } }; - this._renderResult = function(resultobj) { + this._renderResult = function (resultobj) { if (resultobj.bibjson && resultobj.bibjson.journal) { // it is an article return this._renderPublicArticle(resultobj); @@ -2539,15 +2625,14 @@ $.extend(true, doaj, { } }; - this._renderPublicJournal = function(resultobj) { + this._renderPublicJournal = function (resultobj) { var seal = ""; if (edges.objVal("admin.seal", resultobj, false)) { seal = '' - if (this.widget){ + if (this.widget) { seal += ' DOAJ Seal' - } - else { + } else { seal += '\ \ \ @@ -2642,7 +2727,7 @@ $.extend(true, doaj, { var lic = resultobj.bibjson.license[i]; var license_url = lic.url || terms_url; licenses += '' + edges.escapeHtml(lic.type) + ''; - if (i !== (resultobj.bibjson.license.length-1)) { + if (i !== (resultobj.bibjson.license.length - 1)) { licenses += ', '; } } @@ -2666,7 +2751,7 @@ $.extend(true, doaj, { let data = ""; if (actSettings.data) { let dataAttrs = Object.keys(actSettings.data); - for(let j = 0; j < dataAttrs.length; j++) { + for (let j = 0; j < dataAttrs.length; j++) { data += " data-" + dataAttrs[j] + "=" + actSettings.data[dataAttrs[j]]; } } @@ -2689,10 +2774,9 @@ $.extend(true, doaj, { \ ' + edges.escapeHtml(resultobj.bibjson.title) + '\ ' - if (this.widget){ + if (this.widget) { frag += 'link icon' - } - else { + } else { frag += '' } @@ -2701,17 +2785,16 @@ $.extend(true, doaj, { if (resultobj.bibjson.ref && resultobj.bibjson.ref.journal) { externalLink = '
  • Website '; - if (this.widget){ + if (this.widget) { externalLink += 'external-link icon' - } - else { + } else { externalLink += '' } externalLink += '
  • '; } - frag +='\ + frag += '\ \ ' + subtitle + '\ \ @@ -2752,7 +2835,7 @@ $.extend(true, doaj, { return frag; }; - this._renderPublicArticle = function(resultobj) { + this._renderPublicArticle = function (resultobj) { var journal = resultobj.bibjson.journal ? resultobj.bibjson.journal.title : ""; var date = ""; @@ -2789,7 +2872,7 @@ $.extend(true, doaj, { var keywords = ""; if (edges.hasProp(resultobj, "bibjson.keywords") && resultobj.bibjson.keywords.length > 0) { keywords = '

    Article keywords

    '; } @@ -2809,10 +2892,9 @@ $.extend(true, doaj, { abstract = '\ @@ -2895,10 +2977,9 @@ $.extend(true, doaj, {