Skip to content

Commit

Permalink
refactor(places/SearchBox): rewrite with enhanceElement and cleaner i…
Browse files Browse the repository at this point in the history
…nterfaces

BREAKING CHANGE: Input props are now under `inputProps`.

This will get passed in directly into the underlying `<input>` component. You can also override it with `inputStyle`, `inputClassName` and `inputPlaceholder`.

Before:

```js
<SearchBox
  placeholder="Customized your placeholder"
  style={INPUT_STYLE}
/>
```

After:

```js
<SearchBox
  inputPlaceholder="Customized your placeholder"
  inputStyle={INPUT_STYLE}
/>
```
  • Loading branch information
tomchentw committed Oct 4, 2016
1 parent cf5a1cb commit bc97f61
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 165 deletions.
2 changes: 2 additions & 0 deletions src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ export const INFO_WINDOW = `__SECRET_INFO_WINDOW_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
export const OVERLAY_VIEW = `__SECRET_OVERLAY_VIEW_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`;

export const DRAWING_MANAGER = `__SECRET_DRAWING_MANAGER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`;

export const SEARCH_BOX = `__SECRET_SEARCH_BOX_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`;
91 changes: 0 additions & 91 deletions src/lib/creators/SearchBoxCreator.js

This file was deleted.

5 changes: 0 additions & 5 deletions src/lib/eventLists/SearchBoxEventList.js

This file was deleted.

180 changes: 111 additions & 69 deletions src/lib/places/SearchBox.js
Original file line number Diff line number Diff line change
@@ -1,96 +1,138 @@
/* global google */
import _ from "lodash";

import {
default as React,
Component,
PropTypes,
} from "react";

import {
default as canUseDOM,
} from "can-use-dom";
MAP,
SEARCH_BOX,
} from "../constants";

import {
default as SearchBoxCreator,
searchBoxDefaultPropTypes,
searchBoxControlledPropTypes,
searchBoxEventPropTypes,
} from "./creators/SearchBoxCreator";

import GoogleMapHolder from "./creators/GoogleMapHolder";

/*
* Original author: @eyebraus
* Original PR: https://github.com/tomchentw/react-google-maps/pull/110
*/
export default class SearchBox extends Component {
static propTypes = {
// Uncontrolled default[props] - used only in componentDidMount
...searchBoxDefaultPropTypes,
// Controlled [props] - used in componentDidMount/componentDidUpdate
...searchBoxControlledPropTypes,
// Event [onEventName]
...searchBoxEventPropTypes,
}

static contextTypes = {
mapHolderRef: PropTypes.instanceOf(GoogleMapHolder),
}
addDefaultPrefixToPropTypes,
collectUncontrolledAndControlledProps,
default as enhanceElement,
} from "../enhanceElement";

import * as helpers from "../utils/SearchBoxHelper";

const controlledPropTypes = {
// NOTICE!!!!!!
//
// Only expose those with getters & setters in the table as controlled props.
//
// [].map.call($0.querySelectorAll("tr>td>code", function(it){ return it.textContent; })
// .filter(function(it){ return it.match(/^set/) && !it.match(/^setMap/); })
//
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#SearchBox
bounds: PropTypes.any,
inputProps: PropTypes.object,
inputStyle: PropTypes.object,
inputClassName: PropTypes.string,
};

const defaultUncontrolledPropTypes = addDefaultPrefixToPropTypes(controlledPropTypes);

const eventMap = {
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#SearchBox
// [].map.call($0.querySelectorAll("tr>td>code"), function(it){ return it.textContent; })
onPlacesChanged: `places_changed`,
};

const publicMethodMap = {
// Public APIs
//
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#SearchBox
//
// [].map.call($0.querySelectorAll("tr>td>code"), function(it){ return it.textContent; })
// .filter(function(it){ return it.match(/^get/) && !it.match(/Map$/); })
getBounds() { return this.state.searchBox.getBounds(); }
getBounds(searchBox) { return searchBox.getBounds(); },

getPlaces() { return this.state.searchBox.getPlaces(); }
getPlaces(searchBox) { return searchBox.getPlaces(); },
// END - Public APIs
//
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#SearchBox
};

state = {}
const controlledPropUpdaterMap = {
bounds(searchBox, bounds) { searchBox.setBounds(bounds); },
};

componentWillMount() {
if (!canUseDOM) {
return;
}
const { classes, style, placeholder, ...searchBoxProps } = this.props;
function getInstanceFromComponent(component) {
return component.state[SEARCH_BOX];
}

// Cannot create input via component - Google Maps will mess with React's internal state
// by detaching/attaching.
// Allow developers to style the "hidden element" via inputClasses.
const domEl = document.createElement(`input`);
domEl.className = classes;
domEl.type = `text`;
domEl.placeholder = placeholder;
export default _.flowRight(
React.createClass,
enhanceElement(getInstanceFromComponent, publicMethodMap, eventMap, controlledPropUpdaterMap),
)({
displayName: `SearchBox`,

Object.assign(domEl.style, style);
propTypes: {
...controlledPropTypes,
...defaultUncontrolledPropTypes,
controlPosition: PropTypes.any.isRequired,
inputProps: PropTypes.object,
inputStyle: PropTypes.object,
inputClassName: PropTypes.string,
inputPlaceholder: PropTypes.string,
},

const searchBox = SearchBoxCreator._createSearchBox(
domEl,
searchBoxProps
);
contextTypes: {
[MAP]: PropTypes.object,
},

componentWillMount() {
this._inputElement = helpers.createInputElement(this.props);
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#SearchBox
const searchBox = new google.maps.places.SearchBox(this._inputElement,
collectUncontrolledAndControlledProps(
defaultUncontrolledPropTypes,
controlledPropTypes,
this.props
)
);
this.setState({
inputElement: domEl,
searchBox,
[SEARCH_BOX]: searchBox,
});
}
},

componentDidMount() {
this._mountIndex = helpers.mountInputElementToControlPositionOnMap(
this._inputElement,
this.props.controlPosition,
this.context[MAP],
);
},

componentDidUpdate(prevProps) {
if (this.props.controlPosition !== prevProps.controlPosition) {
helpers.unmountInputElementFromControlPositionOnMap(
this._mountIndex,
prevProps.controlPosition,
this.context[MAP],
);
this._mountIndex = helpers.mountInputElementToControlPositionOnMap(
this._inputElement,
this.props.controlPosition,
this.context[MAP],
);
}
},

componentWillUnmount() {
if (this._mountIndex) {
helpers.unmountInputElementFromControlPositionOnMap(
this._mountIndex,
this.props.controlPosition,
this.context[MAP],
);
this._inputElement = null;
}
},

render() {
const { controlPosition } = this.props;
const { mapHolderRef } = this.context;

return this.state.searchBox ? (
<SearchBoxCreator
controlPosition={controlPosition}
inputElement={this.state.inputElement}
mapHolderRef={mapHolderRef}
searchBox={this.state.searchBox}
{...this.props}
>
{this.props.children}
</SearchBoxCreator>
) : (<noscript />);
}
}
return false;
},
});
34 changes: 34 additions & 0 deletions src/lib/utils/SearchBoxHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* global google */
import React from "react";

import {
render,
findDOMNode,
unmountComponentAtNode,
} from "react-dom";

export function createInputElement(props) {
const containerElement = document.createElement(`div`);
// Allow developers to style the "hidden element" via `props.inputProps` and `props.input{*}`.
const component = render((
<input
{...props.inputProps}
style={props.inputStyle}
className={props.inputClassName}
placeholder={props.inputPlaceholder}
/>
), containerElement);
// Cannot directly use the component - Google Maps will mess with React's internal state
// by detaching/attaching.
const inputEl = findDOMNode(component).cloneNode();
unmountComponentAtNode(containerElement);
return inputEl;
}

export function mountInputElementToControlPositionOnMap(inputEl, controlPosition, map) {
return map.controls[controlPosition].push(inputEl) - 1;
}

export function unmountInputElementFromControlPositionOnMap(index, controlPosition, map) {
return map.controls[controlPosition].removeAt(index);
}

0 comments on commit bc97f61

Please sign in to comment.