From 8e1f35ce40730ce92ede329525011c3ec6d4f774 Mon Sep 17 00:00:00 2001 From: Piotr Nalepa Date: Fri, 10 May 2019 15:56:25 +0200 Subject: [PATCH] Converted FinderComponent into a functional component. Removed references to preselected data --- .../components/finder/finder.component.js | 438 +++++------------- 1 file changed, 111 insertions(+), 327 deletions(-) diff --git a/src/modules/udw/tabs/browse/components/finder/finder.component.js b/src/modules/udw/tabs/browse/components/finder/finder.component.js index 93748ad0..fa9af9b4 100644 --- a/src/modules/udw/tabs/browse/components/finder/finder.component.js +++ b/src/modules/udw/tabs/browse/components/finder/finder.component.js @@ -1,229 +1,58 @@ -import React, { Component } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; import PropTypes from 'prop-types'; import FinderTreeBranchComponent from './finder.tree.branch.component'; import deepClone from '../../../../../common/helpers/deep.clone.helper'; -import { - loadPreselectedLocationData, - QUERY_LIMIT, - findLocationsByParentLocationId, -} from '../../../../services/universal.discovery.service'; +import { QUERY_LIMIT, findLocationsByParentLocationId } from '../../../../services/universal.discovery.service'; import { restInfo } from '../../../../../common/rest-info/rest.info'; const ROOT_LOCATION_OBJECT = null; -export default class FinderComponent extends Component { - constructor(props) { - super(props); - - this.state = { - locationsMap: {}, - activeLocations: [], - limit: QUERY_LIMIT, - }; - - this.appendMoreItems = this.appendMoreItems.bind(this); - this.updateLocationsData = this.updateLocationsData.bind(this); - this.findLocationChildren = this.findLocationChildren.bind(this); - this.loadBranchLeaves = this.loadBranchLeaves.bind(this); - this.onLoadMore = this.onLoadMore.bind(this); - this.renderBranch = this.renderBranch.bind(this); - this.setBranchContainerRef = this.setBranchContainerRef.bind(this); - this.setPreselectedState = this.getPreselectedState.bind(this); - - this.locationsMap = {}; - this.activeLocations = []; - this.preselectedItem = null; - } - - componentDidMount() { - const isForcedLocation = this.props.allowedLocations.length === 1; - const allowedIncludesPreselected = this.props.allowedLocations.includes(this.props.preselectedLocation); - const isPreselectedLocation = this.props.preselectedLocation && (!this.props.allowedLocations.length || allowedIncludesPreselected); - - if (isForcedLocation) { - this.loadPreselectedData(this.props.allowedLocations[0]); - } else if (isPreselectedLocation) { - this.loadPreselectedData(this.props.preselectedLocation); - } else { - this.setDefaultActiveLocations(); - findLocationsByParentLocationId({ ...restInfo, parentLocationId: this.props.startingLocationId }, this.updateLocationsData); - } - } - - componentDidUpdate() { - this.updateBranchesContainerScroll(); - } - - UNSAFE_componentWillReceiveProps(nextProps) { - const isShowingUp = nextProps.isVisible && nextProps.isVisible !== this.props.isVisible; - - if (this.preselectedItem && this.locationsMap && this.activeLocations && isShowingUp) { - this.setState(this.getPreselectedState, () => nextProps.isVisible && this.props.onItemSelect(this.preselectedItem)); - } - } - - getPreselectedState() { - return { locationsMap: this.locationsMap, activeLocations: this.activeLocations }; - } - - /** - * Load data of preselected location. - * - * @method loadPreselectedData - * @param {String} locationId - * @memberof FinderComponent - */ - loadPreselectedData(locationId) { - const promise = new Promise((resolve) => - loadPreselectedLocationData({ startingLocationId: this.props.startingLocationId, locationId }, resolve) - ); - - promise.then((response) => { - this.createPreselectedLocationData(response); - this.preselectedItem = response.locations[locationId].Location; - this.setPreselectedLocationData(); - }); - } - - /** - * Create preselected location data. - * - * @method createPreselectedLocationData - * @param {Object} params - * @param {Array} params.locations - * @param {Array} params.subitems - * @memberof FinderComponent - */ - createPreselectedLocationData({ locations, subitems }) { - const createItem = ({ offset = 0, children, location }) => { - const createData = (items) => { - return items.locations.map((value) => { - return { value }; - }); - }; - const data = children ? createData(children) : []; - const count = children ? children.totalCount : location.childCount; - - return { count, data, offset, location }; - }; - - if (subitems[1]) { - const item = createItem({ children: subitems[1], location: ROOT_LOCATION_OBJECT }); - - this.locationsMap[this.props.startingLocationId] = item; - this.activeLocations[0] = ROOT_LOCATION_OBJECT; - } - - Object.entries(locations).forEach(([key, value]) => { - const item = createItem({ - children: subitems[key], - location: value.Location, +const FinderComponent = (props) => { + const { startingLocationId, allowedLocations, maxHeight, allowContainersOnly, multiple, selectedContent } = props; + const { onItemSelect, checkCanSelectContent, onItemDeselect, onItemMarked } = props; + const [locationsMap, setLocationsMap] = useState({}); + const [activeLocations, setActiveLocations] = useState([]); + const refBranchesContainer = useRef(); + const setDefaultActiveLocations = () => setActiveLocations([ROOT_LOCATION_OBJECT]); + const setLocationData = useCallback( + (locationData) => { + setLocationsMap((prevLocationsMap) => { + const { location } = locationData; + const locationId = location ? location.id : startingLocationId; + const locationsMap = { ...deepClone(prevLocationsMap), [locationId]: locationData }; + + return locationsMap; }); - - this.locationsMap[key] = item; - this.activeLocations[value.Location.depth] = item.location; - }); - } - - /** - * Sets preselected location data. - * - * @method setPreselectedLocationData - * @memberof FinderComponent - */ - setPreselectedLocationData() { - this.setState(this.getPreselectedState, () => this.props.isVisible && this.props.onItemSelect(this.preselectedItem)); - } - - /** - * Updates locations based state attributes: activeLocations and locationsMap - * - * @method updateLocationsData - * @param {Object} params params hash containing: parentLocationId, data and offset properties - * @memberof FinderComponent - */ - updateLocationsData({ data, offset }, location = null) { - this.setLocationData({ - location, - data: data.View.Result.searchHits.searchHit, - count: data.View.Result.count, - offset, - }); - } - - setDefaultActiveLocations() { - this.setState(() => ({ activeLocations: [ROOT_LOCATION_OBJECT] })); - } - - setLocationData(locationData) { - this.setState((state, props) => { - const { location } = locationData; - const locationId = location ? location.id : props.startingLocationId; - const locationsMap = { ...deepClone(state.locationsMap), [locationId]: locationData }; - - return { locationsMap }; - }); - } - - /** - * Updates the left scroll position of branches container - * - * @method updateBranchesContainerScroll - * @memberof FinderComponent - */ - updateBranchesContainerScroll() { - const container = this._refBranchesContainer; + }, + [startingLocationId] + ); + const updateBranchesContainerScroll = () => { + const container = refBranchesContainer.current; if (container) { container.scrollLeft = container.scrollWidth - container.clientWidth; } - } - - /** - * Handles loading more items for a selected parent location - * - * @method onLoadMore - * @param {Object} parentLocation - * @memberof FinderComponent - */ - onLoadMore(parentLocation) { - const limit = this.state.limit; - const parentLocationId = parentLocation ? parentLocation.id : this.props.startingLocationId; - const offset = this.state.locationsMap[parentLocationId].offset + limit; - const sortClauses = parentLocation ? this.getLocationSortClauses(parentLocation) : {}; - - findLocationsByParentLocationId({ ...restInfo, parentLocationId, limit, offset, sortClauses }, this.appendMoreItems); - } - - /** - * Appends more subitems for a selected location - * - * @method appendMoreItems - * @param {Object} response object containing information about: parentLocationId, offset and data - * @memberof FinderComponent - */ - appendMoreItems({ parentLocationId, offset, data }) { - this.setState((state) => { - const locationsMap = deepClone(state.locationsMap); + }; + const onLoadMore = (parentLocation) => { + const parentLocationId = parentLocation ? parentLocation.id : startingLocationId; + const offset = locationsMap[parentLocationId].offset + QUERY_LIMIT; + const sortClauses = parentLocation ? getLocationSortClauses(parentLocation) : {}; + + findLocationsByParentLocationId({ ...restInfo, parentLocationId, QUERY_LIMIT, offset, sortClauses }, appendMoreItems); + }; + const appendMoreItems = ({ parentLocationId, offset, data }) => { + setLocationsMap((prevLocationsMap) => { + const locationsMap = deepClone(prevLocationsMap); const locationData = locationsMap[parentLocationId]; locationData.offset = offset; locationData.data = [...locationData.data, ...data.View.Result.searchHits.searchHit]; - return { locationsMap }; + return locationsMap; }); - } - - /** - * Loads branch children (sub-items) - * - * @method loadBranchLeaves - * @param {Object} parentLocation - * @memberof FinderComponent - */ - loadBranchLeaves(parentLocation) { - const { startingLocationId } = this.props; - const sortClauses = this.getLocationSortClauses(parentLocation); + }; + const loadBranchLeaves = (parentLocation) => { + const sortClauses = getLocationSortClauses(parentLocation); const promise = new Promise((resolve) => findLocationsByParentLocationId( { @@ -235,112 +64,73 @@ export default class FinderComponent extends Component { ) ); - promise.then((response) => { - this.updateLocationsData(response, parentLocation); - }); - } - - /** - * Generates sort clause for location - * - * @method getLocationSortClauses - * @param {Object} location - * @returns {Object} sortClauses for given location - * @memberof FinderComponent - */ - getLocationSortClauses(location) { - const { sortFieldMappings, sortOrderMappings } = this.props; - const sortField = sortFieldMappings[location.sortField]; - const sortOrder = sortOrderMappings[location.sortOrder]; + promise.then((response) => updateLocationsData(response, parentLocation)); + }; + const updateLocationsData = useCallback( + ({ data, offset }, location = null) => { + setLocationData({ + location, + data: data.View.Result.searchHits.searchHit, + count: data.View.Result.count, + offset, + }); + }, + [setLocationData] + ); + const getLocationSortClauses = (location) => { + const sortField = window.eZ.adminUiConfig.sortFieldMappings[location.sortField]; + const sortOrder = window.eZ.adminUiConfig.sortOrderMappings[location.sortOrder]; if (!sortField || !sortOrder) { return {}; } return { [sortField]: sortOrder }; - } - - /** - * Finds location children (sub-items) - * - * @method findLocationChildren - * @param {Object} location - * @memberof FinderComponent - */ - findLocationChildren(location) { - if (this.props.allowedLocations.length === 1) { + }; + const findLocationChildren = (location) => { + if (allowedLocations.length === 1) { return; } - this.props.onItemMarked(location); - this.updateSelectedBranches(location); + onItemMarked(location); + updateSelectedBranches(location); if (!location.childCount) { - this.setLocationData({ location, data: [], count: 0, offset: 0 }); + setLocationData({ location, data: [], count: 0, offset: 0 }); return; } - const sortClauses = this.getLocationSortClauses(location); const promise = new Promise((resolve) => findLocationsByParentLocationId( { ...restInfo, parentLocationId: location.id, - sortClauses, + sortClauses: getLocationSortClauses(location), }, resolve ) ); - promise.then((response) => { - this.updateLocationsData(response, location); - }); - } - - /** - * Updates selected branches state - * - * @param {Object} location location struct - * @memberof FinderComponent - */ - updateSelectedBranches(location) { - this.setState(this.updateActiveLocations.bind(this, location)); - } - - /** - * Updates active locations info - * - * @method updateActiveLocations - * @param {Object} location location struct - * @param {Object} state component state - * @returns {Object} - * @memberof FinderComponent - */ - updateActiveLocations(location, state) { - const locationDepth = parseInt(location.depth, 10); - const activeLocations = state.activeLocations.slice(0, locationDepth); + promise.then((response) => updateLocationsData(response, location)); + }; + const updateSelectedBranches = (location) => { + setActiveLocations((prevActiveLocations) => { + const locationDepth = parseInt(location.depth, 10); + const activeLocations = prevActiveLocations.slice(0, locationDepth); - activeLocations[locationDepth] = location; + activeLocations[locationDepth] = location; - return { activeLocations }; - } - - /** - * Renders branch (the sub items container) - * - * @method renderBranch - * @param {Object} locationData params hash containing: parent, count and data properties - * @returns {JSX.Element|null} - * @memberof FinderComponent - */ - renderBranch(locationData, branchActiveLocationId, isBranchActiveLocationLoading) { + return activeLocations; + }); + }; + const renderBranch = (locationData, branchActiveLocationId, isBranchActiveLocationLoading) => { if (!locationData || !locationData.count) { return null; } const { data: childrenData, count, location } = locationData; - const locationId = location ? location.id : this.props.startingLocationId; + const locationId = location ? location.id : startingLocationId; return ( ); - } + }; - setBranchContainerRef(ref) { - this._refBranchesContainer = ref; - } + useEffect(() => { + setDefaultActiveLocations(); + findLocationsByParentLocationId({ ...restInfo, parentLocationId: startingLocationId }, updateLocationsData); + }, [startingLocationId, updateLocationsData]); - render() { - const { activeLocations } = this.state; + useEffect(() => { + updateBranchesContainerScroll(); + }); - if (!activeLocations.length) { - return null; - } - - const { locationsMap } = this.state; + if (!activeLocations.length) { + return null; + } - return ( -
-
- {activeLocations.map((location, index) => { - const locationId = location ? location.id : this.props.startingLocationId; - const branchActiveLocation = activeLocations[index + 1]; - const branchActiveLocationId = branchActiveLocation ? branchActiveLocation.id : null; - const isBranchActiveLocationLoading = branchActiveLocationId && !locationsMap[branchActiveLocationId]; - const locationData = locationsMap[locationId]; + return ( +
+
+ {activeLocations.map((location, index) => { + const locationId = location ? location.id : startingLocationId; + const branchActiveLocation = activeLocations[index + 1]; + const branchActiveLocationId = branchActiveLocation ? branchActiveLocation.id : null; + const isBranchActiveLocationLoading = branchActiveLocationId && !locationsMap[branchActiveLocationId]; + const locationData = locationsMap[locationId]; - return this.renderBranch(locationData, branchActiveLocationId, isBranchActiveLocationLoading); - })} -
+ return renderBranch(locationData, branchActiveLocationId, isBranchActiveLocationLoading); + })}
- ); - } -} +
+ ); +}; FinderComponent.propTypes = { multiple: PropTypes.bool.isRequired, @@ -403,11 +192,7 @@ FinderComponent.propTypes = { onItemSelect: PropTypes.func.isRequired, startingLocationId: PropTypes.number.isRequired, allowContainersOnly: PropTypes.bool, - preselectedLocation: PropTypes.number, allowedLocations: PropTypes.array, - isVisible: PropTypes.bool, - sortFieldMappings: PropTypes.object, - sortOrderMappings: PropTypes.object, selectedContent: PropTypes.array.isRequired, onItemMarked: PropTypes.func.isRequired, checkCanSelectContent: PropTypes.func.isRequired, @@ -417,8 +202,7 @@ FinderComponent.propTypes = { FinderComponent.defaultProps = { allowedLocations: [], allowContainersOnly: false, - preselectedLocation: null, isVisible: true, - sortFieldMappings: window.eZ.adminUiConfig.sortFieldMappings, - sortOrderMappings: window.eZ.adminUiConfig.sortOrderMappings, }; + +export default FinderComponent;