From 5a6f2361cdaa93902c24c7f1624ac7bd8c75fe60 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Wed, 1 Aug 2018 14:49:38 +0300 Subject: [PATCH 1/4] Reusable map container --- .../SearchMap/ReusableMapContainer.js | 90 +++++++++++++++++++ src/components/SearchMap/SearchMap.css | 31 +++++++ src/components/SearchMap/SearchMap.js | 48 +++++----- src/containers/SearchPage/SearchPage.css | 5 ++ src/containers/SearchPage/SearchPage.js | 3 +- 5 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 src/components/SearchMap/ReusableMapContainer.js diff --git a/src/components/SearchMap/ReusableMapContainer.js b/src/components/SearchMap/ReusableMapContainer.js new file mode 100644 index 00000000..d9a4872c --- /dev/null +++ b/src/components/SearchMap/ReusableMapContainer.js @@ -0,0 +1,90 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { node, string } from 'prop-types'; +import css from './SearchMap.css'; + +class ReusableMapContainer extends React.Component { + constructor(props) { + super(props); + + if (typeof window !== 'undefined') { + window.reusableSearchMapElement = + window.reusableSearchMapElement || document.createElement('div'); + + if (!props.className) { + console.warn('ReusableMapContainer should get className prop which defines its layout'); + } + // If no className is given, we use some defaults, which makes it easier to debug loading. + const mapLayoutClassName = props.className || css.defaultMapLayout; + + this.el = window.reusableSearchMapElement; + this.el.id = 'search-map'; + this.el.classList.add(mapLayoutClassName); + } + + this.mountNode = null; + this.renderSearchMap = this.renderSearchMap.bind(this); + } + + componentDidMount() { + this.renderSearchMap(); + } + + componentDidUpdate() { + this.renderSearchMap(); + } + + componentWillUnmount() { + this.el.classList.add(css.reusableMapHidden); + this.mountNode.removeChild(this.el); + document.body.appendChild(this.el); + } + + renderSearchMap() { + const targetDomNode = document.getElementById(this.el.id); + + // Check if we have already added map somewhere on the DOM + if (!targetDomNode) { + if (this.mountNode && !this.mountNode.firstChild) { + // If mountable, but not yet mounted, append rendering context inside SPA rendering tree. + this.mountNode.appendChild(this.el); + } else if (!this.mountNode) { + // if no mountNode is found, append this outside SPA rendering tree (to document body) + document.body.appendChild(this.el); + } + } else { + this.el.classList.remove(css.reusableMapHidden); + + if (this.mountNode && !this.mountNode.firstChild) { + // Move the map to correct location if we have rendered the map before + // (but it's not yet moved to correct location of rendering tree). + document.body.removeChild(this.el); + this.mountNode.appendChild(this.el); + } + } + + ReactDOM.render(this.props.children, this.el); + } + + render() { + return ( +
{ + this.mountNode = node; + }} + /> + ); + } +} + +ReusableMapContainer.defaultProps = { + className: string, +}; + +ReusableMapContainer.propTypes = { + children: node.isRequired, + className: string, +}; + +export default ReusableMapContainer; diff --git a/src/components/SearchMap/SearchMap.css b/src/components/SearchMap/SearchMap.css index ea329fba..36a15a43 100644 --- a/src/components/SearchMap/SearchMap.css +++ b/src/components/SearchMap/SearchMap.css @@ -10,3 +10,34 @@ width: 100%; height: 100%; } + +.reusableMap { + width: 100%; + height: 100%; +} + +.defaultMapLayout { + position: fixed; + top: 0; + right: 0; + width: 50vw; + height: 100vh; +} + +/** + * When reusable map is attached right to the body it's hidden. + * Specificity rule is added to overwrite positioning coming from props.className + */ +body > .reusableMapHidden { + position: absolute; + top: -1000px; + left: -1000px; + visibility: hidden; + opacity: 0; + + @media (--viewportMedium) { + top: -1000px; + left: -1000px; + right: auto; + } +} diff --git a/src/components/SearchMap/SearchMap.js b/src/components/SearchMap/SearchMap.js index 3bcd7880..440c128c 100644 --- a/src/components/SearchMap/SearchMap.js +++ b/src/components/SearchMap/SearchMap.js @@ -12,6 +12,7 @@ import { googleBoundsToSDKBounds } from '../../util/googleMaps'; import { SearchMapInfoCard, SearchMapPriceLabel, SearchMapGroupLabel } from '../../components'; import config from '../../config'; +import ReusableMapContainer from './ReusableMapContainer'; import css from './SearchMap.css'; const LABEL_HANDLE = 'SearchMapLabel'; @@ -240,6 +241,7 @@ export class SearchMapComponent extends Component { const { className, rootClassName, + reuseableContainerClassName, center, isOpenOnModal, listings: originalListings, @@ -263,28 +265,30 @@ export class SearchMapComponent extends Component { // container element listens clicks so that opened SearchMapInfoCard can be closed /* eslint-disable jsx-a11y/no-static-element-interactions */ return isMapsLibLoaded ? ( - } - mapElement={
} - center={center} - isOpenOnModal={isOpenOnModal} - listings={listings} - activeListingId={activeListingId} - infoCardOpen={this.state.infoCardOpen} - onListingClicked={this.onListingClicked} - onMapLoad={this.onMapLoadHandler} - onIdle={() => { - if (this.googleMap) { - onIdle(this.googleMap); - } - }} - onCloseAsModal={() => { - if (onCloseAsModal) { - onCloseAsModal(); - } - }} - zoom={zoom} - /> + + } + mapElement={
} + center={center} + isOpenOnModal={isOpenOnModal} + listings={listings} + activeListingId={activeListingId} + infoCardOpen={this.state.infoCardOpen} + onListingClicked={this.onListingClicked} + onMapLoad={this.onMapLoadHandler} + onIdle={() => { + if (this.googleMap) { + onIdle(this.googleMap); + } + }} + onCloseAsModal={() => { + if (onCloseAsModal) { + onCloseAsModal(); + } + }} + zoom={zoom} + /> + ) : (
); diff --git a/src/containers/SearchPage/SearchPage.css b/src/containers/SearchPage/SearchPage.css index 5b1f2fad..0ac767f4 100644 --- a/src/containers/SearchPage/SearchPage.css +++ b/src/containers/SearchPage/SearchPage.css @@ -137,6 +137,11 @@ } } +.mapWrapper { + width: 100%; + height: 100%; +} + .map { width: 100vw; height: 100vh; diff --git a/src/containers/SearchPage/SearchPage.js b/src/containers/SearchPage/SearchPage.js index eaebf54e..a0714324 100644 --- a/src/containers/SearchPage/SearchPage.js +++ b/src/containers/SearchPage/SearchPage.js @@ -251,9 +251,10 @@ export class SearchPageComponent extends Component { showAsModalMaxWidth={MODAL_BREAKPOINT} onManageDisableScrolling={onManageDisableScrolling} > -
+
{shouldShowSearchMap ? ( Date: Wed, 1 Aug 2018 16:40:05 +0300 Subject: [PATCH 2/4] Add reuseableContainerClassName to proptypes definition and refactor the order --- src/components/SearchMap/SearchMap.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/SearchMap/SearchMap.js b/src/components/SearchMap/SearchMap.js index 440c128c..263959d7 100644 --- a/src/components/SearchMap/SearchMap.js +++ b/src/components/SearchMap/SearchMap.js @@ -297,31 +297,33 @@ export class SearchMapComponent extends Component { } SearchMapComponent.defaultProps = { + className: null, + rootClassName: null, + mapRootClassName: null, + reuseableContainerClassName: null, bounds: null, center: new sdkTypes.LatLng(0, 0), activeListingId: null, - className: null, isOpenOnModal: false, listings: [], - mapRootClassName: null, onCloseAsModal: null, - rootClassName: null, useLocationSearchBounds: true, zoom: 11, coordinatesConfig: config.coordinates, }; SearchMapComponent.propTypes = { + className: string, + rootClassName: string, + mapRootClassName: string, + reuseableContainerClassName: string, bounds: propTypes.latlngBounds, center: propTypes.latlng, activeListingId: propTypes.uuid, - className: string, isOpenOnModal: bool, listings: arrayOf(propTypes.listing), - mapRootClassName: string, onCloseAsModal: func, onIdle: func.isRequired, - rootClassName: string, useLocationSearchBounds: bool, // eslint-disable-line react/no-unused-prop-types zoom: number, coordinatesConfig: shape({ From 817d3be26d80331abc4df9ed9f8d8ea41ccb9854 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Wed, 1 Aug 2018 16:49:20 +0300 Subject: [PATCH 3/4] Update tests --- src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap index d44699f5..72a96aa7 100644 --- a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap +++ b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap @@ -116,6 +116,7 @@ exports[`SearchPageComponent matches snapshot 1`] = ` mapRootClassName={null} onCloseAsModal={[Function]} onIdle={[Function]} + reuseableContainerClassName={null} rootClassName={null} useLocationSearchBounds={true} zoom={11} From 53bd0b36861caf873ce5e07a280ec1b3ba7ac636 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Wed, 1 Aug 2018 15:04:04 +0300 Subject: [PATCH 4/4] Update Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d8a8dd..3c9d03dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ way to update this template, but currently, we follow a pattern: --- ## Upcoming version +* [change] Reusable SearchMap. + [#877](https://github.com/sharetribe/flex-template-web/pull/877) * [change] Use seeded random for client side coordinate obfuscation [#874](https://github.com/sharetribe/flex-template-web/pull/874)