From 9f9bf11707f61ff8f5fffcc75e150ddbbdd6ba79 Mon Sep 17 00:00:00 2001 From: KL-yamada Date: Thu, 27 Oct 2022 10:40:24 +0900 Subject: [PATCH 1/9] =?UTF-8?q?JDCAT=202022/11=20=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app-facet-search/src/App.js | 39 ++- .../src/components/RangeCheckboxList.jsx | 255 ++++++++++++++++++ .../src/components/RangeFacet.jsx | 18 +- app-facet-search/src/index.css | 90 +++++++ 4 files changed, 385 insertions(+), 17 deletions(-) create mode 100644 app-facet-search/src/components/RangeCheckboxList.jsx create mode 100644 app-facet-search/src/index.css diff --git a/app-facet-search/src/App.js b/app-facet-search/src/App.js index e0c28f7..7d752cf 100644 --- a/app-facet-search/src/App.js +++ b/app-facet-search/src/App.js @@ -1,5 +1,6 @@ import 'rc-slider/assets/index.css'; import 'rc-tooltip/assets/bootstrap.css'; +import './index.css'; import React from "react"; import fetch from "unfetch"; import RangeFacet from "./components/RangeFacet"; @@ -15,10 +16,13 @@ class FacetSearch extends React.Component { constructor(props) { super(props); this.state = { - is_enable: true, + is_enable: false, list_title: {}, list_facet: {}, - list_order: {} + list_order: {}, + list_uiType: {}, + list_isOpen: {}, + list_displayNumber: {} }; this.getTitleAndOrder = this.getTitleAndOrder.bind(this); this.get_facet_search_list = this.get_facet_search_list.bind(this); @@ -28,15 +32,25 @@ class FacetSearch extends React.Component { getTitleAndOrder() { let titleLst = {}; let orderLst = {}; + let uiTypeLst = {}; + let isOpenLst = {}; + let displayNumberLst = {}; fetch("/facet-search/get-title-and-order", {method: "POST"}) .then((r) => r.json()) .then((response) => { if (response.status) { titleLst = response.data.titles; - orderLst =response.data.order; + orderLst = response.data.order; + uiTypeLst = response.data.uiTypes; + isOpenLst = response.data.isOpens; + displayNumberLst = response.data.displayNumbers; } this.setState({ list_title: titleLst }); this.setState({ list_order: orderLst }); + this.setState({ list_uiType: uiTypeLst }); + this.setState({ list_isOpen: isOpenLst }); + this.setState({ list_displayNumber: displayNumberLst }); + this.setState({ is_enable: true }); }); } @@ -77,9 +91,13 @@ class FacetSearch extends React.Component { let a = tmp.buckets[i]; if ((l == "en") && ((a.key).charCodeAt(0) > 256 || (a.key).charCodeAt(a.key.length - 1) > 256)) { - delete list_facet[name].buckets[i]; + //delete list_facet[name].buckets[i]; + list_facet[name].buckets.splice(i,1); + i--; } else if ((l != "en") && ((a.key).charCodeAt(0) < 256 && (a.key).charCodeAt(a.key.length - 1) < 256)) { - delete list_facet[name].buckets[i]; + //delete list_facet[name].buckets[i]; + list_facet[name].buckets.splice(i,1); + i--; } } } @@ -90,7 +108,9 @@ class FacetSearch extends React.Component { let a = tmp.buckets[i]; if (((a.key).charCodeAt(0) > 256 || (a.key).charCodeAt(a.key.length - 1) > 256)) { - delete list_facet[name].buckets[i]; + //delete list_facet[name].buckets[i]; + list_facet[name].buckets.splice(i,1); + i--; } } } @@ -107,7 +127,7 @@ class FacetSearch extends React.Component { } render() { - const { is_enable, list_title, list_facet, list_order } = this.state; + const { is_enable, list_title, list_facet, list_order, list_uiType, list_isOpen, list_displayNumber } = this.state; return (
{is_enable && ( @@ -116,8 +136,11 @@ class FacetSearch extends React.Component { const name = list_order[order]; const item = list_facet[name]; const nameshow = list_title[name]; + const isOpen = list_isOpen[name]; + const uiType = list_uiType[name]; + const displayNumber = list_displayNumber[name]; return ( - + ); })}
diff --git a/app-facet-search/src/components/RangeCheckboxList.jsx b/app-facet-search/src/components/RangeCheckboxList.jsx new file mode 100644 index 0000000..8fddcf6 --- /dev/null +++ b/app-facet-search/src/components/RangeCheckboxList.jsx @@ -0,0 +1,255 @@ +import "rc-slider/assets/index.css"; +import "rc-tooltip/assets/bootstrap.css"; +import React from "react"; + + +/** + * A UI component that displays faceted items as a list of checkboxes. + * This part consists of a list portion and a modal portion. + * + * + * The List portion displays a list of checkboxes with the number of displayNumber + * set in the admin panel. If the number of faceted items is greater than the + * number of displayNumber, a link to display the Modal is displayed. + * + * The checkboxes displayed in the List section are narrowed down by facet item + * at the same time as Click is performed. + * + * + * + * In the modal section, all faceted items are displayed in a modal. + * Since scrolling is used, there is no limit to the number of items. + * The number of display columns is also changed according to the screen size to be displayed. + * + * In the modal portion, no narrowing is performed until the search button is pressed. + * Multiple items can be selected and narrowed down in a batch. + * + * The modal portion can be closed by pressing the Cancel button or clicking + * on the portion outside the modal. The facet items that were selected before + * the refinement are cleared when the modal is closed. + * + * @param {array} values An array consisting of faceted item names (key) and the number of items in the target (doc_count). + * @param {string} name English name of facet item. + * @param {array} labels Array of labels used in translation. + * @param {integer} displayNumber Number of items displayed in the list. + * + * @author knowledge labo yamada + */ +function RangeCheckboxList({ values, name, labels, displayNumber }) { + + /** + * Returns the DOM representing the checkbox. + * + * @param {string} id ID of the checkbox. + * @param {string} value Value of the checkbox. + * @param {boolean} checked The selected state of the checkbox. + * @param {function} onChange Process when the check box is clicked, which is set only when List is displayed. + * @returns DOM representing a checkbox. + */ + const CheckBox = ({ id, value, checked, onChange}) => { + if(onChange !=null) { + //for lists + return ( + + ) + } else { + //for Modal + return ( + + ) + } + + } + + /** + * Returns the DOM of a list of checkboxes. + * This function is used for both List and Modal. The parameter isModal controls which use is made of this function. + * + * @param {array} values An array consisting of faceted item names (key) and the number of items in the target (doc_count). + * @param {string} name English name of facet item. + * @param {bool} isModal True for modal use. false for list use. + * @param {integer} displayNumber Number of items displayed in the list. + * @param {function} onChange Process when the check box is clicked, which is set only when List is displayed. + * @returns DOM representing a checkbox list. + */ + const CheckBoxList = ({ values, name, isModal, displayNumber, onChange}) => { + return ( + values.map((subitem,index) => { + if (isModal || index < displayNumber) { + let id = "id_" + name + (isModal ? "_chkbox_mdl_" : "_chkbox_") + index; + let label = subitem.key + "(" + subitem.doc_count + ")"; + let checked = params.indexOf(name + "=" + subitem.key)!= -1; + return ( +
+ +
+ ) + } + }) + ) + } + + /** + * Returns the modal DOM. + * + * @param {array} values An array consisting of faceted item names (key) and the number of items in the target (doc_count). + * @param {string} name English name of facet item. + * @param {bool} modalId ID set for the modal. + * @returns Modal DOM + */ + const ModalCheckboxList = ({ values, name, modalId }) => { + return ( +
+ +
+
+
+ +
+
+ {labels['cancel']} + +
+
+
+
+ ) + }; + + // Show/Hide Modal + var status = 'Hide'; + + /** + * Called to close the modal. + */ + function closeModal(){ + status = 'Hide'; + } + + /** + * Called to open a modal. + * Reconfigure it to select only the checkboxes that have been narrowed down + * from the parameters of the URL at the time before displaying. + */ + function openModal(){ + status = 'Show'; + document.querySelectorAll('.chbox-mdl input').forEach(el => { + el.checked = params.indexOf(name + "=" + el.value)!= -1; + }); + } + + /** + * Processing when a check box is selected when the List is displayed. The search items of + * the newly selected check box are added to the parameter to narrow down the search. + */ + function handleListChange(e) { + const targets = []; + document.querySelectorAll('.chbox-mdl input').forEach(el => { + if((el.checked && e.target.value !== el.value) || (e.target.checked && e.target.value === el.value)){ + targets.push({label: name, value: el.value}); + } + }); + executeSearch(targets); + } + + /** + * When the search button is pressed during modal display, a callout difference srere. + * The search item for the newly selected check box is added to the parameters and a narrowed search is performed. + */ + function handleModalListChange(e) { + const targets = []; + document.querySelectorAll('.chbox-mdl input').forEach(el => { + if(el.checked){ + targets.push({label: name, value: el.value}); + } + }); + executeSearch(targets); + } + + /** + * Narrowing is performed based on the narrowing target specified in this facet item. + * Parameters other than this facet item are used as is. + * + * @param {array} targets Parameters to be refined by this facet item. + */ + function executeSearch(targets) { + let searchUrl = ""; + if (search.indexOf("&") >= 0) { + let arrSearch = search.split("&"); + for (let i = 0; i < arrSearch.length; i++) { + //Parameters other than this facet item are used as is. + if (arrSearch[i].indexOf(encodeURIComponent(name) + "=") < 0) { + searchUrl += "&" + arrSearch[i]; + } + } + //Delete "&" in First element + searchUrl = searchUrl.substring(1); + } + if (searchUrl != "") { + search = searchUrl; + } + targets.map(function (subitem, k) { + const pattern = + encodeURIComponent(name) + "=" + encodeURIComponent(subitem.value); + search += "&" + pattern; + }); + search = search.replace("q=0", "q="); + search += search.indexOf('is_facet_search=') == -1 ? '&is_facet_search=true' : ''; + window.location.href = "/search" + search; + } + + let search = window.location.search.replace(",", "%2C") || "?"; + let params = window.location.search.substring(1).split('&'); + for (let i = 0; i < params.length; i++) { + params[i] = decodeURIComponent(params[i]); + } + let modalId = "id_" + name + "_checkbox_modal"; + var timeoutId ; + + /* + * Responsive measures. + * Addresses the problem of hiding the modal at the timing when the style is changed + * for mobile when the Window size is reduced. When resizing, prompt to redisplay if in display state. + */ + window.addEventListener("resize", function() { + if(status==='Hide'){ + return; + } + if ( timeoutId ) return ; + timeoutId = setTimeout( function () { + timeoutId = 0 ; + window.location.hash='#' + modalId; + }, 100 ) ; + }); + + let dp = displayNumber == null ? 5 : displayNumber; + return ( +
+
+ + {values.length > dp && + . . . See More + } + +
+
+ ); +} + +export default RangeCheckboxList; diff --git a/app-facet-search/src/components/RangeFacet.jsx b/app-facet-search/src/components/RangeFacet.jsx index 2112b67..ba194ab 100644 --- a/app-facet-search/src/components/RangeFacet.jsx +++ b/app-facet-search/src/components/RangeFacet.jsx @@ -4,21 +4,18 @@ import React, { useState } from "react"; import { Collapse } from "reactstrap"; import RangeSelect from "./RangeSelect"; import RangeSlider from "./RangeSlider"; +import RangeCheckboxList from "./RangeCheckboxList"; -function check_temp(name) { - return name === "Time Period(s)"; -} - -function RangeFacet({ item, nameshow, name, key, labels }) { +function RangeFacet({ item, nameshow, name, key, labels, isInitOpen, uiType, displayNumber }) { const toggle = () => setIsOpen(!isOpen); const search = window.location.search.replace(",", "%2C"); - const is_check = search.indexOf(encodeURIComponent(name)) >= 0 ? true : false; + const is_check = search.indexOf(encodeURIComponent(name)) >= 0 ? true : isInitOpen; const [isOpen, setIsOpen] = useState(is_check); return (

{nameshow}

- + {!isOpen && ( @@ -33,10 +30,13 @@ function RangeFacet({ item, nameshow, name, key, labels }) {
- {!check_temp(name) && ( + {item != null && uiType === "SelectBox" && ( )} - {check_temp(name) && ( + {item != null && uiType === "CheckboxList" && ( + + )} + {item != null && uiType === "RangeSlider" && ( )}
diff --git a/app-facet-search/src/index.css b/app-facet-search/src/index.css new file mode 100644 index 0000000..3c04939 --- /dev/null +++ b/app-facet-search/src/index.css @@ -0,0 +1,90 @@ +.chbox-container { + margin-left:10px; + display:table; + width:100%; +} + +.chbox-container label{ + user-select: none; + +} + +.chbox-mdl { + z-index: 999; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 40px 10px; + text-align: center; +} + +.chbox-mdl:not(:target) { + opacity: 0; + visibility: hidden; + transition: opacity .3s, visibility .3s; +} + +.chbox-mdl:target { + opacity: 1; + visibility: visible; + transition: opacity .4s, visibility .4s; +} + +.chbox-mdl::after { + display: inline-block; + height: 100%; + margin-left: -.05em; + vertical-align: middle; + content: "" +} + +.chbox-mdl .overlay { + z-index: 10; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, .8) +} + +.chbox-mdl .window { + box-sizing: border-box; + display: inline-block; + z-index: 20; + position: relative; + width: 90%; + min-width: 320px; + border-radius: 2px; + background: #fff; + box-shadow: 0 0 30px rgba(0, 0, 0, .6); + vertical-align: middle +} + +.chbox-mdl .window .content { + max-height: 80vh; + overflow-y: auto; + text-align: left; + padding: 20px; + line-height: 15px +} + +.chbox-mdl .window .content .list { + display: grid; + gap: 10px; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + max-height: 50vh; + overflow-y: scroll; +} + +.chbox-mdl .window .content .footer { + width: 100%; + text-align: center; + padding-top: 20px; +} + +.chbox-mdl .window .content .footer button { + margin-left: 50px; +} \ No newline at end of file From 405eec49a524328f136615fc70a3ee59ba040f9a Mon Sep 17 00:00:00 2001 From: KL-yamada Date: Thu, 24 Nov 2022 13:06:47 +0900 Subject: [PATCH 2/9] Fixing test defects --- .../src/components/RangeCheckboxList.jsx | 42 +++++++------------ app-facet-search/src/index.css | 14 +++++-- cp_app_facet_search.sh | 5 ++- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/app-facet-search/src/components/RangeCheckboxList.jsx b/app-facet-search/src/components/RangeCheckboxList.jsx index 8fddcf6..571bca4 100644 --- a/app-facet-search/src/components/RangeCheckboxList.jsx +++ b/app-facet-search/src/components/RangeCheckboxList.jsx @@ -116,14 +116,14 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { const ModalCheckboxList = ({ values, name, modalId }) => { return (
- +
- {labels['cancel']} + {labels['cancel']}
@@ -132,14 +132,14 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { ) }; - // Show/Hide Modal - var status = 'Hide'; - /** * Called to close the modal. */ - function closeModal(){ - status = 'Hide'; + function closeModal(e){ + if(e == null){ + return; + } + document.getElementById(e.target.getAttribute('modalId')).classList.remove("is-active"); } /** @@ -147,8 +147,13 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { * Reconfigure it to select only the checkboxes that have been narrowed down * from the parameters of the URL at the time before displaying. */ - function openModal(){ - status = 'Show'; + function openModal(e){ + + if(e == null){ + console.log("event == null" ); + return; + } + document.getElementById(e.target.getAttribute('modalId')).classList.add("is-active"); document.querySelectorAll('.chbox-mdl input').forEach(el => { el.checked = params.indexOf(name + "=" + el.value)!= -1; }); @@ -220,23 +225,6 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { params[i] = decodeURIComponent(params[i]); } let modalId = "id_" + name + "_checkbox_modal"; - var timeoutId ; - - /* - * Responsive measures. - * Addresses the problem of hiding the modal at the timing when the style is changed - * for mobile when the Window size is reduced. When resizing, prompt to redisplay if in display state. - */ - window.addEventListener("resize", function() { - if(status==='Hide'){ - return; - } - if ( timeoutId ) return ; - timeoutId = setTimeout( function () { - timeoutId = 0 ; - window.location.hash='#' + modalId; - }, 100 ) ; - }); let dp = displayNumber == null ? 5 : displayNumber; return ( @@ -244,7 +232,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) {
{values.length > dp && - . . . See More + . . . See More }
diff --git a/app-facet-search/src/index.css b/app-facet-search/src/index.css index 3c04939..17a37b9 100644 --- a/app-facet-search/src/index.css +++ b/app-facet-search/src/index.css @@ -18,15 +18,12 @@ left: 0; padding: 40px 10px; text-align: center; -} - -.chbox-mdl:not(:target) { opacity: 0; visibility: hidden; transition: opacity .3s, visibility .3s; } -.chbox-mdl:target { +.chbox-mdl.is-active { opacity: 1; visibility: visible; transition: opacity .4s, visibility .4s; @@ -87,4 +84,13 @@ .chbox-mdl .window .content .footer button { margin-left: 50px; +} + +.rc-slider-mark-text { + display: none !important; +} + +.rc-slider-dot { + border: initial !important; + background-color: initial !important; } \ No newline at end of file diff --git a/cp_app_facet_search.sh b/cp_app_facet_search.sh index 72507d0..30491a6 100755 --- a/cp_app_facet_search.sh +++ b/cp_app_facet_search.sh @@ -17,8 +17,11 @@ fi TARGETDIR=$WEKODIR/modules # args-check-end +# Merging Style Files +cat ./app-facet-search/build/static/css/2.*chunk.css ./app-facet-search/build/static/css/main.*.chunk.css > ./app-facet-search/build/static/css/facet_chunk.css + # copy-begin -cp -p ./app-facet-search/build/static/css/*.chunk.css ${TARGETDIR}/weko-search-ui/weko_search_ui/static/css/weko_search_ui/facet_chunk.css +cp -p ./app-facet-search/build/static/css/facet_chunk.css ${TARGETDIR}/weko-search-ui/weko_search_ui/static/css/weko_search_ui/facet_chunk.css cp -p ./app-facet-search/build/static/js/main.*.chunk.js ${TARGETDIR}/weko-search-ui/weko_search_ui/static/js/weko_search_ui/facet.main.chunk.js cp -p ./app-facet-search/build/static/js/runtime-main.*.js ${TARGETDIR}/weko-search-ui/weko_search_ui/static/js/weko_search_ui/facet.runtime-main.js cp -p ./app-facet-search/build/static/js/2.*.chunk.js ${TARGETDIR}/weko-search-ui/weko_search_ui/static/js/weko_search_ui/facet.chunk.js From 146ed01c6a6af7f2aac84cc832e8c7bdc0d5389a Mon Sep 17 00:00:00 2001 From: KL-yamada Date: Mon, 19 Dec 2022 22:51:12 +0900 Subject: [PATCH 3/9] bug fix #35026 --- .../src/components/RangeCheckboxList.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app-facet-search/src/components/RangeCheckboxList.jsx b/app-facet-search/src/components/RangeCheckboxList.jsx index 571bca4..53bf763 100644 --- a/app-facet-search/src/components/RangeCheckboxList.jsx +++ b/app-facet-search/src/components/RangeCheckboxList.jsx @@ -37,6 +37,10 @@ import React from "react"; */ function RangeCheckboxList({ values, name, labels, displayNumber }) { + //If there is a space in the id attribute, it cannot be searched by ID, so escape it. + let facet_item_id = "id_" + name + "_chkbox"; + let facet_item_id_for_search = facet_item_id.replace(' ', '\\ '); + /** * Returns the DOM representing the checkbox. * @@ -154,7 +158,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { return; } document.getElementById(e.target.getAttribute('modalId')).classList.add("is-active"); - document.querySelectorAll('.chbox-mdl input').forEach(el => { + document.querySelector('#' + facet_item_id_for_search).querySelectorAll('.chbox-mdl input').forEach(el => { el.checked = params.indexOf(name + "=" + el.value)!= -1; }); } @@ -165,7 +169,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { */ function handleListChange(e) { const targets = []; - document.querySelectorAll('.chbox-mdl input').forEach(el => { + document.querySelector('#' + facet_item_id_for_search).querySelectorAll('.chbox-mdl input').forEach(el => { if((el.checked && e.target.value !== el.value) || (e.target.checked && e.target.value === el.value)){ targets.push({label: name, value: el.value}); } @@ -179,7 +183,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { */ function handleModalListChange(e) { const targets = []; - document.querySelectorAll('.chbox-mdl input').forEach(el => { + document.querySelector('#' + facet_item_id_for_search).querySelectorAll('.chbox-mdl input').forEach(el => { if(el.checked){ targets.push({label: name, value: el.value}); } @@ -228,7 +232,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { let dp = displayNumber == null ? 5 : displayNumber; return ( -
+
{values.length > dp && From a6f3d088574e1820469be80594f4b7e88bc642df Mon Sep 17 00:00:00 2001 From: KL-yamada Date: Mon, 19 Dec 2022 22:51:12 +0900 Subject: [PATCH 4/9] bug fix #35026 --- .../src/components/RangeCheckboxList.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app-facet-search/src/components/RangeCheckboxList.jsx b/app-facet-search/src/components/RangeCheckboxList.jsx index 571bca4..32b5926 100644 --- a/app-facet-search/src/components/RangeCheckboxList.jsx +++ b/app-facet-search/src/components/RangeCheckboxList.jsx @@ -37,6 +37,10 @@ import React from "react"; */ function RangeCheckboxList({ values, name, labels, displayNumber }) { + //If there is a space in the id attribute, it cannot be searched by ID, so escape it. + let facet_item_id = "id_" + name + "_chkbox"; + let facet_item_id_for_search = CSS.escape(facet_item_id); + /** * Returns the DOM representing the checkbox. * @@ -154,7 +158,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { return; } document.getElementById(e.target.getAttribute('modalId')).classList.add("is-active"); - document.querySelectorAll('.chbox-mdl input').forEach(el => { + document.querySelector('#' + facet_item_id_for_search).querySelectorAll('.chbox-mdl input').forEach(el => { el.checked = params.indexOf(name + "=" + el.value)!= -1; }); } @@ -165,7 +169,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { */ function handleListChange(e) { const targets = []; - document.querySelectorAll('.chbox-mdl input').forEach(el => { + document.querySelector('#' + facet_item_id_for_search).querySelectorAll('.chbox-mdl input').forEach(el => { if((el.checked && e.target.value !== el.value) || (e.target.checked && e.target.value === el.value)){ targets.push({label: name, value: el.value}); } @@ -179,7 +183,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { */ function handleModalListChange(e) { const targets = []; - document.querySelectorAll('.chbox-mdl input').forEach(el => { + document.querySelector('#' + facet_item_id_for_search).querySelectorAll('.chbox-mdl input').forEach(el => { if(el.checked){ targets.push({label: name, value: el.value}); } @@ -228,7 +232,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { let dp = displayNumber == null ? 5 : displayNumber; return ( -
+
{values.length > dp && From e18e4c40323d88e979de0a47075c76f58f02ae61 Mon Sep 17 00:00:00 2001 From: KL-yamada Date: Thu, 23 Feb 2023 17:58:43 +0900 Subject: [PATCH 5/9] Basic implementation of Specification 1 and Specification 4 --- app-facet-search/src/App.js | 28 ++++ .../src/components/RangeCheckboxList.jsx | 26 ++- .../src/components/RangeFacet.jsx | 1 + .../src/components/RangeSelect.jsx | 8 +- .../src/components/RangeSlider.jsx | 148 +++++++++++++----- app-facet-search/src/index.css | 8 + 6 files changed, 169 insertions(+), 50 deletions(-) diff --git a/app-facet-search/src/App.js b/app-facet-search/src/App.js index 7d752cf..fb1e83d 100644 --- a/app-facet-search/src/App.js +++ b/app-facet-search/src/App.js @@ -12,6 +12,8 @@ for (let i = 0; i < labels.length; i++) { LABELS[labels[i].id] = labels[i].value; } +let facetSearchComponent; + class FacetSearch extends React.Component { constructor(props) { super(props); @@ -27,6 +29,7 @@ class FacetSearch extends React.Component { this.getTitleAndOrder = this.getTitleAndOrder.bind(this); this.get_facet_search_list = this.get_facet_search_list.bind(this); this.convertData = this.convertData.bind(this); + facetSearchComponent = this; } getTitleAndOrder() { @@ -55,6 +58,7 @@ class FacetSearch extends React.Component { } get_facet_search_list() { + console.debug("get_facet_search_list実行"); let search = new URLSearchParams(window.location.search); let url = search.get('search_type') == 2 ? "/api/index/" : "/api/records/"; fetch(url + '?' + search.toString()) @@ -73,6 +77,7 @@ class FacetSearch extends React.Component { } convertData(data) { + console.debug("convertData実行"); let list_facet = {}; if (data) { Object.keys(data).map(function (name, k) { @@ -119,6 +124,12 @@ class FacetSearch extends React.Component { }); } this.setState({list_facet: list_facet}); + Object.keys(data).map(function (name, k) { + if(window.facetSearchFunctions[name + '_clearSliderValue']) { + window.facetSearchFunctions[name + '_clearSliderValue'](); + } + }); + } componentDidMount() { @@ -150,4 +161,21 @@ class FacetSearch extends React.Component { } } +const useFacetSearch = () => { + return facetSearchComponent != null; +} + +const resetFacetData = () => { + console.debug("resetFacetData 呼び出されました。"); + if(facetSearchComponent != null) { + console.debug("facetSearchComponent != null → true"); + facetSearchComponent.get_facet_search_list(); + } +} + +// windowオブジェクトのグローバル変数用キーに定義した関数を入れる +window.facetSearchFunctions = {}; +window.facetSearchFunctions.useFacetSearch = useFacetSearch; +window.facetSearchFunctions.resetFacetData = resetFacetData; + export default FacetSearch; diff --git a/app-facet-search/src/components/RangeCheckboxList.jsx b/app-facet-search/src/components/RangeCheckboxList.jsx index 32b5926..28f76ff 100644 --- a/app-facet-search/src/components/RangeCheckboxList.jsx +++ b/app-facet-search/src/components/RangeCheckboxList.jsx @@ -1,7 +1,7 @@ import "rc-slider/assets/index.css"; import "rc-tooltip/assets/bootstrap.css"; -import React from "react"; - +import React, { useState } from "react"; +//import reSearchFacet from '../App.js'; /** * A UI component that displays faceted items as a list of checkboxes. @@ -36,6 +36,7 @@ import React from "react"; * @author knowledge labo yamada */ function RangeCheckboxList({ values, name, labels, displayNumber }) { + const [listCheckedItems, setListCheckedItems] = useState({}); //If there is a space in the id attribute, it cannot be searched by ID, so escape it. let facet_item_id = "id_" + name + "_chkbox"; @@ -53,12 +54,13 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { const CheckBox = ({ id, value, checked, onChange}) => { if(onChange !=null) { //for lists + listCheckedItems[id] = checked; return ( @@ -128,7 +130,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) {
{labels['cancel']} - +
@@ -174,6 +176,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { targets.push({label: name, value: el.value}); } }); + setListCheckedItems({...listCheckedItems, [e.target.id]: e.target.checked}); executeSearch(targets); } @@ -186,9 +189,12 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { document.querySelector('#' + facet_item_id_for_search).querySelectorAll('.chbox-mdl input').forEach(el => { if(el.checked){ targets.push({label: name, value: el.value}); + }else if(listCheckedItems[el.id]){ + setListCheckedItems({...listCheckedItems, [e.target.id]: el.checked}); } }); executeSearch(targets); + document.getElementById(e.target.getAttribute('modalId')).classList.remove("is-active"); } /** @@ -213,6 +219,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { if (searchUrl != "") { search = searchUrl; } + targets.map(function (subitem, k) { const pattern = encodeURIComponent(name) + "=" + encodeURIComponent(subitem.value); @@ -220,7 +227,14 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { }); search = search.replace("q=0", "q="); search += search.indexOf('is_facet_search=') == -1 ? '&is_facet_search=true' : ''; - window.location.href = "/search" + search; + if(window.invenioSearchFunctions) { + window.history.pushState(null,document.title,"/search" + search); + window.invenioSearchFunctions.reSearchInvenio(); + }else { + window.location.href = "/search" + search; + } + + } let search = window.location.search.replace(",", "%2C") || "?"; @@ -236,7 +250,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) {
{values.length > dp && - . . . See More + . . . See More }
diff --git a/app-facet-search/src/components/RangeFacet.jsx b/app-facet-search/src/components/RangeFacet.jsx index ba194ab..42ced44 100644 --- a/app-facet-search/src/components/RangeFacet.jsx +++ b/app-facet-search/src/components/RangeFacet.jsx @@ -7,6 +7,7 @@ import RangeSlider from "./RangeSlider"; import RangeCheckboxList from "./RangeCheckboxList"; function RangeFacet({ item, nameshow, name, key, labels, isInitOpen, uiType, displayNumber }) { + console.debug("RangeFacet実行"); const toggle = () => setIsOpen(!isOpen); const search = window.location.search.replace(",", "%2C"); const is_check = search.indexOf(encodeURIComponent(name)) >= 0 ? true : isInitOpen; diff --git a/app-facet-search/src/components/RangeSelect.jsx b/app-facet-search/src/components/RangeSelect.jsx index 43352ae..cbd464d 100644 --- a/app-facet-search/src/components/RangeSelect.jsx +++ b/app-facet-search/src/components/RangeSelect.jsx @@ -27,7 +27,13 @@ function RangeSelect({ values, name, labels }) { }); search = search.replace("q=0", "q="); search += search.indexOf('is_facet_search=') == -1 ? '&is_facet_search=true' : ''; - window.location.href = "/search" + search; + + if(window.invenioSearchFunctions) { + window.history.pushState(null,document.title,"/search" + search); + window.invenioSearchFunctions.reSearchInvenio(); + }else { + window.location.href = "/search" + search; + } } let search = window.location.search.replace(",", "%2C") || "?"; diff --git a/app-facet-search/src/components/RangeSlider.jsx b/app-facet-search/src/components/RangeSlider.jsx index 5e6f0ed..697a290 100644 --- a/app-facet-search/src/components/RangeSlider.jsx +++ b/app-facet-search/src/components/RangeSlider.jsx @@ -4,17 +4,56 @@ import 'rc-tooltip/assets/bootstrap.css'; import React, { useState } from "react"; function RangeSlider({ value, name, labels }) { + //If there is a space in the id attribute, it cannot be searched by ID, so escape it. + let facet_item_id = "id_" + name + "_slider"; + let facet_item_id_for_search = CSS.escape(facet_item_id); - function checkFormat(date) { - let pattern = new RegExp(/^(\d{8})|(\d{6})|(\d{4})$/); - let match = pattern.exec(date); - let result = false; - if (match.length > 0) { - if (date == match[0]) { - result = true; + function validateInputIsOk() { + let headComp = document.getElementById(facet_item_id + "_input_head"); + let tailComp = document.getElementById(facet_item_id + "_input_tail"); + let msgComp = document.getElementById(facet_item_id + "_msg"); + + // 必須チェック + if(!headComp.value || !tailComp.value){ + if(!headComp.value) { + setHeadStyle('form-control range-slider-error'); + } + if(!tailComp.value) { + setTailStyle('form-control range-slider-error'); + } + setErrMsg('Set the value.'); + return false; + } + // 数値入力チェック + //TODO 型に応じた対応 + let pattern = new RegExp(/^([1-9]\d*|0)$/); + let headResult = pattern.exec(inputHead); + let tailResult = pattern.exec(inputTail); + + if(headResult == null || headResult[0] != inputHead || + tailResult == null || tailResult[0] != inputTail) { + if(headResult == null || headResult[0] != inputHead) { + setHeadStyle('form-control range-slider-error'); + } + if(tailResult == null || tailResult[0] != inputTail) { + setTailStyle('form-control range-slider-error'); } + setErrMsg('Set the correct value.'); + return false; } - return result; + + // 相関チェック + if(parseFloat(inputHead) > parseFloat(inputTail)) { + setHeadStyle('form-control range-slider-error'); + setTailStyle('form-control range-slider-error'); + setErrMsg('The range from should be less than or equal to the range to.'); + return false; + } + //エラー情報のクリア + setHeadStyle('form-control'); + setTailStyle('form-control'); + setErrMsg(''); + return true; } function clearUrlSlide() { @@ -37,68 +76,90 @@ function RangeSlider({ value, name, labels }) { } function handleSlide(valuelog) { - setInputHead(marks[valuelog[0]]); - setInputTail(marks[valuelog[1]]); + setSliderValues([valuelog[0],valuelog[1]]); + if(inputHead != valuelog[0]) { + setInputHead(Math.round(valuelog[0])); + } + if(inputTail != valuelog[1]) { + setInputTail(Math.round(valuelog[1])); + } } function handleGo() { clearUrlSlide(); - let inputHeadVal = parseInt(inputHead); - let inputTailVal = parseInt(inputTail); - let pattern = ""; - if (inputHeadVal && checkFormat(inputHeadVal)) { - pattern += "&" + encodeURIComponent("date_range1_from") + "=" + encodeURIComponent(inputHeadVal); + //TODO チェックロジック + if(!validateInputIsOk()) { + return; } - if (inputTailVal && checkFormat(inputTailVal)) { - pattern += "&" + encodeURIComponent("date_range1_to") + "=" + encodeURIComponent(inputTailVal); - } - if (pattern) { - search += pattern; + let pattern = ""; + pattern += "&" + encodeURIComponent("date_range1_from") + "=" + encodeURIComponent(inputHead); + pattern += "&" + encodeURIComponent("date_range1_to") + "=" + encodeURIComponent(inputTail); + search += pattern; + + if(window.invenioSearchFunctions) { + window.history.pushState(null,document.title,"/search" + search); + window.invenioSearchFunctions.reSearchInvenio(); + }else { window.location.href = "/search" + search; } } - let marks = {}; - let distance; - // Put to Matks - let point_mark; - let marks_arr = []; + let search = window.location.search.replace(",", "%2C") || "?"; - if (value) { + + // URLパラメータより最大値と最小値を取得 + let params = (new URL(document.location)).searchParams; + // TODO 汎用化 + let minValue = params.get('date_range1_from') == null ? null : parseInt(params.get('date_range1_from')); + let maxValue = params.get('date_range1_to') == null ? null : parseInt(params.get('date_range1_to')); + if (value && minValue == null && maxValue == null) { value.map(function (subitem, k) { let parse_Int; if (subitem.key.length > 0) { parse_Int = parseInt(subitem.key); - marks_arr.push(parse_Int); + if(minValue == null || minValue > parse_Int) { + minValue = parse_Int; + } + if(maxValue == null || maxValue < parse_Int) { + maxValue = parse_Int; + } } }); } - if (marks_arr.length > 1) { - // Sort. - marks_arr.sort(); - marks_arr = Array.from(new Set(marks_arr)) - distance = 100 / (marks_arr.length - 1); - for (point_mark in marks_arr) { - marks[point_mark * distance] = marks_arr[point_mark].toString(); - } - } + let step = (maxValue - minValue)/100; - const [inputHead, setInputHead] = useState(marks_arr[0]); - const [inputTail, setInputTail] = useState(marks_arr[point_mark]); + console.log("====== SLIEDER DEBUG START ======"); + const [sliderValues, setSliderValues] = useState([minValue,maxValue]); + const [inputHead, setInputHead] = useState(minValue); + const [inputTail, setInputTail] = useState(maxValue); + const [headStyle, setHeadStyle] = useState('form-control'); + const [tailStyle, setTailStyle] = useState('form-control'); + const [errMsg, setErrMsg] = useState(''); + + const clearSliderValue = () => { + //再呼び出しされた場合の情報再設定 + setSliderValues([minValue, maxValue]); + setInputHead(minValue); + setInputTail(maxValue); + setHeadStyle('form-control'); + setTailStyle('form-control'); + setErrMsg(''); + } + window.facetSearchFunctions[name + '_clearSliderValue'] = clearSliderValue; return ( -
+
- +
- setInputHead(e.target.value)} />
- setInputTail(e.target.value)} /> @@ -106,10 +167,11 @@ function RangeSlider({ value, name, labels }) {
+
{errMsg}
) } diff --git a/app-facet-search/src/index.css b/app-facet-search/src/index.css index 17a37b9..a3375fe 100644 --- a/app-facet-search/src/index.css +++ b/app-facet-search/src/index.css @@ -93,4 +93,12 @@ .rc-slider-dot { border: initial !important; background-color: initial !important; +} + +.range-slider-error { + background-color: #FFF0F5; +} + +.range-slider-error-msg { + color: red; } \ No newline at end of file From 628256895ff516c3c1d7f537ee3443f27db29e01 Mon Sep 17 00:00:00 2001 From: KL-yamada Date: Wed, 29 Mar 2023 00:14:32 +0900 Subject: [PATCH 6/9] Requirement Specification Implementation --- app-facet-search/package.json | 23 +++--- app-facet-search/src/App.js | 32 ++++++--- .../src/components/RangeCheckboxList.jsx | 50 ++++--------- .../src/components/RangeFacet.jsx | 2 +- .../src/components/RangeSelect.jsx | 42 +++-------- .../src/components/RangeSlider.jsx | 72 +++++++------------ 6 files changed, 84 insertions(+), 137 deletions(-) diff --git a/app-facet-search/package.json b/app-facet-search/package.json index 0f3697e..b650eab 100644 --- a/app-facet-search/package.json +++ b/app-facet-search/package.json @@ -3,18 +3,19 @@ "version": "0.1.0", "private": true, "dependencies": { - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "rc-slider": "^9.7.2", - "react": "^17.0.2", - "react-collapse": "^5.1.0", - "react-dom": "^17.0.2", - "react-scripts": "^4.0.3", - "react-select": "^4.3.1", - "reactstrap": "^8.9.0", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.4.3", + "rc-slider": "^10.1.0", + "rc-tooltip": "^5.2.2", + "react": "^18.2.0", + "react-collapse": "^5.1.1", + "react-dom": "^18.2.0", + "react-scripts": "^5.0.1", + "react-select": "^5.7.0", + "reactstrap": "^9.1.5", "unfetch": "^4.2.0", - "web-vitals": "^1.0.1" + "web-vitals": "^3.1.0" }, "scripts": { "start": "react-scripts start", diff --git a/app-facet-search/src/App.js b/app-facet-search/src/App.js index fb1e83d..66c951e 100644 --- a/app-facet-search/src/App.js +++ b/app-facet-search/src/App.js @@ -58,8 +58,9 @@ class FacetSearch extends React.Component { } get_facet_search_list() { - console.debug("get_facet_search_list実行"); + let search = new URLSearchParams(window.location.search); + let url = search.get('search_type') == 2 ? "/api/index/" : "/api/records/"; fetch(url + '?' + search.toString()) .then((r) => r.json()) @@ -77,7 +78,6 @@ class FacetSearch extends React.Component { } convertData(data) { - console.debug("convertData実行"); let list_facet = {}; if (data) { Object.keys(data).map(function (name, k) { @@ -138,7 +138,7 @@ class FacetSearch extends React.Component { } render() { - const { is_enable, list_title, list_facet, list_order, list_uiType, list_isOpen, list_displayNumber } = this.state; + const { is_enable, list_title, list_facet, list_order, list_uiType, list_isOpen, list_displayNumber} = this.state; return (
{is_enable && ( @@ -165,17 +165,33 @@ const useFacetSearch = () => { return facetSearchComponent != null; } -const resetFacetData = () => { - console.debug("resetFacetData 呼び出されました。"); +const resetFacetData = (data) => { if(facetSearchComponent != null) { - console.debug("facetSearchComponent != null → true"); - facetSearchComponent.get_facet_search_list(); + facetSearchComponent.convertData(data); } } -// windowオブジェクトのグローバル変数用キーに定義した関数を入れる +const getFacetSearchCondition = () => { + let search = new URLSearchParams(window.location.search); + let result = new URLSearchParams(); + Object.keys(facetSearchComponent.state.list_order).map(function (order, k) { + let name = facetSearchComponent.state.list_order[order]; + if(search.has(name)) { + for(var value of search.getAll(name)) { + result.append(name, value); + } + } + }); + console.debug(result); + return result; +} + + + +// Put the defined function in the key for the global variable of the window object. window.facetSearchFunctions = {}; window.facetSearchFunctions.useFacetSearch = useFacetSearch; window.facetSearchFunctions.resetFacetData = resetFacetData; +window.facetSearchFunctions.getFacetSearchCondition = getFacetSearchCondition; export default FacetSearch; diff --git a/app-facet-search/src/components/RangeCheckboxList.jsx b/app-facet-search/src/components/RangeCheckboxList.jsx index 28f76ff..20ec2b4 100644 --- a/app-facet-search/src/components/RangeCheckboxList.jsx +++ b/app-facet-search/src/components/RangeCheckboxList.jsx @@ -97,7 +97,7 @@ function RangeCheckboxList({ values, name, labels, displayNumber }) { if (isModal || index < displayNumber) { let id = "id_" + name + (isModal ? "_chkbox_mdl_" : "_chkbox_") + index; let label = subitem.key + "(" + subitem.doc_count + ")"; - let checked = params.indexOf(name + "=" + subitem.key)!= -1; + let checked = search.get(name) && search.getAll(name).includes(subitem.key); return (