From e6801de22386f0b5cb2920da3173052ef10405b4 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Mon, 3 Oct 2016 09:17:53 -0700 Subject: [PATCH 1/6] First pass at wiring up reduxified country states. See #7922. --- .../upgrades/checkout/domain-details-form.jsx | 5 +- .../upgrades/components/form/state-select.jsx | 92 ++++++++++++------- .../edit-contact-info/form-card.jsx | 2 - 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/client/my-sites/upgrades/checkout/domain-details-form.jsx b/client/my-sites/upgrades/checkout/domain-details-form.jsx index c4150f454489b..4da04edbf67d8 100644 --- a/client/my-sites/upgrades/checkout/domain-details-form.jsx +++ b/client/my-sites/upgrades/checkout/domain-details-form.jsx @@ -16,7 +16,6 @@ import PrivacyProtection from './privacy-protection'; import PaymentBox from './payment-box'; import { cartItems } from 'lib/cart-values'; import { forDomainRegistrations as countriesListForDomainRegistrations } from 'lib/countries-list'; -import { forDomainRegistrations as statesListForDomainRegistrations } from 'lib/states-list'; import analytics from 'lib/analytics'; import formState from 'lib/form-state'; import { addPrivacyToAllDomains, removePrivacyFromAllDomains, setDomainDetails } from 'lib/upgrades/actions'; @@ -24,8 +23,7 @@ import FormButton from 'components/forms/form-button'; // Cannot convert to ES6 import const wpcom = require( 'lib/wp' ).undocumented(), - countriesList = countriesListForDomainRegistrations(), - statesList = statesListForDomainRegistrations(); + countriesList = countriesListForDomainRegistrations(); export default React.createClass( { displayName: 'DomainDetailsForm', @@ -238,7 +236,6 @@ export default React.createClass( { diff --git a/client/my-sites/upgrades/components/form/state-select.jsx b/client/my-sites/upgrades/components/form/state-select.jsx index bcc246c350322..57b0dd67d2912 100644 --- a/client/my-sites/upgrades/components/form/state-select.jsx +++ b/client/my-sites/upgrades/components/form/state-select.jsx @@ -1,32 +1,32 @@ /** * External dependencies */ -const React = require( 'react' ), - classNames = require( 'classnames' ), - isEmpty = require( 'lodash/isEmpty' ), - ReactDom = require( 'react-dom' ), - observe = require( 'lib/mixins/data-observe' ); +import React, { Component, PropTypes } from 'react'; +import classNames from 'classnames'; +import { connect } from 'react-redux'; +import isEmpty from 'lodash/isEmpty'; +import { localize } from 'i18n-calypso'; +import ReactDom from 'react-dom'; /** * Internal dependencies */ -const analytics = require( 'lib/analytics' ), - FormLabel = require( 'components/forms/form-label' ), - FormSelect = require( 'components/forms/form-select' ), - FormInputValidation = require( 'components/forms/form-input-validation' ), - scrollIntoViewport = require( 'lib/scroll-into-viewport' ), - Input = require( './input' ); +import analytics from 'lib/analytics'; +import FormLabel from 'components/forms/form-label'; +import FormSelect from 'components/forms/form-select'; +import FormInputValidation from 'components/forms/form-input-validation'; +import { getCountryStates } from 'state/country-states/selectors'; +import Input from './input'; +import QueryCountryStates from 'components/data/query-country-states'; +import scrollIntoViewport from 'lib/scroll-into-viewport'; -module.exports = React.createClass( { - displayName: 'StateSelect', +class StateSelect extends Component { - mixins: [ observe( 'statesList' ) ], - - recordStateSelectClick: function() { + recordStateSelectClick() { if ( this.props.eventFormName ) { analytics.ga.recordEvent( 'Upgrades', `Clicked ${ this.props.eventFormName } State Select` ); } - }, + } focus() { const node = ReactDom.findDOMNode( this.refs.input ); @@ -36,27 +36,24 @@ module.exports = React.createClass( { } else { this.refs.state.focus(); } - }, + } + + query() { + return this.props.countryCode && ; + } + + render() { + const classes = classNames( this.props.additionalClasses, 'state' ); - render: function() { - const classes = classNames( this.props.additionalClasses, 'state' ), - statesList = this.props.statesList.getByCountry( this.props.countryCode ); - let options = []; + this.query(); - if ( isEmpty( statesList ) ) { + if ( isEmpty( this.props.countryStates ) ) { return ( ); } - options.push( { key: '', label: this.translate( 'Select State' ), disabled: 'disabled' } ); - - options = options.concat( statesList.map( function( state ) { - if ( ! state.code ) { - return { key: '--', label: '', disabled: 'disabled' }; - } - return { key: state.code, label: state.name }; - } ) ); + this.props.countryStates.push( { code: '', name: this.translate( 'Select State' ) } ); return (
@@ -70,8 +67,15 @@ module.exports = React.createClass( { onChange={ this.props.onChange } onClick={ this.recordStateSelectClick } isError={ this.props.isError } > - { options.map( function( option ) { - return ; + { this.props.countryStates.map( ( state ) => { + let disabled; + + if ( ! state.code ) { + state.code = '--'; + disabled = 'disabled'; + } + + return ; } ) }
@@ -79,4 +83,24 @@ module.exports = React.createClass( { ); } -} ); +} + +StateSelect.propTypes = { + additionalClasses: PropTypes.string, + countryCode: PropTypes.string, + countryStates: PropTypes.array, + diabled: PropTypes.bool, + errorMessage: PropTypes.string, + eventFormName: PropTypes.string, + isError: PropTypes.bool, + label: PropTypes.string, + name: PropTypes.string, + onChange: PropTypes.func, + value: PropTypes.string, +}; + +module.exports = connect( + ( state, { countryCode } ) => ( { + countryStates: countryCode ? getCountryStates( state, countryCode ) : [] + } ) +)( localize( StateSelect ) ); diff --git a/client/my-sites/upgrades/domain-management/edit-contact-info/form-card.jsx b/client/my-sites/upgrades/domain-management/edit-contact-info/form-card.jsx index 5b6dfa5e108ac..128e1ed1526ba 100644 --- a/client/my-sites/upgrades/domain-management/edit-contact-info/form-card.jsx +++ b/client/my-sites/upgrades/domain-management/edit-contact-info/form-card.jsx @@ -22,7 +22,6 @@ const Card = require( 'components/card' ), formState = require( 'lib/form-state' ), notices = require( 'notices' ), paths = require( 'my-sites/upgrades/paths' ), - statesList = require( 'lib/states-list' ).forDomainRegistrations(), upgradesActions = require( 'lib/upgrades/actions' ), wpcom = require( 'lib/wp' ).undocumented(), successNotice = require( 'state/notices/actions' ).successNotice; @@ -177,7 +176,6 @@ const EditContactInfoFormCard = React.createClass( { } ) } { this.getField( FormStateSelect, { countryCode: formState.getFieldValue( this.state.form, 'countryCode' ), - statesList, name: 'state', label: this.translate( 'State', { context: 'Domain Edit Contact Info form.', From 8e0e9f24617bf148aa0019db4f0c50729ffa3457 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 4 Oct 2016 15:06:32 -0700 Subject: [PATCH 2/6] Second pass at wiring up reduxified country states. * Uses Redux based Google Analytics. * Moves `QueryCountryStates` inline. * Removes `lib/states-list`. --- client/lib/states-list/README.md | 28 ---- client/lib/states-list/index.js | 152 ------------------ .../upgrades/components/form/state-select.jsx | 67 +++----- 3 files changed, 26 insertions(+), 221 deletions(-) delete mode 100644 client/lib/states-list/README.md delete mode 100644 client/lib/states-list/index.js diff --git a/client/lib/states-list/README.md b/client/lib/states-list/README.md deleted file mode 100644 index 1635da44cdf37..0000000000000 --- a/client/lib/states-list/README.md +++ /dev/null @@ -1,28 +0,0 @@ -States List -============== - -The `states-list` module provides access to localized list of states as returned from the REST API. These lists are ordered alphabetically. - -## Usage - -The list of supported states for domain registrations can be retrieved with: - -```js -var statesList = require( 'lib/states-list' ).forDomainRegistrations(); -``` - -## Methods - -The following public methods are available: - -### `fetchForCountry( countryCode )` - -This fetches the corresponding list of states from the server. The list is cached in the store upon retrieval. - -### `getByCountry( countryCode )` - -This retrieves the list of states as a set of key and value pairs. The list is loaded from the store and then fetched once from the server to update any stale data. - -### `hasLoadedFromServer()` - -This determines whether the list of states has already been loaded from the server or not. diff --git a/client/lib/states-list/index.js b/client/lib/states-list/index.js deleted file mode 100644 index 7387c19be349b..0000000000000 --- a/client/lib/states-list/index.js +++ /dev/null @@ -1,152 +0,0 @@ -/** - * External dependencies - */ -var debug = require( 'debug' )( 'calypso:StatesList' ), - inherits = require( 'inherits' ), - isEmpty = require( 'lodash/isEmpty' ), - store = require( 'store' ); - -/** - * Internal dependencies - */ -var Emitter = require( 'lib/mixins/emitter' ), - wpcom = require( 'lib/wp' ).undocumented(); - -/** - * Initializes a new list of states. - * - * @constructor - * @param {string} key - key used to identify this list in the store or the debug messages - */ -function StatesList( key ) { - if ( ! ( this instanceof StatesList ) ) { - return new StatesList( key ); - } - - this.key = key + 'StatesList'; - this.initialized = false; -} - -/** - * Adds event capabilities to this list of states. - */ -Emitter( StatesList.prototype ); - -/** - * Fetches the list of states for the specified country from the server. - * - * @param {string} countryCode - country code - */ -StatesList.prototype.fetchForCountry = function( countryCode ) { - if ( ! this.isFetching && ! isEmpty( countryCode ) ) { - debug( 'Fetching ' + this.key + ' for ' + countryCode + ' from api' ); - - this.isFetching = true; - - // Sends a request to the API endpoint defined in the subclass - this.requestFromEndpoint( countryCode, function( error, data ) { - var statesList; - - if ( error ) { - debug( 'Unable to fetch ' + this.key + ' for ' + countryCode + ' from api', error ); - - return; - } - - statesList = data; - - debug( this.key + ' for ' + countryCode + ' fetched from api successfully:', statesList ); - - if ( ! this.initialized ) { - var statesLists = {}; - statesLists[ countryCode ] = statesList; - - this.initialize( statesLists ); - } else { - this.data[ countryCode ] = statesList; - } - - this.isFetching = false; - - this.emit( 'change' ); - - store.set( this.key, this.data ); - }.bind( this ) ); - } -}; - -/** - * Retrieves the list of states as a set of key and value pairs. This list will be loaded from the store and then - * fetched once from the server to update any stale data. - * - * @param {string} countryCode - country code - * @returns {object} the list of states - */ -StatesList.prototype.getByCountry = function( countryCode ) { - var data; - - if ( ! this.data ) { - data = store.get( this.key ); - - if ( data ) { - debug( 'Loaded ' + this.key + ' from store', data ); - - this.initialize( data ); - } else { - this.data = {}; - } - - this.fetchForCountry( countryCode ); - } else if ( ! ( countryCode in this.data ) ) { - this.fetchForCountry( countryCode ); - } - - if ( countryCode in this.data ) { - return this.data[ countryCode ]; - } else { - return null; - } -}; - -/** - * Determines whether this list of states has already been loaded from the server or not. - * - * @return {boolean} true if this list of states has been loaded, false otherwise - */ -StatesList.prototype.hasLoadedFromServer = function() { - return this.initialized; -}; - -/** - * Initializes this list of states with the specified data. - * - * @param {object} data - data - */ -StatesList.prototype.initialize = function( data ) { - this.data = data; - - this.initialized = true; -}; - -/** - * Initializes a new list of states for domain registrations. - * - * @constructor - */ -function DomainRegistrationStatesList() { - StatesList.call( this, 'DomainRegistration' ); -} - -inherits( DomainRegistrationStatesList, StatesList ); - -DomainRegistrationStatesList.prototype.requestFromEndpoint = function( countryCode, fn ) { - return wpcom.getDomainRegistrationSupportedStates( countryCode, fn ); -}; - -var domainRegistrationStatesList = new DomainRegistrationStatesList(); - -module.exports = { - forDomainRegistrations: function() { - return domainRegistrationStatesList; - } -}; diff --git a/client/my-sites/upgrades/components/form/state-select.jsx b/client/my-sites/upgrades/components/form/state-select.jsx index 57b0dd67d2912..4e67afb80e255 100644 --- a/client/my-sites/upgrades/components/form/state-select.jsx +++ b/client/my-sites/upgrades/components/form/state-select.jsx @@ -11,20 +11,20 @@ import ReactDom from 'react-dom'; /** * Internal dependencies */ -import analytics from 'lib/analytics'; import FormLabel from 'components/forms/form-label'; import FormSelect from 'components/forms/form-select'; import FormInputValidation from 'components/forms/form-input-validation'; import { getCountryStates } from 'state/country-states/selectors'; import Input from './input'; import QueryCountryStates from 'components/data/query-country-states'; +import { recordGoogleEvent } from 'state/analytics/actions'; import scrollIntoViewport from 'lib/scroll-into-viewport'; class StateSelect extends Component { - recordStateSelectClick() { - if ( this.props.eventFormName ) { - analytics.ga.recordEvent( 'Upgrades', `Clicked ${ this.props.eventFormName } State Select` ); + recordStateSelectClick( eventFormName ) { + if ( eventFormName ) { + recordGoogleEvent( 'Upgrades', `Clicked ${ eventFormName } State Select` ); } } @@ -38,47 +38,32 @@ class StateSelect extends Component { } } - query() { - return this.props.countryCode && ; - } - render() { const classes = classNames( this.props.additionalClasses, 'state' ); - this.query(); - - if ( isEmpty( this.props.countryStates ) ) { - return ( - - ); - } - - this.props.countryStates.push( { code: '', name: this.translate( 'Select State' ) } ); - return ( -
-
- { this.props.label } - - { this.props.countryStates.map( ( state ) => { - let disabled; - - if ( ! state.code ) { - state.code = '--'; - disabled = 'disabled'; - } +
+ { this.props.countryCode && } + { isEmpty( this.props.countryStates ) + ? + :
+ { this.props.label } + - return ; - } ) } - -
+ + { this.props.countryStates.map( ( state ) => + + ) } + +
+ } { this.props.errorMessage && }
); @@ -89,7 +74,7 @@ StateSelect.propTypes = { additionalClasses: PropTypes.string, countryCode: PropTypes.string, countryStates: PropTypes.array, - diabled: PropTypes.bool, + disabled: PropTypes.bool, errorMessage: PropTypes.string, eventFormName: PropTypes.string, isError: PropTypes.bool, From 717a78483cb5b7a00e4271b80d6f550381034d20 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Wed, 5 Oct 2016 11:36:07 -0700 Subject: [PATCH 3/6] Import `isEmpty` from root module directly. See #6539. See https://github.com/Automattic/wp-calypso/pull/8414/files#r81896999 --- client/my-sites/upgrades/components/form/state-select.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/my-sites/upgrades/components/form/state-select.jsx b/client/my-sites/upgrades/components/form/state-select.jsx index 4e67afb80e255..609e6735d5a08 100644 --- a/client/my-sites/upgrades/components/form/state-select.jsx +++ b/client/my-sites/upgrades/components/form/state-select.jsx @@ -4,7 +4,7 @@ import React, { Component, PropTypes } from 'react'; import classNames from 'classnames'; import { connect } from 'react-redux'; -import isEmpty from 'lodash/isEmpty'; +import { isEmpty } from 'lodash'; import { localize } from 'i18n-calypso'; import ReactDom from 'react-dom'; From 6812e8ce69868f222470a13430e188b2a195ae66 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Wed, 5 Oct 2016 13:04:45 -0700 Subject: [PATCH 4/6] Properly bind and dispatch state select click. See https://github.com/Automattic/wp-calypso/pull/8414/files#r81897352 --- .../upgrades/components/form/state-select.jsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/client/my-sites/upgrades/components/form/state-select.jsx b/client/my-sites/upgrades/components/form/state-select.jsx index 609e6735d5a08..010de57fe7d7e 100644 --- a/client/my-sites/upgrades/components/form/state-select.jsx +++ b/client/my-sites/upgrades/components/form/state-select.jsx @@ -22,11 +22,13 @@ import scrollIntoViewport from 'lib/scroll-into-viewport'; class StateSelect extends Component { - recordStateSelectClick( eventFormName ) { + recordStateSelectClick = () => { + const { eventFormName, recordGoogleEvent: recordEvent } = this.props; + if ( eventFormName ) { - recordGoogleEvent( 'Upgrades', `Clicked ${ eventFormName } State Select` ); + recordEvent( 'Upgrades', `Clicked ${ eventFormName } State Select` ); } - } + }; focus() { const node = ReactDom.findDOMNode( this.refs.input ); @@ -54,7 +56,7 @@ class StateSelect extends Component { value={ this.props.value } disabled={ this.props.disabled } onChange={ this.props.onChange } - onClick={ this.recordStateSelectClick.bind( this, this.props.eventFormName ) } + onClick={ this.recordStateSelectClick } isError={ this.props.isError } > @@ -84,8 +86,9 @@ StateSelect.propTypes = { value: PropTypes.string, }; -module.exports = connect( +export default connect( ( state, { countryCode } ) => ( { countryStates: countryCode ? getCountryStates( state, countryCode ) : [] - } ) + } ), + { recordGoogleEvent } )( localize( StateSelect ) ); From d5f0f902063aaf14b35653777a9e2fd4e53d377f Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Wed, 5 Oct 2016 13:13:27 -0700 Subject: [PATCH 5/6] Add id attribute so `FormLabel` works with the element. See https://github.com/Automattic/wp-calypso/pull/8414#discussion_r81897484 --- client/my-sites/upgrades/components/form/state-select.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/my-sites/upgrades/components/form/state-select.jsx b/client/my-sites/upgrades/components/form/state-select.jsx index 010de57fe7d7e..2dbc8c3e35eee 100644 --- a/client/my-sites/upgrades/components/form/state-select.jsx +++ b/client/my-sites/upgrades/components/form/state-select.jsx @@ -52,6 +52,7 @@ class StateSelect extends Component { { this.props.label } Date: Wed, 5 Oct 2016 13:55:24 -0700 Subject: [PATCH 6/6] Use a unique id for `FormLabel` See https://github.com/Automattic/wp-calypso/pull/8414#discussion_r82059804 --- .../my-sites/upgrades/components/form/state-select.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/my-sites/upgrades/components/form/state-select.jsx b/client/my-sites/upgrades/components/form/state-select.jsx index 2dbc8c3e35eee..3eeb6ad2b8b6f 100644 --- a/client/my-sites/upgrades/components/form/state-select.jsx +++ b/client/my-sites/upgrades/components/form/state-select.jsx @@ -21,6 +21,11 @@ import { recordGoogleEvent } from 'state/analytics/actions'; import scrollIntoViewport from 'lib/scroll-into-viewport'; class StateSelect extends Component { + static instances = 0; + + componentWillMount() { + this.instance = ++this.constructor.instances; + } recordStateSelectClick = () => { const { eventFormName, recordGoogleEvent: recordEvent } = this.props; @@ -49,10 +54,10 @@ class StateSelect extends Component { { isEmpty( this.props.countryStates ) ? :
- { this.props.label } + { this.props.label }