From be93fadccb0382c3fa4fd4f90121b84592cf9e42 Mon Sep 17 00:00:00 2001 From: Michel Drescher Date: Thu, 10 Sep 2020 11:15:11 +0100 Subject: [PATCH] New feature: Search projects --- src/client/css/styles.css | 85 ++++++++++++++++++++++---- src/client/index.js | 110 ++++++++++++++++++++-------------- src/client/js/radar/search.js | 49 +++++++++++++++ src/server/views/radar.pug | 4 +- src/server/views/search.pug | 10 ++++ 5 files changed, 199 insertions(+), 59 deletions(-) create mode 100644 src/client/js/radar/search.js create mode 100644 src/server/views/search.pug diff --git a/src/client/css/styles.css b/src/client/css/styles.css index 70d21df..52c6578 100644 --- a/src/client/css/styles.css +++ b/src/client/css/styles.css @@ -659,29 +659,41 @@ section.modal > div.modal-window > section.buttonbar { * * *******************************/ /* General layout for the filter list(s) */ -#radar section#filters { +#radar > #filters { margin: 0 4em; + display: flex; +} + +/* + * All other filters + */ +/* Other filters get all space and have a right margin */ +#radar > #filters > #other { + flex: 1 1 auto; + margin-right: 1em; } -#radar section#filters div.filter { + +/* Each filder ... */ +#radar > #filters > #other > div.filter { margin: 0.5em 0; display: flex; align-items: flex-start; } -#radar section#filters div.filter:first-child { +#radar > #filters > #other > .filter:first-child { margin-top: 0em; } -#radar section#filters div.filter:last-child { +#radar > #filters > #other > .filter:last-child { margin-bottom: 0em; } -/* Each filter ... */ -section#filters div.filter * { +/* Each filter child ... */ +#filters > #other > .filter * { vertical-align: top; } -section#filters div.filter > *:first-child { +#filters > #other > .filter > *:first-child { margin-right: 0.5em; } /* ... has a button, ... */ -section#filters div.filter button { +#filters > #other > div.filter button { font-family: 'Lato'; font-size: 0.8em; padding: 0.2em; @@ -689,25 +701,25 @@ section#filters div.filter button { flex: 0 0 auto; } /* ... an ops div, ... */ -section#filters div.filter div.ops { +#filters > #other > div.filter div.ops { flex: 0 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 0 0.2em; font-size: 0.8em; } -section#filters div.filter div.ops * { +#filters > #other > div.filter div.ops * { text-align: center; margin: auto; } /* ... and a noneditable list of tags. */ -section#filters div.filter div.tags { +#filters > #other > div.filter div.tags { font-family: 'Lato'; font-size: 0.8em; } /* Each individual tag */ -section#filters div.filter div.tags div.tag { +#filters > #other > div.filter div.tags div.tag { display: inline-block; border: 1px solid darkgray; padding: 0.1em 0.5em; @@ -723,3 +735,52 @@ form.form--filter-tags > ul > li:first-child { font-weight: bold; font-size: 1.2em; } + +/* + * Search + */ +/* Search area stays small and can shrink if necessary, but not grow */ +#filters > #search { + flex: 0 1 auto; +} +/* search text field needs styling */ +#filters > #search #search_term { +} +#filters > #search #search_term:focus { + outline: none; +} +/* ditto the cancel button */ +#filters > #search #search_clear { + background-color: white; + border: unset; + font-size: 16pt; + vertical-align: middle; + color: black; + line-height: 0%; +} +#filters > #search > #search_clear:focus { + outline: none; +} +/* search results list position is absolute so as to overlay the radar if very long */ +#filters > #search > #search_results { + position: absolute; + display: grid; + gap: 0 0; + border: 1px solid lightgray; +} +/* each search result element */ +#filters > #search > #search_results > button { + display: block; + margin: 0; + border: 0; + background-color: white; + text-align: left; + border-bottom: 1px dotted lightgray; + padding: 0.3em 0.5em; +} +#filters > #search > #search_results > button:last-child { + border-bottom: unset; +} +#filters > #search > #search_results > button:hover { + background-color: #eaeaea; +} diff --git a/src/client/index.js b/src/client/index.js index edb5023..87a13df 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -15,17 +15,18 @@ import { createUser, deleteUser, updateUsersDetails, - updateUsersPassword + updateUsersPassword, } from './js/admin/userActions' import { createRadar, updateRadar, deleteRadar, advanceRadar } from './js/admin/radarActions' import { createProject, deleteProject, updateProject, - importProjects + importProjects, } from './js/admin/projectActions' import { addClassification, addScore } from './js/admin/scoreAndClassify' import { getName } from '../common/datamodel/jrc-taxonomy' +import { searchProjects, clearProjects } from './js/radar/search.js' /**************************************************************** * * @@ -38,8 +39,8 @@ import { getName } from '../common/datamodel/jrc-taxonomy' // const radarButtons = document.querySelectorAll('.radar') if (radarButtons) { - radarButtons.forEach(btn => { - btn.addEventListener('click', event => { + radarButtons.forEach((btn) => { + btn.addEventListener('click', (event) => { event.preventDefault() const slug = event.target.getAttribute('radar') location.assign(`/radar/${slug}`) @@ -52,8 +53,8 @@ if (radarButtons) { // const adminButtons = document.querySelectorAll('.admin') if (adminButtons) { - adminButtons.forEach(btn => { - btn.addEventListener('click', event => { + adminButtons.forEach((btn) => { + btn.addEventListener('click', (event) => { event.preventDefault() const route = event.target.getAttribute('route') location.assign(route) @@ -72,7 +73,7 @@ if (adminButtons) { // const loginForm = document.getElementById('login-form') if (loginForm) { - loginForm.addEventListener('submit', e => { + loginForm.addEventListener('submit', (e) => { e.preventDefault() const name = document.getElementById('name').value const password = document.getElementById('password').value @@ -91,7 +92,7 @@ if (logOutBtn) logOutBtn.addEventListener('click', logout) // const passwordForm = document.getElementById('password-form') if (passwordForm) { - passwordForm.addEventListener('submit', async e => { + passwordForm.addEventListener('submit', async (e) => { e.preventDefault() document.querySelector('.btn--update-password').textContent = 'Updating...' @@ -112,6 +113,23 @@ if (passwordForm) { * R A D A R D I S P L A Y * * * ****************************************************************/ + +// +// Interactive search form +// +const searchField = document.getElementById('search_term') +if (searchField) { + searchField.addEventListener('keyup', (e) => searchProjects(searchField.value)) +} +// clear button +const clearBtn = document.getElementById('search_clear') +if (clearBtn) { + clearBtn.addEventListener('click', (e) => { + if (searchField) searchField.value = '' + clearProjects() + }) +} + // // Display the radar // @@ -138,7 +156,7 @@ if (radarSection) { const jrcTagFormButton = document.querySelector('#jrctagsfilter button') if (jrcTagFormButton) { // wire up the button to show the filter tags meny - jrcTagFormButton.addEventListener('click', event => { + jrcTagFormButton.addEventListener('click', (event) => { event.preventDefault() // show modal showFilterTagForm() @@ -150,8 +168,8 @@ if (jrcTagFormButton) { // const anyAllRadios = document.querySelectorAll('div.ops input') if (anyAllRadios) { - anyAllRadios.forEach(radio => { - radio.addEventListener('click', async event => { + anyAllRadios.forEach((radio) => { + radio.addEventListener('click', async (event) => { const filter = getTags() filter.union = event.target.value await updateTags(filter) @@ -171,7 +189,7 @@ if (anyAllRadios) { // const newUserForm = document.getElementById('new-user-form') if (newUserForm) { - newUserForm.addEventListener('submit', async e => { + newUserForm.addEventListener('submit', async (e) => { e.preventDefault() document.getElementById('btn--create-user').textContent = 'Updating...' @@ -195,8 +213,8 @@ if (newUserForm) { // const deleteUserLinks = document.querySelectorAll('.delete-user') if (deleteUserLinks) { - deleteUserLinks.forEach(link => { - link.addEventListener('click', async event => { + deleteUserLinks.forEach((link) => { + link.addEventListener('click', async (event) => { event.preventDefault() await deleteUser(event.path[1].getAttribute('route'), location.href) }) @@ -208,8 +226,8 @@ if (deleteUserLinks) { // const editUserLinks = document.querySelectorAll('.edit-user') if (editUserLinks) { - editUserLinks.forEach(link => { - link.addEventListener('click', async event => { + editUserLinks.forEach((link) => { + link.addEventListener('click', async (event) => { event.preventDefault() location.assign(event.path[1].getAttribute('route')) }) @@ -221,7 +239,7 @@ if (editUserLinks) { // const updateUserDetailsForm = document.getElementById('edit-user-form') if (updateUserDetailsForm) { - updateUserDetailsForm.addEventListener('submit', async event => { + updateUserDetailsForm.addEventListener('submit', async (event) => { event.preventDefault() const name = document.getElementById('name').value const email = document.getElementById('email').value @@ -236,7 +254,7 @@ if (updateUserDetailsForm) { // const setUserPasswordForm = document.getElementById('set-password-form') if (setUserPasswordForm) { - setUserPasswordForm.addEventListener('submit', async event => { + setUserPasswordForm.addEventListener('submit', async (event) => { event.preventDefault() const userid = document.getElementById('userid').value const password = document.getElementById('newPass').value @@ -250,7 +268,7 @@ if (setUserPasswordForm) { // const createRadarForm = document.getElementById('new-radar-form') if (createRadarForm) { - createRadarForm.addEventListener('submit', async event => { + createRadarForm.addEventListener('submit', async (event) => { event.preventDefault() const edition = document.getElementById('edition').value const year = document.getElementById('year').value @@ -265,8 +283,8 @@ if (createRadarForm) { // const editRadarLinks = document.querySelectorAll('.edit-radar') if (editRadarLinks) { - editRadarLinks.forEach(link => { - link.addEventListener('click', async event => { + editRadarLinks.forEach((link) => { + link.addEventListener('click', async (event) => { event.preventDefault() location.assign(event.path[1].getAttribute('route')) }) @@ -278,8 +296,8 @@ if (editRadarLinks) { // const deleteRadarLinks = document.querySelectorAll('.delete-radar') if (deleteRadarLinks) { - deleteRadarLinks.forEach(link => { - link.addEventListener('click', async event => { + deleteRadarLinks.forEach((link) => { + link.addEventListener('click', async (event) => { event.preventDefault() console.log(event.path[1].getAttribute('route')) await deleteRadar(event.path[1].getAttribute('route'), location.href) @@ -292,7 +310,7 @@ if (deleteRadarLinks) { // const updateRadarForm = document.getElementById('edit-radar-form') if (updateRadarForm) { - updateRadarForm.addEventListener('submit', async event => { + updateRadarForm.addEventListener('submit', async (event) => { event.preventDefault() const id = document.getElementById('radarid').value const summary = document.getElementById('summary').value @@ -305,8 +323,8 @@ if (updateRadarForm) { // const administerRadarForms = document.querySelectorAll('.administer-radar-form') if (administerRadarForms) { - administerRadarForms.forEach(form => { - form.addEventListener('submit', async event => { + administerRadarForms.forEach((form) => { + form.addEventListener('submit', async (event) => { event.preventDefault() const cutoff = document.getElementById('cutoff').value const route = event.target.getAttribute('route') @@ -320,7 +338,7 @@ if (administerRadarForms) { // const newProjectForm = document.getElementById('new-project-form') if (newProjectForm) { - newProjectForm.addEventListener('submit', async event => { + newProjectForm.addEventListener('submit', async (event) => { event.preventDefault() const values = { name: document.getElementById('name').value, @@ -334,7 +352,7 @@ if (newProjectForm) { projectURL: document.getElementById('projecturl').value, fundingBodyLink: document.getElementById('fundingbodylink').value, cwurl: document.getElementById('cwprojecthublink').value, - teaser: document.getElementById('teaser').value + teaser: document.getElementById('teaser').value, } await createProject(values) }) @@ -345,8 +363,8 @@ if (newProjectForm) { // const deleteProjectLinks = document.querySelectorAll('.delete-project') if (deleteProjectLinks) { - deleteProjectLinks.forEach(link => { - link.addEventListener('click', async event => { + deleteProjectLinks.forEach((link) => { + link.addEventListener('click', async (event) => { event.preventDefault() await deleteProject(event.path[1].getAttribute('route'), location.href) }) @@ -358,8 +376,8 @@ if (deleteProjectLinks) { // const editProjectLinks = document.querySelectorAll('.edit-project') if (editProjectLinks) { - editProjectLinks.forEach(link => { - link.addEventListener('click', async event => { + editProjectLinks.forEach((link) => { + link.addEventListener('click', async (event) => { event.preventDefault() location.assign(event.path[1].getAttribute('route')) }) @@ -371,7 +389,7 @@ if (editProjectLinks) { // const editProjectForm = document.getElementById('edit-project-form') if (editProjectForm) { - editProjectForm.addEventListener('submit', async event => { + editProjectForm.addEventListener('submit', async (event) => { event.preventDefault() const values = { id: document.getElementById('projectid').value, @@ -386,7 +404,7 @@ if (editProjectForm) { projectURL: document.getElementById('projecturl').value, fundingBodyLink: document.getElementById('fundingbodylink').value, cwurl: document.getElementById('cwprojecthublink').value, - teaser: document.getElementById('teaser').value + teaser: document.getElementById('teaser').value, } await updateProject(values) }) @@ -397,7 +415,7 @@ if (editProjectForm) { // const uploadImportForm = document.getElementById('import-projects-form') if (uploadImportForm) { - uploadImportForm.addEventListener('submit', async event => { + uploadImportForm.addEventListener('submit', async (event) => { event.preventDefault() const form = new FormData() form.append('importfile', document.getElementById('importfile').files[0]) @@ -410,7 +428,7 @@ if (uploadImportForm) { // const addCategoryForm = document.getElementById('add-category-form') if (addCategoryForm) { - addCategoryForm.addEventListener('submit', async event => { + addCategoryForm.addEventListener('submit', async (event) => { event.preventDefault() const cw_id = document.getElementById('cwid').value const classification = document.getElementById('classification').value @@ -425,7 +443,7 @@ if (addCategoryForm) { // const addScoreForm = document.getElementById('add-score-form') if (addScoreForm) { - addScoreForm.addEventListener('submit', async event => { + addScoreForm.addEventListener('submit', async (event) => { event.preventDefault() const cw_id = document.getElementById('cwid').value const mrl = document.getElementById('mrl').value @@ -442,18 +460,18 @@ if (addScoreForm) { // when selecting a dimension header, unselect al the dimension's terms const dimensionHeaders = document.querySelectorAll('.dimension-header') if (dimensionHeaders) { - dimensionHeaders.forEach(box => { - box.addEventListener('click', event => { + dimensionHeaders.forEach((box) => { + box.addEventListener('click', (event) => { const termBoxes = box.parentNode.parentNode.querySelectorAll('.term') - termBoxes.forEach(tB => (tB.checked = false)) + termBoxes.forEach((tB) => (tB.checked = false)) }) }) } // when selecting a dimension's term, unselect the dimension header const dimensionTerms = document.querySelectorAll('.term') if (dimensionTerms) { - dimensionTerms.forEach(termBox => { - termBox.addEventListener('click', event => { + dimensionTerms.forEach((termBox) => { + termBox.addEventListener('click', (event) => { const parentBox = termBox.parentNode.parentNode.parentNode.parentNode.querySelector( '.dimension-header' ) @@ -467,13 +485,13 @@ if (dimensionTerms) { // const taxonomySubmit = document.getElementById('edit-tags-form') if (taxonomySubmit) { - taxonomySubmit.addEventListener('submit', async event => { + taxonomySubmit.addEventListener('submit', async (event) => { event.preventDefault() const values = { id: document.getElementById('projectid').value, - tags: [] + tags: [], } - document.querySelectorAll('.term:checked,.dimension-header:checked').forEach(c => { + document.querySelectorAll('.term:checked,.dimension-header:checked').forEach((c) => { values.tags.push(c.value) }) await updateProject(values) @@ -485,7 +503,7 @@ if (taxonomySubmit) { // const rdExpander = document.querySelector('#overview #summary-flicker') if (rdExpander) { - rdExpander.addEventListener('click', event => { + rdExpander.addEventListener('click', (event) => { event.target.classList.toggle('open') event.target.parentNode.parentNode.classList.toggle('open') }) diff --git a/src/client/js/radar/search.js b/src/client/js/radar/search.js new file mode 100644 index 0000000..13460c7 --- /dev/null +++ b/src/client/js/radar/search.js @@ -0,0 +1,49 @@ +// +// IMPORTS +// +// libraries +// modules +// app modules +import showProjectData from './projectInfo' + +// +// EXPORTS +// +export { searchProjects, clearProjects } + +/***************** + * * + * FUNCTIONS * + * * + *****************/ + +// +// JRC FILTER TAGS FORM +// +// Show the filter tags form using client-side PUG +const resultDiv = document.getElementById('search_results') +const searchProjects = (searchStr) => { + if (searchStr.length > 1) { + // find blips + const blips = document.querySelectorAll( + `g.segment:not([style*='scale(0)']) g.blip[label*=${searchStr} i]` + ) + //remove all buttons + resultDiv.innerHTML = '' + // add buttns + blips.forEach((blip) => { + const blipData = JSON.parse(blip.getAttribute('data')) + // create a button and add to search result + const resultBtn = document.createElement('button') + resultBtn.addEventListener('click', (e) => { + showProjectData(blipData) + }) + resultBtn.innerHTML = blipData.prj_name + resultDiv.appendChild(resultBtn) + }) + } +} + +const clearProjects = () => { + resultDiv.innerHTML = '' +} diff --git a/src/server/views/radar.pug b/src/server/views/radar.pug index e734e9e..d1610e9 100644 --- a/src/server/views/radar.pug +++ b/src/server/views/radar.pug @@ -20,7 +20,9 @@ block content #summary-flicker ⌃ section#filters - include jrc-tagfilter + section#other + include jrc-tagfilter + include search section#radardata #rendering diff --git a/src/server/views/search.pug b/src/server/views/search.pug new file mode 100644 index 0000000..cd61f9a --- /dev/null +++ b/src/server/views/search.pug @@ -0,0 +1,10 @@ +div#search + // outer container for search icon overlay + div(style='position: relative;display: inline-block;') + // the search input field + input(type='text', id='search_term', name='search_term', size=20 ) + // the overlaid search icon + div(style='position: absolute; top: 0; right: 0; font-size: 90%; vertical-align: middle; opacity: 70%') 🔎 + + button(id='search_clear') ✕ + div#search_results