Refactor MarketSubmitButton to pull the piece as necessary rather than always fetching it in MarketAdditionalDataForm

This commit is contained in:
Brett Sun 2016-01-15 14:22:28 +01:00
parent c6071d8ab4
commit 50129b9d0c
9 changed files with 194 additions and 159 deletions

View File

@ -5,15 +5,14 @@ import React from 'react';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import Form from './../ascribe_forms/form';
import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import FurtherDetailsFileuploader from './further_details_fileuploader';
import Form from './../ascribe_forms/form';
import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';

View File

@ -30,7 +30,7 @@ let MarketAclButtonList = React.createClass({
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
UserActions.fetchCurrentUser.defer();
},
componentWillUnmount() {

View File

@ -3,6 +3,11 @@
import React from 'react';
import classNames from 'classnames';
import PieceActions from '../../../../../../actions/piece_actions';
import PieceStore from '../../../../../../stores/piece_store';
import WhitelabelActions from '../../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../../stores/whitelabel_store';
import MarketAdditionalDataForm from '../market_forms/market_additional_data_form';
import AclFormFactory from '../../../../../ascribe_forms/acl_form_factory';
@ -11,15 +16,14 @@ import ConsignForm from '../../../../../ascribe_forms/form_consign';
import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
import AclProxy from '../../../../../acl_proxy';
import PieceActions from '../../../../../../actions/piece_actions';
import WhitelabelActions from '../../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../../stores/whitelabel_store';
import AscribeSpinner from '../../../../../ascribe_spinner';
import ApiUrls from '../../../../../../constants/api_urls';
import { getAclFormMessage, getAclFormDataId } from '../../../../../../utils/form_utils';
import { mergeOptions } from '../../../../../../utils/general_utils';
import { getLangText } from '../../../../../../utils/lang_utils';
import { onChangeOnce } from '../../../../../../utils/store_utils';
let MarketSubmitButton = React.createClass({
propTypes: {
@ -27,11 +31,19 @@ let MarketSubmitButton = React.createClass({
currentUser: React.PropTypes.object,
editions: React.PropTypes.array.isRequired,
handleSuccess: React.PropTypes.func.isRequired,
className: React.PropTypes.string,
className: React.PropTypes.string
},
// This component may eventually need to use the
// PieceStore, but we don't need it initially
getInitialState() {
return WhitelabelStore.getState();
return mergeOptions(
WhitelabelStore.getState(),
{
piece: {}
}
);
},
componentDidMount() {
@ -41,6 +53,7 @@ let MarketSubmitButton = React.createClass({
},
componentWillUnmount() {
PieceStore.unlisten(this.onChange);
WhitelabelStore.unlisten(this.onChange);
},
@ -62,8 +75,24 @@ let MarketSubmitButton = React.createClass({
return false;
},
getFormDataId() {
return getAclFormDataId(false, this.props.editions);
getAdditionalDataForm() {
const { piece } = this.state;
if (piece.id) {
return (
<MarketAdditionalDataForm
extraData={piece.extra_data}
otherData={piece.other_data}
pieceId={piece.id}
submitLabel={getLangText('Continue to consignment')} />
);
} else {
return (
<div className="fullpage-spinner">
<AscribeSpinner color='dark-blue' size='lg'/>
</div>
);
}
},
getAggregateEditionDetails() {
@ -82,6 +111,10 @@ let MarketSubmitButton = React.createClass({
});
},
getFormDataId() {
return getAclFormDataId(false, this.props.editions);
},
handleAdditionalDataSuccess(pieceId) {
// Fetch newly updated piece to update the views
PieceActions.fetchPiece(pieceId);
@ -89,6 +122,19 @@ let MarketSubmitButton = React.createClass({
this.refs.consignModal.show();
},
loadPieceIfNeeded(neededPieceId) {
if (neededPieceId) {
const pieceStore = PieceStore.getState();
if (pieceStore.piece.id === neededPieceId) {
this.setState(pieceStore);
} else {
onChangeOnce(this, PieceStore);
PieceActions.fetchPiece(neededPieceId);
}
}
},
render() {
const { availableAcls, currentUser, className, editions, handleSuccess } = this.props;
const { whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } } = this.state;
@ -102,7 +148,9 @@ let MarketSubmitButton = React.createClass({
});
const triggerButton = (
<button className={classNames('btn', 'btn-default', 'btn-sm', className)}>
<button
className={classNames('btn', 'btn-default', 'btn-sm', className)}
onClick={solePieceId && !canSubmit ? () => this.loadPieceIfNeeded(solePieceId) : () => {}}>
{getLangText('CONSIGN TO %s', whitelabelName.toUpperCase())}
</button>
);
@ -128,9 +176,7 @@ let MarketSubmitButton = React.createClass({
trigger={triggerButton}
handleSuccess={() => this.handleAdditionalDataSuccess(solePieceId)}
title={getLangText('Add additional information')}>
<MarketAdditionalDataForm
pieceId={solePieceId}
submitLabel={getLangText('Continue to consignment')} />
{this.getAdditionalDataForm()}
</ModalWrapper>
<ModalWrapper

View File

@ -6,8 +6,12 @@ import MarketAdditionalDataForm from '../market_forms/market_additional_data_for
let MarketFurtherDetails = React.createClass({
propTypes: {
pieceId: React.PropTypes.number,
pieceId: React.PropTypes.number.isRequired,
editable: React.PropTypes.bool,
extraData: React.PropTypes.object,
handleSuccess: React.PropTypes.func,
otherData: React.PropTypes.arrayOf(React.PropTypes.object)
},
render() {

View File

@ -2,21 +2,18 @@
import React from 'react';
import Form from '../../../../../ascribe_forms/form';
import Property from '../../../../../ascribe_forms/property';
import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
import AscribeSpinner from '../../../../../ascribe_spinner';
import GlobalNotificationModel from '../../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
import Form from '../../../../../ascribe_forms/form';
import Property from '../../../../../ascribe_forms/property';
import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils';
import PieceActions from '../../../../../../actions/piece_actions';
import PieceStore from '../../../../../../stores/piece_store';
import AscribeSpinner from '../../../../../ascribe_spinner';
import ApiUrls from '../../../../../../constants/api_urls';
import AppConstants from '../../../../../../constants/application_constants';
@ -28,13 +25,16 @@ import { getLangText } from '../../../../../../utils/lang_utils';
let MarketAdditionalDataForm = React.createClass({
propTypes: {
pieceId: React.PropTypes.number,
pieceId: React.PropTypes.number.isRequired,
editable: React.PropTypes.bool,
extraData: React.PropTypes.object,
handleSuccess: React.PropTypes.func,
isInline: React.PropTypes.bool,
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
showHeading: React.PropTypes.bool,
showNotification: React.PropTypes.bool,
submitLabel: React.PropTypes.string,
handleSuccess: React.PropTypes.func
submitLabel: React.PropTypes.string
},
getDefaultProps() {
@ -45,50 +45,34 @@ let MarketAdditionalDataForm = React.createClass({
},
getInitialState() {
const pieceStore = PieceStore.getState();
return mergeOptions(
pieceStore,
{
// Allow the form to be submitted if there's already an additional image uploaded
isUploadReady: this.isUploadReadyOnChange(pieceStore.piece),
forceUpdateKey: 0
});
},
componentDidMount() {
PieceStore.listen(this.onChange);
if (this.props.pieceId) {
PieceActions.fetchPiece(this.props.pieceId);
return {
// Allow the form to be submitted if there's already an additional image uploaded
isUploadReady: this.isUploadReadyOnChange(),
forceUpdateKey: 0
}
},
componentWillUnmount() {
PieceStore.unlisten(this.onChange);
},
componentWillReceiveProps(nextProps) {
if (this.props.extraData !== nextProps.extraData || this.props.otherData !== nextProps.otherData) {
this.setState({
// Allow the form to be submitted if the updated piece has an additional image uploaded
isUploadReady: this.isUploadReadyOnChange(),
onChange(state) {
Object.assign({}, state, {
// Allow the form to be submitted if the updated piece already has an additional image uploaded
isUploadReady: this.isUploadReadyOnChange(state.piece),
/**
* Increment the forceUpdateKey to force the form to rerender on each change
*
* THIS IS A HACK TO MAKE SURE THE FORM ALWAYS DISPLAYS THE MOST RECENT STATE
* BECAUSE SOME OF OUR FORM ELEMENTS DON'T UPDATE FROM PROP CHANGES (ie.
* InputTextAreaToggable).
*/
forceUpdateKey: this.state.forceUpdateKey + 1
});
this.setState(state);
/**
* Increment the forceUpdateKey to force the form to rerender on each change
*
* THIS IS A HACK TO MAKE SURE THE FORM ALWAYS DISPLAYS THE MOST RECENT STATE
* BECAUSE SOME OF OUR FORM ELEMENTS DON'T UPDATE FROM PROP CHANGES (ie.
* InputTextAreaToggable).
*/
forceUpdateKey: this.state.forceUpdateKey + 1
});
}
},
getFormData() {
let extradata = {};
let formRefs = this.refs.form.refs;
const extradata = {};
const formRefs = this.refs.form.refs;
// Put additional fields in extra data object
Object
@ -99,12 +83,12 @@ let MarketAdditionalDataForm = React.createClass({
return {
extradata: extradata,
piece_id: this.state.piece.id
piece_id: this.props.pieceId
};
},
isUploadReadyOnChange(piece) {
return piece && piece.other_data && piece.other_data.length > 0;
isUploadReadyOnChange() {
return this.props.otherData && this.props.otherData.length;
},
handleSuccessWithNotification() {
@ -123,10 +107,18 @@ let MarketAdditionalDataForm = React.createClass({
},
render() {
const { editable, isInline, handleSuccess, showHeading, showNotification, submitLabel } = this.props;
const { piece } = this.state;
let buttons, heading;
const {
editable,
extraData,
isInline,
handleSuccess,
otherData,
pieceId,
showHeading,
showNotification,
submitLabel } = this.props;
let buttons, heading;
let spinner = <AscribeSpinner color='dark-blue' size='lg' />;
if (!isInline) {
@ -156,76 +148,68 @@ let MarketAdditionalDataForm = React.createClass({
) : null;
}
if (piece.id) {
return (
<Form
className="ascribe-form-bordered"
ref='form'
key={this.state.forceUpdateKey}
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: piece.id})}
handleSuccess={showNotification ? this.handleSuccessWithNotification : handleSuccess}
getFormData={this.getFormData}
buttons={buttons}
spinner={spinner}
disabled={!this.props.editable || !piece.acl.acl_edit}>
{heading}
<FurtherDetailsFileuploader
label={getLangText('Marketplace Thumbnail Image')}
submitFile={function () {}}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
pieceId={piece.id}
otherData={piece.other_data}
editable={editable} />
<Property
name='artist_bio'
label={getLangText('Artist Bio')}
expanded={editable || !!piece.extra_data.artist_bio}>
<InputTextAreaToggable
rows={1}
defaultValue={piece.extra_data.artist_bio}
placeholder={getLangText('Enter a biography of the artist...')}
required />
</Property>
<Property
name='work_description'
label={getLangText('Work Description')}
expanded={editable || !!piece.extra_data.work_description}>
<InputTextAreaToggable
rows={1}
defaultValue={piece.extra_data.work_description}
placeholder={getLangText('Enter a description of the work...')}
required />
</Property>
<Property
name='technology_details'
label={getLangText('Technology Details')}
expanded={editable || !!piece.extra_data.technology_details}>
<InputTextAreaToggable
rows={1}
defaultValue={piece.extra_data.technology_details}
placeholder={getLangText('Enter technological details about the work...')}
required />
</Property>
<Property
name='display_instructions'
label={getLangText('Display Instructions')}
expanded={editable || !!piece.extra_data.display_instructions}>
<InputTextAreaToggable
rows={1}
defaultValue={piece.extra_data.display_instructions}
placeholder={getLangText('Enter instructions on how to best display the work...')}
required />
</Property>
</Form>
);
} else {
return (
<div className="ascribe-loading-position">
{spinner}
</div>
);
}
return (
<Form
className="ascribe-form-bordered"
ref='form'
key={this.state.forceUpdateKey}
url={requests.prepareUrl(ApiUrls.piece_extradata, {piece_id: pieceId})}
handleSuccess={showNotification ? this.handleSuccessWithNotification : handleSuccess}
getFormData={this.getFormData}
buttons={buttons}
spinner={spinner}
disabled={!this.props.editable}>
{heading}
<FurtherDetailsFileuploader
label={getLangText('Marketplace Thumbnail Image')}
submitFile={function () {}}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
pieceId={pieceId}
otherData={otherData}
editable={editable} />
<Property
name='artist_bio'
label={getLangText('Artist Bio')}
expanded={editable || !!extraData.artist_bio}>
<InputTextAreaToggable
rows={1}
defaultValue={extraData.artist_bio}
placeholder={getLangText('Enter a biography of the artist...')}
required />
</Property>
<Property
name='work_description'
label={getLangText('Work Description')}
expanded={editable || !!extraData.work_description}>
<InputTextAreaToggable
rows={1}
defaultValue={extraData.work_description}
placeholder={getLangText('Enter a description of the work...')}
required />
</Property>
<Property
name='technology_details'
label={getLangText('Technology Details')}
expanded={editable || !!extraData.technology_details}>
<InputTextAreaToggable
rows={1}
defaultValue={extraData.technology_details}
placeholder={getLangText('Enter technological details about the work...')}
required />
</Property>
<Property
name='display_instructions'
label={getLangText('Display Instructions')}
expanded={editable || !!extraData.display_instructions}>
<InputTextAreaToggable
rows={1}
defaultValue={extraData.display_instructions}
placeholder={getLangText('Enter instructions on how to best display the work...')}
required />
</Property>
</Form>
);
}
});

View File

@ -6,11 +6,6 @@ import { History } from 'react-router';
import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row';
import MarketAdditionalDataForm from './market_forms/market_additional_data_form';
import Property from '../../../../ascribe_forms/property';
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
import PieceActions from '../../../../../actions/piece_actions';
import PieceListStore from '../../../../../stores/piece_list_store';
import PieceListActions from '../../../../../actions/piece_list_actions';
@ -19,6 +14,11 @@ import UserActions from '../../../../../actions/user_actions';
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../stores/whitelabel_store';
import MarketAdditionalDataForm from './market_forms/market_additional_data_form';
import Property from '../../../../ascribe_forms/property';
import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
import SlidesContainer from '../../../../ascribe_slides_container/slides_container';
import { getLangText } from '../../../../../utils/lang_utils';

View File

@ -7,8 +7,8 @@ import PieceActions from '../actions/piece_actions';
const PieceSource = {
lookupPiece: {
remote(state) {
return requests.get('piece', { piece_id: state.pieceMeta.idToFetch });
remote(state, pieceId) {
return requests.get('piece', { piece_id: pieceId });
},
success: PieceActions.successFetchPiece,

View File

@ -21,8 +21,7 @@ class PieceStore {
getInitialState() {
this.piece = {};
this.pieceMeta = {
err: null,
idToFetch: null
err: null
};
return {
@ -31,10 +30,12 @@ class PieceStore {
}
}
onFetchPiece(idToFetch) {
this.pieceMeta.idToFetch = idToFetch;
onFetchPiece(pieceId) {
this.getInstance().lookupPiece(pieceId);
this.getInstance().lookupPiece();
// Prevent alt from sending an empty change event when a request is sent
// off to the source
this.preventDefault();
}
onSuccessFetchPiece({ piece }) {
@ -42,10 +43,12 @@ class PieceStore {
this.onUpdatePiece(piece);
} else {
this.pieceMeta.err = new Error('Problem fetching the piece');
console.logGlobal(this.pieceMeta.err);
}
}
onErrorPiece(err) {
console.logGlobal(err);
this.pieceMeta.err = err;
}
@ -56,11 +59,10 @@ class PieceStore {
onUpdatePiece(piece) {
this.piece = piece;
this.pieceMeta.err = null;
this.pieceMeta.idToFetch = null;
}
onUpdateProperty({ key, value }) {
if(this.piece && key in this.piece) {
if (this.piece && key in this.piece) {
this.piece[key] = value;
} else {
throw new Error('There is no piece defined in PieceStore or the piece object does not have the property you\'re looking for.');

View File

@ -238,7 +238,7 @@ $vivi23--highlight-color: #de2600;
&.active:hover{
background-color: $vivi23--highlight-color;
border-color: $vivi23--highlight-color;
color: $vivi23--highlight-color;
color: $vivi23--bg-color;
}
}