Skip to content

Instantly share code, notes, and snippets.

@j-quelly
Created September 1, 2016 16:52
Show Gist options
  • Save j-quelly/5cbcaefe8b38aaef41f5985a98f5d0f9 to your computer and use it in GitHub Desktop.
Save j-quelly/5cbcaefe8b38aaef41f5985a98f5d0f9 to your computer and use it in GitHub Desktop.
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