Created
September 1, 2016 16:52
-
-
Save j-quelly/5cbcaefe8b38aaef41f5985a98f5d0f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const TimersDashboard = React.createClass({ | |
getInitialState: function() { | |
return { | |
timers: [], | |
serverError: false, | |
timerError: false, | |
}; | |
}, | |
componentDidMount: function() { | |
this.loadTimersFromServer(); | |
// make sure the clients app state does not drift from the server | |
// setInterval(this.loadTimersFromServer, 5000); | |
}, | |
loadTimersFromServer: function() { | |
client.getTimers((serverTimers) => { | |
this.setState({ | |
timers: serverTimers | |
}) | |
}, (err) => { | |
this.setState({ | |
serverError: err.status | |
}); | |
}); | |
}, | |
handleCreateFormSubmit: function(timer) { | |
this.createTimer(timer); | |
}, | |
handleEditFormSubmit: function(attrs) { | |
this.updateTimer(attrs); | |
}, | |
handleTrashClick: function(timerId) { | |
this.deleteTimer(timerId); | |
}, | |
handleStartClick: function(timerId) { | |
this.startTimer(timerId); | |
}, | |
handleStopClick: function(timerId) { | |
this.stopTimer(timerId); | |
}, | |
handleMouseEnter: function(timerId) { | |
this.showExtras(timerId); | |
}, | |
handleMouseLeave: function(timerId) { | |
this.hideExtras(timerId); | |
}, | |
showExtras: function(timerId) { | |
this.setState({ | |
timers: this.state.timers.map((timer) => { | |
if (timer.id === timerId) { | |
return Object.assign({}, timer, { | |
extras: 'extra content', | |
}); | |
} else { | |
return timer; | |
} | |
}), | |
}); | |
}, | |
hideExtras: function(timerId) { | |
this.setState({ | |
timers: this.state.timers.map((timer) => { | |
if (timer.id === timerId) { | |
return Object.assign({}, timer, { | |
extras: 'extra content hidden', | |
}); | |
} else { | |
return timer; | |
} | |
}), | |
}); | |
}, | |
createTimer: function(timer) { | |
const t = helpers.newTimer(timer); | |
this.setState({ | |
timers: this.state.timers.concat(t), | |
}); | |
client.createTimer(t, (err) => { | |
this.setState({ | |
serverError: err.status, | |
timerError: t | |
}) | |
}); | |
}, | |
updateTimer: function(attrs) { | |
this.setState({ | |
timers: this.state.timers.map((timer) => { | |
if (timer.id === attrs.id) { | |
return Object.assign({}, timer, { | |
title: attrs.title, | |
project: attrs.project, | |
}); | |
} else { | |
return timer; | |
} | |
}), | |
}); | |
client.updateTimer(attrs, (err) => { | |
this.setState({ | |
timerError: attrs | |
}); | |
}); | |
}, | |
deleteTimer: function(timerId) { | |
this.setState({ | |
timers: this.state.timers.filter(t => t.id !== timerId), | |
}); | |
client.deleteTimer({ | |
id: timerId | |
}); | |
}, | |
startTimer: function(timerId) { | |
const now = Date.now(); | |
this.setState({ | |
timers: this.state.timers.map((timer) => { | |
if (timer.id === timerId) { | |
return Object.assign({}, timer, { | |
runningSince: now, | |
}); | |
} else { | |
return timer; | |
} | |
}), | |
}); | |
client.startTimer({ | |
id: timerId, | |
start: now | |
}); | |
}, | |
stopTimer: function(timerId) { | |
const now = Date.now(); | |
// optimistically update the state | |
this.setState({ | |
timers: this.state.timers.map((timer) => { | |
if (timer.id === timerId) { | |
const lastElapsed = now - timer.runningSince; | |
return Object.assign({}, timer, { | |
elapsed: timer.elapsed + lastElapsed, | |
runningSince: null, | |
}); | |
} else { | |
return timer; | |
} | |
}), | |
}); | |
// then update the state via server so it persists | |
client.stopTimer({ | |
id: timerId, | |
stop: now | |
}); | |
}, | |
render: function() { | |
return ( | |
<div> | |
<ServerError error={this.state.serverError} /> | |
<div className='ui three column centered grid'> | |
<div className='column'> | |
<EditableTimerList timers={this.state.timers} | |
onFormSubmit={this.handleEditFormSubmit} | |
onTrashClick={this.handleTrashClick} | |
onStartClick={this.handleStartClick} | |
onStopClick={this.handleStopClick} | |
onEnter={this.handleMouseEnter} | |
onLeave={this.handleMouseLeave} | |
serverError={this.state.serverError} | |
timerError={this.state.timerError} /> | |
<ToggleableTimerForm onFormSubmit={this.handleCreateFormSubmit} /> | |
</div> | |
</div> | |
</div> | |
); | |
}, | |
}); | |
// static child component of TimersDashboard | |
const EditableTimerList = React.createClass({ | |
// render the EditableTimerList component | |
render: function() { | |
// map over state (two static timers) as props passed down from EditableTimerList <- TimersDashboard | |
const timers = this.props.timers.map((timer) => { | |
let timerError = (this.props.timerError.id === timer.id ? this.props.timerError : false); | |
return ( | |
<EditableTimer key={timer.id} | |
id={timer.id} | |
title={timer.title} | |
project={timer.project} | |
elapsed={timer.elapsed} | |
runningSince={timer.runningSince} | |
extras={timer.extras} | |
onFormSubmit={this.props.onFormSubmit} | |
onTrashClick={this.props.onTrashClick} | |
onStartClick={this.props.onStartClick} | |
onStopClick={this.props.onStopClick} | |
onEnter={this.props.onEnter} | |
onLeave={this.props.onLeave} | |
serverError={this.props.serverError} | |
timerError={timerError} /> | |
); | |
}); | |
return ( | |
<div id='timers'> | |
{timers} | |
</div> | |
); | |
}, | |
}); | |
// static child component of EditableTimerList | |
const EditableTimer = React.createClass({ | |
// create the initial state | |
getInitialState: function() { | |
return { | |
editFormOpen: false | |
} | |
}, | |
handleEditClick: function() { | |
this.openForm(); | |
}, | |
handleFormClose: function() { | |
this.closeForm(); | |
}, | |
handleSubmit: function(timer) { | |
// received from EditableTimer <- EditableTimerList <- TimersDashboard | |
this.props.onFormSubmit(timer); | |
this.closeForm(); | |
}, | |
closeForm: function() { | |
this.setState({ | |
editFormOpen: false | |
}); | |
}, | |
openForm: function() { | |
this.setState({ | |
editFormOpen: true | |
}); | |
}, | |
// render the EditableTimer component | |
render: function() { | |
// console.log(this.props.timerError); | |
if (this.state.editFormOpen || this.props.timerError.id === this.props.id) { | |
// props received from EditableTimer <- EditableTimersList <- TimersDashboard | |
return ( | |
<TimerForm id={this.props.id} | |
title={this.props.title} | |
project={this.props.project} | |
onFormSubmit={this.handleSubmit} | |
onFormClose={this.handleFormClose} | |
serverError={this.props.serverError} | |
timerError={this.props.timerError} /> | |
); | |
} else { | |
// props received from EditableTimer <- EditableTimersList <- TimersDashboard | |
return ( | |
<Timer id={this.props.id} | |
title={this.props.title} | |
project={this.props.project} | |
elapsed={this.props.elapsed} | |
runningSince={this.props.runningSince} | |
extras={this.props.extras} | |
onEditClick={this.handleEditClick} | |
onTrashClick={this.props.onTrashClick} | |
onStartClick={this.props.onStartClick} | |
onStopClick={this.props.onStopClick} | |
onEnter={this.props.onEnter} | |
onLeave={this.props.onLeave} | |
serverError={this.props.serverError} | |
timerError={this.props.timerError} /> | |
); | |
} | |
}, | |
}); | |
// static child component of EditableTimer | |
const TimerForm = React.createClass({ | |
getInitialState: function() { | |
return { | |
titleError: false, | |
projectError: false, | |
}; | |
}, | |
validateForm: function() { | |
if (!this.refs.title.value || !this.refs.project.value) { | |
this.setState({ | |
titleError: (!this.refs.title.value ? true : false), | |
projectError: (!this.refs.project.value ? true : false) | |
}); | |
} else { | |
this.setState({ | |
titleError: false, | |
projectError: false | |
}, this.handleSubmit()); | |
} | |
}, | |
// handleSubmit method passes parameters to the onFormSubmit method | |
handleSubmit: function() { | |
// onFormSubmit passed as props from TimerForm <- EditableTimer | |
this.props.onFormSubmit({ | |
id: this.props.id, | |
title: this.refs.title.value, | |
project: this.refs.project.value, | |
}); | |
}, | |
// render the TimerForm component | |
render: function() { | |
// if an id was passed as props we update, otherwise we create | |
const submitText = this.props.id ? 'Update' : 'Create'; | |
// receives props from TimerForm <- EditableTimer <- TimersDashboard | |
return ( | |
<div className='ui centered card'> | |
<div className='content'> | |
<div className='ui form error'> | |
<ErrorMessage errorTitle='Unexpected Error' | |
errorMessage='An unexpected server error occured. Please try again.' | |
error={this.props.timerError} /> | |
<ErrorMessage errorTitle='Invalid Input' | |
errorMessage='Please enter a title.' | |
error={this.state.titleError} /> | |
<div className='field'> | |
<label> | |
Title | |
</label> | |
<input type='text' | |
ref='title' | |
defaultValue={this.props.title} /> | |
</div> | |
<ErrorMessage errorTitle='Invalid Input' | |
errorMessage='Please enter a project name.' | |
error={this.state.projectError} /> | |
<div className='field'> | |
<label> | |
Project | |
</label> | |
<input type='text' | |
ref='project' | |
defaultValue={this.props.project} /> | |
</div> | |
<div className='ui two bottom attached buttons'> | |
<button className='ui basic blue button' | |
onClick={this.validateForm}> | |
{submitText} | |
</button> | |
<button className='ui basic red button' | |
onClick={this.props.onFormClose}> | |
Cancel | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
}, | |
}); | |
// static child component of TimersDashboard | |
const ToggleableTimerForm = React.createClass({ | |
// get initial state | |
getInitialState: function() { | |
return { | |
isOpen: false, | |
}; | |
}, | |
// handleFormOpen method sets state to open form | |
handleFormOpen: function() { | |
this.setState({ | |
isOpen: true | |
}); | |
}, | |
// handleFormClose method sets state to closed form | |
handleFormClose: function() { | |
this.setState({ | |
isOpen: false | |
}); | |
}, | |
// handleFormSubmit method invokes onFormSubmit method passed | |
// as props from TimersDashboard and sets application state to close the form | |
handleFormSubmit: function(timer) { | |
// received from ToggleableTimerForm <- TimersDashboard | |
this.props.onFormSubmit(timer); | |
this.setState({ | |
isOpen: false | |
}); | |
}, | |
// render the component | |
render: function() { | |
if (this.state.isOpen) { | |
return ( | |
<TimerForm onFormSubmit={this.handleFormSubmit} | |
onFormClose={this.handleFormClose} /> | |
); | |
} else { | |
return ( | |
<div className='ui basic content center aligned segment'> | |
<button className='ui basic button icon' | |
onClick={this.handleFormOpen}> | |
<i className='plus icon'></i> | |
</button> | |
</div> | |
); | |
} | |
}, | |
}); | |
// static child component of EditableTime | |
const Timer = React.createClass({ | |
// runs once the component mounts | |
componentDidMount: function() { | |
this.forceUpdateInterval = setInterval(() => { | |
this.forceUpdate(); | |
}, 50); | |
}, | |
// runs once the component unmounts | |
componentWillUnmount: function() { | |
clearInterval(this.forceUpdateInterval); | |
}, | |
handleStartClick: function() { | |
this.props.onStartClick(this.props.id); | |
}, | |
handleStopClick: function() { | |
this.props.onStopClick(this.props.id); | |
}, | |
handleTrashClick: function() { | |
// received from Timer <- EditableTimer <- EditableTimerList <- TimersDashboard | |
this.props.onTrashClick(this.props.id); | |
}, | |
handleEnter: function() { | |
this.props.onEnter(this.props.id); | |
}, | |
handleLeave: function() { | |
this.props.onLeave(this.props.id); | |
}, | |
handleEditClick: function() { | |
this.props.onEditClick(this.props.id); | |
}, | |
// render Timer component | |
render: function() { | |
// elapsed props passed from Timer <- EditableTimer <- EditableTimerList <- TimersDashboard | |
const elapsedString = helpers.renderElapsedString(this.props.elapsed); | |
// props received from Timer <- EditableTimer <- EditableTimerList <- TimersDashboard | |
return ( | |
<div className='ui centered card' | |
onMouseMove={this.handleEnter} | |
onMouseLeave={this.handleLeave}> | |
<div className='content'> | |
<div className='header'> | |
{this.props.title} | |
</div> | |
<div className='meta'> | |
{this.props.project} | |
</div> | |
<div className='center aligned description'> | |
<h2>{elapsedString}</h2> | |
</div> | |
<div className={this.props.extras}> | |
<span className='right floated edit icon' | |
onClick={this.handleEditClick}><i className='edit icon'></i></span> | |
<span className='right floated trash icon' | |
onClick={this.handleTrashClick}><i className='trash icon'></i></span> | |
</div> | |
</div> | |
<TimerActionButton timerIsRunning={!!this.props.runningSince} | |
onStartClick={this.handleStartClick} | |
onStopClick={this.handleStopClick} /> | |
</div> | |
); | |
}, | |
}); | |
const TimerActionButton = React.createClass({ | |
render: function() { | |
if (this.props.timerIsRunning) { | |
return ( | |
<div className='ui bottom attached red basic button' | |
onClick={this.props.onStopClick}> | |
Stop | |
</div> | |
); | |
} else { | |
return ( | |
<div className='ui bottom attached green basic button' | |
onClick={this.props.onStartClick}> | |
Start | |
</div> | |
); | |
} | |
}, | |
}); | |
const ErrorMessage = React.createClass({ | |
render: function() { | |
let className = 'ui error message hide'; | |
if (this.props.error) { | |
className = 'ui error message'; | |
} | |
// if (this.props.timerError) { | |
// console.log(this.props.timerError); | |
// className = 'ui error message'; | |
// } | |
return ( | |
<div className={className}> | |
<div className='header'> | |
{this.props.errorTitle} | |
</div> | |
<p> | |
{this.props.errorMessage} | |
</p> | |
</div> | |
); | |
}, | |
}); | |
const ServerError = React.createClass({ | |
render: function() { | |
let className = 'server-error hide'; | |
if (this.props.error) { | |
className = 'animated fadeInDown server-error'; | |
} | |
return ( | |
<div className={className}> | |
<p> | |
Unexpected server error: <strong>{this.props.error}.</strong> Please refresh the page and try again. | |
</p> | |
</div> | |
); | |
}, | |
}); | |
// render the application | |
ReactDOM.render( | |
<TimersDashboard />, | |
document.getElementById('content') | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment