Skip to content

Instantly share code, notes, and snippets.

@jungchris
Created December 5, 2016 01:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jungchris/9fe08afa426c5cbc07a4c50c8f2de166 to your computer and use it in GitHub Desktop.
Save jungchris/9fe08afa426c5cbc07a4c50c8f2de166 to your computer and use it in GitHub Desktop.
Scoreboard Application to Learn React
body {
background: #d5d5d5;
font-family: arial;
color: #FAFAFA;
text-transform: uppercase;
}
.scoreboard {
background: #333;
width: 700px;
margin: 70px auto;
box-shadow: 0 4px 0px #333;
border-radius: 15px;
}
.header {
padding: 5px 10px;
text-align: center;
display: flex;
align-items: center;
background-color: #222;
border-radius: 15px 15px 0 0;
border-bottom: solid 2px #444;
}
.header h1 {
flex-grow: 1;
font-size: 1.5em;
letter-spacing: 3px;
font-weight: normal;
}
.header .stats,
.header .stopwatch {
width: 170px;
}
.stats {
margin-top: 0;
font-weight: normal;
}
.stats td:first-child {
text-align: right;
font-weight: normal;
letter-spacing: 2px;
color: #666;
font-size: .7em;
}
.stats td:last-child {
text-align: left;
}
.stopwatch {
padding: 15px 10px 5px 10px;
margin: -5px -10px -5px 10px;
background: #2f2f2f;
border-radius: 0 15px 0 0;
}
.stopwatch-time {
font-family: monospace;
font-size: 2em;
}
.stopwatch button {
margin: 8px 5px;
background-color: #222;
border-radius: 5px;
padding: 7px 8px;
border: none;
color: #999;
letter-spacing: 2px;
font-weight: bold;
text-shadow: none;
text-transform: uppercase;
}
.stopwatch button:hover {
background: #4b71b5;
color: #fafafa;
cursor: pointer;
}
.stopwatch h2 {
font-size: .6em;
margin: 0;
font-weight: normal;
letter-spacing: 2px;
color: #666;
}
.player {
display: flex;
font-size: 1.2em;
border-bottom: solid 2px #444;
letter-spacing: 2px;
}
.remove-player {
visibility: hidden;
margin-right: 10px;
color: #e57373;
cursor: pointer;
}
.player-name:hover .remove-player {
visibility: visible;
}
.player-name {
flex-grow: 1;
padding: 20px 10px 10px 10px;
}
.player-score {
width: 190px;
background: blue;
}
.counter {
display: flex;
}
.counter-action {
border: none;
font-weight: bold;
color: #FAFAFA;
display: block;
padding: 20px 20px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.counter-action.increment {
background: #66BB6A;
}
.counter-action.increment:hover {
background: #549d59;
cursor: pointer;
}
.counter-action.decrement {
background: #ef5350;
}
.counter-action.decrement:hover {
background: #c44442;
cursor: pointer;
}
.counter-score {
flex-grow: 1;
background: #2b2b2b;
color: #FAFAFA;
text-align: center;
font-family: monospace;
padding: 10px;
font-size: 2em;
}
.add-player-form form {
display: flex;
background-color: #222;
border-radius: 0 0 20px 20px
}
.add-player-form input[type=text] {
flex-grow: 1;
border-width: 0 0 1px 0;
margin: 15px 10px 15px 15px;
padding: 10px;
border-radius: 5px;
background-color: #333;
border-style: none;
text-shadow: none;
text-transform: uppercase;
color: #999;
letter-spacing: 2px;
outline: none;
}
.add-player-form input[type=text]::-webkit-input-placeholder{
color: #666;
letter-spacing: 2px;
}
.add-player-form input[type=text]:focus{
background-color: #444;
}
.add-player-form input[type=submit] {
display: block;
font-size: .6em;
margin: 15px 15px 15px 0;
padding: 10px;
background-color: #333;
border-radius: 5px;
border: none;
color: #999;
letter-spacing: 2px;
font-weight: bold;
text-shadow: none;
text-transform: uppercase;
}
.add-player-form input[type=submit]:hover{
background: #4b71b5;
color: #fafafa;
cursor: pointer;
}
var PLAYERS = [
{
name: "Jim Hoskins",
score: 31,
id: 1,
},
{
name: "Andrew Chalkley",
score: 35,
id: 2,
},
{
name: "Alena Holligan",
score: 42,
id: 3,
},
];
var Stopwatch = React.createClass({
render: function() {
return (
<div className="stopwatch">
<h2>Stopwatch</h2>
<div className="stopwatch-time">0</div>
<button>Start</button>
<button>Reset</button>
</div>
);
}
});
var nextId = 4;
// stateful class for taking user input
var AddPlayerForm = React.createClass({
propTypes: {
onAdd: React.PropTypes.func.isRequired,
},
//
// // this is used by the form render function this.state.name
getInitialState: function() {
return {
name: "",
};
},
//
// note that setState is a native function
onNameChange: function(e) {
//console.log('onNameChange', e.target.value);
this.setState({name: e.target.value});
},
//
onSubmit: function(e) {
e.preventDefault();
// add name
this.props.onAdd(this.state.name);
// clear input field
this.setState({name: ""});
},
// <form onSubmit={this.onSubmit}>
// <input type="text" value={this.state.name} onChange={this.onNameChange} />
render: function() {
return (
<div className="add-player-form">
<form onSubmit={this.onSubmit}>
<input type="text" value={this.state.name} onChange={this.onNameChange} />
<input type="submit" value="Add Player" />
</form>
</div>
);
},
});
function Stats(props) {
var totalPlayers = props.players.length;
var totalPoints = props.players.reduce(function(total, player){
return total + player.score;
}, 0);
return (
<table className="stats">
<tbody>
<tr>
<td>Players: </td>
<td>{totalPlayers}</td>
</tr>
<tr>
<td>Total Points:</td>
<td>{totalPoints}</td>
</tr>
</tbody>
</table>
);
}
// making players a property of stats so that we can figure out the num of players in Stats
Stats.propTypes = {
players: React.PropTypes.array.isRequired,
};
function Header(props) {
return (
<div className="header">
<Stats players={props.players}/>
<h1>{props.title}</h1>
<Stopwatch />
</div>
);
}
Header.propTypes = {
title: React.PropTypes.string.isRequired,
players: React.PropTypes.array.isRequired,
};
function Counter(props) {
return (
<div className="counter">
<button className="counter-action decrement" onClick={function() {props.onChange(-1);}} > - </button>
<div className="counter-score"> {props.score} </div>
<button className="counter-action increment" onClick={function() {props.onChange(1);}}> + </button>
</div>
);
}
Counter.propTypes = {
score: React.PropTypes.number.isRequired,
onChange: React.PropTypes.func.isRequired,
}
//<a className="remove-player" onClick={props.onRemove}>X</a>
function Player(props) {
return (
<div className="player">
<div className="player-name">
<a className="remove-player" onClick={props.onRemove}>X</a>
{props.name}
</div>
<div className="player-score">
<Counter score={props.score} onChange={props.onScoreChange} />
</div>
</div>
);
}
Player.propTypes = {
name: React.PropTypes.string.isRequired,
score: React.PropTypes.number.isRequired,
onScoreChange: React.PropTypes.func.isRequired,
onRemove: React.PropTypes.func.isRequired,
};
// Which component in the Application currently holds state? (not Player, Counter)
var Application = React.createClass({
propTypes: {
title: React.PropTypes.string,
initialPlayers: React.PropTypes.arrayOf(React.PropTypes.shape({
name: React.PropTypes.string.isRequired,
score: React.PropTypes.number.isRequired,
id: React.PropTypes.number.isRequired,
})).isRequired,
},
getDefaultProps: function() {
return {
title: "Scoreboard",
}
},
getInitialState: function() {
return {
players: this.props.initialPlayers,
};
},
onScoreChange: function(index, delta) {
console.log('onScoreChange', index, delta);
this.state.players[index].score += delta;
this.setState(this.state);
},
onPlayerAdd: function(name) {
console.log('Player added:', name);
this.state.players.push({
name: name,
score: 0,
id: nextId,
});
this.setState(this.state);
nextId += 1;
},
onRemovePlayer: function(index) {
console.log('onRemovePlayer', index);
this.state.players.splice(index, 1);
this.setState(this.state);
},
render: function() {
return (
<div className="scoreboard">
<Header title={this.props.title} players={this.state.players} />
<div className="players">
{this.state.players.map(function(player, index) {
return (
<Player
onScoreChange={function(delta) {this.onScoreChange(index ,delta)}.bind(this)}
onRemove={function() {this.onRemovePlayer(index)}.bind(this)}
name={player.name}
score={player.score}
key={player.id} />
);
}.bind(this))}
</div>
<AddPlayerForm onAdd={this.onPlayerAdd} />
</div>
);
}
});
ReactDOM.render(<Application initialPlayers={PLAYERS}/>, document.getElementById('container'));
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Scoreboard</title>
<link rel="stylesheet" href="./app.css" />
</head>
<body>
<div id="container">Loading...</div>
<script src="./vendor/react.js"></script>
<script src="./vendor/react-dom.js"></script>
<script src="./vendor/babel-browser.min.js"></script>
<script type="text/babel" src="./app.jsx"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment