Skip to content

Commit

Permalink
refactored assessment form to use separate components not tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
mvillis committed Mar 13, 2016
1 parent daeb2c1 commit 26e2174
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 170 deletions.
112 changes: 112 additions & 0 deletions client/js/components/assessment/attribute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use strict'

var React = require('react')
var ReactBootstrap = require('react-bootstrap')
var Rating = require('./rating')
var ObserveInput = require('./observeInput')
var Loader = require('react-loader')
var _ = require('lodash')
var Panel = ReactBootstrap.Panel
var Alert = ReactBootstrap.Alert
var ListGroup = ReactBootstrap.ListGroup
var $ = require('jquery')

var Attribute = React.createClass({
propTypes: {
key: React.PropTypes.number,
eventKey: React.PropTypes.number,
activeTab: React.PropTypes.number,
attribute: React.PropTypes.object,
params: React.PropTypes.object
},
getInitialState: function () {
return {
attribute: null,
measurement: null,
measureSyncActivity: false,
dirtyObservation: false,
loaded: false
}
},
componentDidMount: function () {
this.getData(this.props.params.id, this.props.params.attribute)
},
componentWillReceiveProps: function (nextProps) {
this.getData(nextProps.params.id, nextProps.params.attribute)
},
getData: function (id, attribute) {
var setAttribute = function (data) { this.setState({attribute: data}) }.bind(this)
var setMeasurement = function (data) { this.setState({measurement: _.head(data), loaded: true}) }.bind(this)
this.dataSource('/api/attributes/' + attribute, setAttribute)
this.dataSource('/api/measurements/' + '?assessment__id=' + id + '&rating__attribute=' + attribute, setMeasurement)
},
dataSource: function (url, callback) {
$.ajax({
type: 'get',
dataType: 'json',
url: url,
success: callback,
error: this.handleSubmitFailure
})
},
handleSubmitFailure: function (xhr, ajaxOptions, thrownError) {
console.error('There was a failure')
},
saveMeasurement: function (ratingType, value) {
var existingMeasurement = this.state.measurement
var postData = {
observations: (this.state.measurement.observations) ? this.state.measurement.observations : '',
id: (this.state.measurement) ? this.state.measurement.id : '',
assessment: this.props.params.id,
rating: (ratingType === 'rating') ? value : this.state.measurement.rating,
target_rating: (ratingType === 'target') ? value : ((existingMeasurement && this.state.measurement.target_rating) ? this.state.measurement.target_rating : '') // eslint-disable-line camelcase
}
this.syncMeasurement(postData)
},
measurementUpdateCallback: function (data) {
this.setState({
measurement: data,
measureSyncActivity: false,
dirtyObservation: false
})
},
syncMeasurement: function (postData) {
var createNewMeasure = !postData.id
this.setState({ measureSyncActivity: true })
console.log('Should a new measurement be created? ' + createNewMeasure)
$.ajax({
type: ((createNewMeasure) ? 'POST' : 'PUT'),
contentType: 'application/json; charset=utf-8',
url: ((createNewMeasure) ? '/api/measurements/' : ('/api/measurements/' + postData.id + '/')),
data: JSON.stringify(postData),
dataType: 'json',
success: this.measurementUpdateCallback,
error: this.handleSubmitFailure
})
},
render: function () {
if (this.state.attribute !== null) {
var ratingList = this.state.attribute.ratings.map(function (rating) {
return (
<Rating measurement={this.state.measurement} key={rating.id} rating={rating} saveMeasurement={this.saveMeasurement} assessId={this.props.params.id}/>
)
}.bind(this))
}

return (
<Loader loaded={this.state.loaded}>
<Panel header={(this.state.attribute && this.state.attribute.name) ? this.state.attribute.name : ''} bsStyle='primary'>
<Alert bsStyle='warning' className={this.state.attribute && this.state.attribute.desc_class ? this.state.attribute.desc_class : ''}>
{this.state.attribute && this.state.attribute.desc ? this.state.attribute.desc : ''}
</Alert>
<Loader loaded={!this.state.measureSyncActivity}/>
<ListGroup fill>
{ratingList}
</ListGroup>
</Panel>
</Loader>
)
}
})

module.exports = Attribute
158 changes: 43 additions & 115 deletions client/js/components/assessment/attributeList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@

var React = require('react')
var ReactBootstrap = require('react-bootstrap')
var _ = require('lodash')
var ReactRouterBootstrap = require('react-router-bootstrap')
var PageHeader = ReactBootstrap.PageHeader
var Tabs = ReactBootstrap.Tabs
var Tab = ReactBootstrap.Tab
var Panel = ReactBootstrap.Panel
var Alert = ReactBootstrap.Alert
var ListGroup = ReactBootstrap.ListGroup
var Nav = ReactBootstrap.Nav
var NavItem = ReactBootstrap.NavItem
var Grid = ReactBootstrap.Grid
var Row = ReactBootstrap.Row
var Col = ReactBootstrap.Col
var Pager = ReactBootstrap.Pager
var PageItem = ReactBootstrap.PageItem
var Glyphicon = ReactBootstrap.Glyphicon
var LinkContainer = ReactRouterBootstrap.LinkContainer
var Loader = require('react-loader')
var RatingList = require('./ratingList')
var ObserveInput = require('./observeInput')
var AssessmentReport = require('./assessmentReport')
var $ = require('jquery')

var AttributeList = React.createClass({
propTypes: {
params: React.PropTypes.object
params: React.PropTypes.object,
location: React.PropTypes.object,
children: React.PropTypes.object
},
getInitialState: function () {
return {
activeTab: 1,
activeTab: parseInt(this.props.location.query.tab, 10) || 1,
activeAttribute: null,
measurements: null,
template: null,
Expand All @@ -40,18 +40,11 @@ var AttributeList = React.createClass({
componentWillMount: function () {
this.dataSource('/api/assessments/' + this.props.params.id + '/', this.assessmentCallback)
},
measurementCallback: function (data) {
templateCallback: function (data) {
this.setState({
measurements: data,
template: data,
initialLoad: true
})
this.handleSelect(1)
},
templateCallback: function (data) {
this.setState({
template: data
}, this.dataSource('/api/measurements/' + '?assessment__id=' + this.props.params.id, this.measurementCallback)
)
},
assessmentCallback: function (data) {
this.setState({
Expand All @@ -71,58 +64,6 @@ var AttributeList = React.createClass({
error: this.handleSubmitFailure
})
},
measurementUpdateCallback: function (data) {
var existingMeasurementIndex = _.findIndex(this.state.measurements, function (measurement) {
return measurement.id === data.id
})
var updatedDirtyObservation = this.state.dirtyObservation
updatedDirtyObservation[this.state.activeTab] = false
if (existingMeasurementIndex !== -1) {
var updatedMeasurements = this.state.measurements.slice()
updatedMeasurements[existingMeasurementIndex] = data
this.setState({
measurements: updatedMeasurements,
measureSyncActivity: false,
dirtyObservation: updatedDirtyObservation
})
} else {
var newMeasurements = this.state.measurements.concat([data])
this.setState({
measurements: newMeasurements,
measureSyncActivity: false,
dirtyObservation: updatedDirtyObservation
})
}
},
syncMeasurement: function (postData) {
var createNewMeasure = !postData.id
if (this.state.observations[this.state.activeTab]) {
postData['observations'] = this.state.observations[this.state.activeTab]
} else {
var matchMeasure = _.find(this.state.measurements, function (measurement) {
return measurement.id === postData.id
})
matchMeasure ? postData['observations'] = matchMeasure.observations : postData['observations'] = ''
}
this.setState({ measureSyncActivity: true })
console.log('Should a new measurement be created? ' + createNewMeasure)
$.ajax({
type: ((createNewMeasure) ? 'POST' : 'PUT'),
contentType: 'application/json; charset=utf-8',
url: ((createNewMeasure) ? '/api/measurements/' : ('/api/measurements/' + postData.id + '/')),
data: JSON.stringify(postData),
dataType: 'json',
success: this.measurementUpdateCallback,
error: this.handleSubmitFailure
})
},
onObservationChange: function (text, activeTab) {
var observations = this.state.observations
observations[activeTab] = text
var updatedDirtyObservation = this.state.dirtyObservation
updatedDirtyObservation[this.state.activeTab] = true
this.setState({observations: observations, dirtyObservation: updatedDirtyObservation})
},
handleNext: function () {
if (this.state.activeTab < this.state.template.attributes.length + 1 && !this.state.nextHide) {
var newTab = this.state.activeTab + 1
Expand Down Expand Up @@ -195,40 +136,18 @@ var AttributeList = React.createClass({
var completeMeasurement = measurement && measurement.rating && measurement.target_rating
var tabIcon = (completeMeasurement) ? <Glyphicon glyph='ok' tabClassName='text-success'/> : <Glyphicon glyph='minus' />
return (
<Tab eventKey={i + 1} key={attribute.id} id={i + 1} title={<div>{tabIcon} <span>{attribute.name}</span></div>}>
<Panel header={attribute.name} bsStyle='primary'>
<Alert bsStyle='warning' className={attribute.desc_class}>
{attribute.desc}
</Alert>
<ObserveInput eventKey={i + 1} dirtyObservation={this.state.dirtyObservation} activeTab={this.state.activeTab} measurement={measurement} syncMeasurement={this.syncMeasurement} onObservationChange={this.onObservationChange}/>
<Loader loaded={!this.state.measureSyncActivity}/>
<ListGroup fill>
<RatingList eventKey={i + 1} activeTab={this.state.activeTab} dirtyObservation={this.state.dirtyObservation} key={attribute.id} measurement={measurement} attribute={attribute} assessId={this.props.params.id} syncMeasurement={this.syncMeasurement}/>
</ListGroup>
</Panel>
</Tab>
<LinkContainer key={attribute.id} to={{pathname: '/assessment/' + this.state.assessment.id + '/' + attribute.id}}>
<NavItem activeClassName='active' eventKey={i + 1} id={i + 1}>{attribute.name}</NavItem>
</LinkContainer>
)
}.bind(this))

var summaryTab = function () {
var summaryNode = function () {
if (!this.state.template) return (undefined)
return (
<Tab eventKey={this.state.template ? this.state.template.attributes.length + 1 : null} key={this.state.template ? this.state.template.attributes.length + 1 : null} id={this.state.template ? this.state.template.attributes.length + 1 : null} title={<div><Glyphicon glyph='stats'/> <span>Summary</span></div>}>
<Panel header='Summary' bsStyle='primary'>
<Alert bsStyle='warning'>
How did you go? Where are you strengths and weaknesses? What are some improvements you could make?
</Alert>
<AssessmentReport
eventKey={this.state.template ? this.state.template.attributes.length + 1 : null}
key={this.state.template ? this.state.template.attributes.length + 1 : null}
id={this.state.template ? this.state.template.attributes.length + 1 : null}
activeTab={this.state.activeTab}
measurements={this.state.measurements}
attributes={this.state.template.attributes}
template={this.state.template}
assessId={this.props.params.id}
/>
</Panel>
</Tab>
<LinkContainer key='summary' to={{pathname: '/assessment/' + this.state.assessment.id + '/summary'}}>
<NavItem activeClassName='active' eventKey={this.state.template.attributes.length + 1} id={this.state.template.attributes.length + 1}>Summary</NavItem>
</LinkContainer>
)
}.bind(this)()
}
Expand All @@ -238,19 +157,28 @@ var AttributeList = React.createClass({
<PageHeader>
{!!this.state.assessment === true ? this.state.assessment.template.name : ''} <small> {this.state.assessment ? this.state.assessment.template.short_desc : ''}</small>
</PageHeader>
<Tabs position='right' activeKey={this.state.activeTab} onSelect={this.handleSelect} tabWidth={3}>
{attributeNodes}
{summaryTab}
<Pager>
<PageItem disabled={this.state.previous_hide} onClick={this.handlePrevious}>
<Glyphicon glyph='chevron-left' /> {' '} Previous
</PageItem>
{' '}
<PageItem disabled={this.state.next_hide} onClick={this.handleNext}>
Next {' '} <Glyphicon glyph='chevron-right' />
</PageItem>
</Pager>
</Tabs>
<Grid>
<Row>
<Col xs={12} md={8}>
{React.cloneElement(this.props.children, {attributes: this.state.attributes})}
<Pager>
<PageItem disabled={this.state.previous_hide} onClick={this.handlePrevious}>
<Glyphicon glyph='chevron-left' /> {' '} Previous
</PageItem>
{' '}
<PageItem disabled={this.state.next_hide} onClick={this.handleNext}>
Next {' '} <Glyphicon glyph='chevron-right' />
</PageItem>
</Pager>
</Col>
<Col xs={6} md={4}>
<Nav bsStyle='pills' stacked activeKey={1} onSelect={this.handleSelect}>
{attributeNodes}
{summaryNode}
</Nav>
</Col>
</Row>
</Grid>
</Loader>
</div>
)
Expand Down
38 changes: 38 additions & 0 deletions client/js/components/assessment/intro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict'

var React = require('react')
var ReactBootstrap = require('react-bootstrap')
var ReactRouterBootstrap = require('react-router-bootstrap')
var LinkContainer = ReactRouterBootstrap.LinkContainer
var Jumbotron = ReactBootstrap.Jumbotron
var Button = ReactBootstrap.Button

var Intro = React.createClass({
propTypes: {
attributes: React.PropTypes.array,
params: React.PropTypes.object
},
getInitialState: function () {
return {
attribute: undefined,
measureSyncActivity: false,
dirtyObservation: false
}
},
render: function () {
return (
<Jumbotron>
<h1>Let's get started!</h1>
<p>Look to your right. There you will find a list of the areas we are going to measure. Start by selecting the top one.</p>
<span>
<LinkContainer to={{pathname: '/assessment/' + this.props.params.id + '/' + ((this.props.attributes) ? this.props.attributes[0].id : '')}}>
<Button bsStyle='primary'>Begin</Button>
</LinkContainer>
&nbsp;<Button bsStyle='default'>See Results</Button>
</span>
</Jumbotron>
)
}
})

module.exports = Intro
7 changes: 0 additions & 7 deletions client/js/components/assessment/rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,12 @@ var Rating = React.createClass({
propTypes: {
key: React.PropTypes.number,
eventKey: React.PropTypes.number,
activeTab: React.PropTypes.number,
measurement: React.PropTypes.object,
assessId: React.PropTypes.string.isRequired,
attribute: React.PropTypes.object,
saveMeasurement: React.PropTypes.func.isRequired,
rating: React.PropTypes.object
},
shouldComponentUpdate: function (nextProps, nextState) {
if (nextProps.activeTab === this.props.eventKey) {
return true
}
return false
},
render: function () {
var ratingActive = (this.props.measurement && this.props.measurement.rating) ? (this.props.measurement.rating === this.props.rating.id) : false
var targetActive = (this.props.measurement && this.props.measurement.target_rating) ? (this.props.measurement.target_rating === this.props.rating.id) : false
Expand Down
Loading

0 comments on commit 26e2174

Please sign in to comment.