diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index 6be7fa2d..37750010 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -128,7 +128,14 @@ let Form = React.createClass({ const ref = this.refs[refName]; if (ref.state && 'value' in ref.state) { - data[ref.props.name] = ref.state.value; + // An input can also provide an `Object` as a value + // which we're going to merge with `data` (overwrites) + if(ref.state.value.constructor === Object) { + console.log(ref.state.value); + Object.assign(data, ref.state.value); + } else { + data[ref.props.name] = ref.state.value; + } } } diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 77f81a69..c4942f30 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -4,11 +4,13 @@ import React from 'react'; import Button from 'react-bootstrap/lib/Button'; -import ContractAgreementProperty from './property_contract_agreement'; -import InputTextAreaToggable from './input_textarea_toggable'; import Form from './form'; import Property from './property'; +import InputContractAgreementCheckbox from './input_contract_agreement_checkbox'; +import InputTextAreaToggable from './input_textarea_toggable'; + + import AscribeSpinner from '../ascribe_spinner'; import AclInformation from '../ascribe_buttons/acl_information'; @@ -110,12 +112,14 @@ let ConsignForm = React.createClass({ placeholder={getLangText('Enter a message...')} required /> - + + + diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 1381bf28..b8370016 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -1,23 +1,26 @@ 'use strict'; import React from 'react'; - import classnames from 'classnames'; import Button from 'react-bootstrap/lib/Button'; -import ContractAgreementProperty from './property_contract_agreement'; +import ContractAgreementListStore from '../../stores/contract_agreement_list_store'; + import Form from './form'; -import InputTextAreaToggable from './input_textarea_toggable'; -import InputDate from './input_date'; import Property from './property'; +import InputDate from './input_date'; +import InputTextAreaToggable from './input_textarea_toggable'; +import InputContractAgreementCheckbox from './input_contract_agreement_checkbox'; + import AscribeSpinner from '../ascribe_spinner'; import AclInformation from '../ascribe_buttons/acl_information'; -import { mergeOptions } from '../../utils/general_utils'; import { getLangText } from '../../utils/lang_utils'; +import { mergeOptions } from '../../utils/general_utils'; + let LoanForm = React.createClass({ propTypes: { @@ -57,6 +60,10 @@ let LoanForm = React.createClass({ }; }, + onChange(state) { + this.setState(state); + }, + handleEmailOnChange(event) { // event.target.value is the submitted email of the loanee this.setState({ @@ -69,10 +76,7 @@ let LoanForm = React.createClass({ }, getFormData() { - return mergeOptions( - this.props.id, - this.refs.contractAgreement.getFormDataForProperty() - ); + return this.props.id; }, getButtons() { @@ -190,13 +194,14 @@ let LoanForm = React.createClass({ placeholder={getLangText('Enter a message...')} required={showPersonalMessage}/> - this.refs.contractAgreement = ref} - createPublicContractAgreement={createPublicContractAgreement} - email={email} - embedClassName={'loan-form'} + + label={getLangText('Loan Contract')} + className="ascribe-property-collapsible-toggle"> + + 0) { + ContractAgreementListActions.flushContractAgreementList(); + } + } + }, + + componentWillUnmount() { + ContractAgreementListStore.unlisten(this.onStoreChange); + }, + + onStoreChange(state) { + const contractAgreement = this.getContractAgreement(state.contractAgreementList); + this.props.setExpanded(!!contractAgreement); + + state = mergeOptions(state, { + value: { + contract_agreement_id: contractAgreement ? contractAgreement.id : null, + terms: !contractAgreement || !!contractAgreement.datetime_accepted + } + }); + + this.setState(state); + }, + + onChange(event) { + this.setState({ + value: React.addons.update(this.state.value, { + terms: { $set: event.target.value } + }) + }); + this.props.onChange(event); + }, + + getContractAgreement(contractAgreementList) { + if (contractAgreementList && contractAgreementList.length > 0) { + return contractAgreementList[0]; + } + }, + + getContractAgreementsOrCreatePublic(email) { + ContractAgreementListActions.flushContractAgreementList.defer(); + + if (email) { + // fetch the available contractagreements (pending/accepted) + ContractAgreementListActions.fetchAvailableContractAgreementList(email, this.props.createPublicContractAgreement); + } + }, + + getAppendix() { + const { contractAgreementList } = this.state; + + if (contractAgreementList && contractAgreementList.length > 0) { + const appendix = contractAgreementList[0].appendix; + if (appendix && appendix.default) { + return ( +
{appendix.default}
+ ); + } + } + }, + + getContractCheckbox() { + const { name, + disabled, + style } = this.props; + const { contractAgreementList } = this.state; + const inputCheckboxProps = { + name, + disabled, + style, + onChange: this.onChange + }; + + if (contractAgreementList && contractAgreementList.length > 0) { + const contractAgreement = contractAgreementList[0]; + const { issuer: contractIssuer, blob: { url_safe: contractUrl } } = contractAgreement.contract; + + + if (contractAgreement.datetime_accepted) { + // For `InputCheckbox` we want to override style in this case + Object.assign(inputCheckboxProps, { style: { 'display': 'none' } }); + + return ( +
+ + + + +
+ ); + } else { + return ( + + + {getLangText('I agree to the')}  + + {getLangText('terms of ')} {contractIssuer} + + + + ); + } + } else { + return ( + + ); + } + }, + + render() { + return ( +
+ {this.getContractCheckbox()} + {this.getAppendix()} +
+ ); + } +}); + +export default InputContractAgreementCheckbox; \ No newline at end of file diff --git a/js/components/ascribe_forms/property.js b/js/components/ascribe_forms/property.js index d0b6ef37..8ed7362b 100644 --- a/js/components/ascribe_forms/property.js +++ b/js/components/ascribe_forms/property.js @@ -78,13 +78,15 @@ const Property = React.createClass({ componentWillReceiveProps(nextProps) { let childInput = this.refs.input; - // For expanded there are actually two use cases: + // For expanded there are actually three use cases: // // 1. Control its value from the outside completely (do not define `checkboxLabel`) // 2. Let it be controlled from the inside (default value can be set though via `expanded`) + // 3. Let it be controlled from a child by using `setExpanded` (`expanded` must not be + // set from the outside as a prop then(!!!)) // - // This handles case 1. - if(nextProps.expanded !== this.state.expanded && !this.props.checkboxLabel) { + // This handles case 1. and 3. + if(typeof nextProps.expanded === this.props.expanded && nextProps.expanded !== this.state.expanded && !this.props.checkboxLabel) { this.setState({ expanded: nextProps.expanded }); } @@ -226,6 +228,10 @@ const Property = React.createClass({ } }, + setExpanded(expanded) { + this.setState({ expanded }); + }, + renderChildren(style) { // Input's props should only be cloned and propagated down the tree, // if the component is actually being shown (!== 'expanded === false') @@ -247,7 +253,8 @@ const Property = React.createClass({ onBlur: this.handleBlur, disabled: !this.props.editable, ref: 'input', - name: this.props.name + name: this.props.name, + setExpanded: this.setExpanded }); }); } diff --git a/js/components/ascribe_forms/property_contract_agreement.js b/js/components/ascribe_forms/property_contract_agreement.js deleted file mode 100644 index cbb8924d..00000000 --- a/js/components/ascribe_forms/property_contract_agreement.js +++ /dev/null @@ -1,191 +0,0 @@ -'use strict' - -import React from 'react' - -import InputCheckbox from './input_checkbox'; -import Property from './property'; - -import ContractAgreementListStore from '../../stores/contract_agreement_list_store'; -import ContractAgreementListActions from '../../actions/contract_agreement_list_actions'; - -import { getLangText } from '../../utils/lang_utils'; -import { isEmail } from '../../utils/regex_utils'; - -let ContractAgreementProperty = React.createClass({ - propTypes: { - createPublicContractAgreement: React.PropTypes.bool, - email: React.PropTypes.string, - embedClassName: React.PropTypes.string, - label: React.PropTypes.string, - - // Necessary for Form to pick this element up as a ref - name: React.PropTypes.string, - - // Passed down from Form element - handleChange: React.PropTypes.func - }, - - getDefaultProps() { - return { - createPublicContractAgreement: true - }; - }, - - getInitialState() { - return ContractAgreementListStore.getState(); - }, - - componentDidMount() { - ContractAgreementListStore.listen(this.onChange); - this.getContractAgreementsOrCreatePublic(this.props.email); - }, - - componentWillReceiveProps({ email: nextEmail }) { - const { contractAgreementList } = this.state; - - if (this.props.email !== nextEmail) { - if (isEmail(nextEmail)) { - this.getContractAgreementsOrCreatePublic(nextEmail); - } else if (contractAgreementList && contractAgreementList.length > 0) { - ContractAgreementListActions.flushContractAgreementList(); - } - } - }, - - componentWillUnmount() { - ContractAgreementListStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - - getFormDataForProperty() { - const contractAgreementId = this.getContractAgreementId(); - const formData = { - 'terms': this.refs.terms.state.value - }; - - if (contractAgreementId) { - formData.contract_agreement_id = contractAgreementId; - } - - return formData; - }, - - getContractAgreementId() { - if (this.state.contractAgreementList && this.state.contractAgreementList.length > 0) { - return this.state.contractAgreementList[0].id; - } - }, - - getContractAgreementsOrCreatePublic(email) { - ContractAgreementListActions.flushContractAgreementList.defer(); - - if (email) { - // fetch the available contractagreements (pending/accepted) - ContractAgreementListActions.fetchAvailableContractAgreementList(email, this.props.createPublicContractAgreement); - } - }, - - getAppendix() { - const { contractAgreementList } = this.state; - - if (contractAgreementList && contractAgreementList.length > 0) { - const appendix = contractAgreementList[0].appendix; - if (appendix && appendix.default) { - return ( - -
{appendix.default}
-
- ); - } - } - }, - - getContractCheckbox() { - const { embedClassName, handleChange, label } = this.props; - const { contractAgreementList } = this.state; - - if (contractAgreementList && contractAgreementList.length > 0) { - const contractAgreement = contractAgreementList[0]; - const { issuer: contractIssuer, blob: { url_safe: contractUrl } } = contractAgreement.contract; - - // we need to define a key on the InputCheckboxes as otherwise - // react is not rerendering them on a store switch and is keeping - // the default value of the component (which is in that case true) - if (contractAgreement.datetime_accepted) { - return ( - - ); - } else { - return ( - - - - {getLangText('I agree to the')}  - - {getLangText('terms of ')} {contractIssuer} - - - - - ); - } - } else { - return ( - - ); - } - }, - - render() { - return ( -
- {this.getContractCheckbox()} - {this.getAppendix()} -
- ); - } -}); - -export default ContractAgreementProperty;