Skip to content

Instantly share code, notes, and snippets.

@seanr707
Last active August 25, 2016 15:03
Show Gist options
  • Save seanr707/792b5e7208e0511496b9a65a82f11d15 to your computer and use it in GitHub Desktop.
Save seanr707/792b5e7208e0511496b9a65a82f11d15 to your computer and use it in GitHub Desktop.
Camper Leaderboard
<!-- Sean's FreeCodeCamp Leaderboard in React -->
#react-root
'use strict';
const Leaderboard = React.createClass({
// Set up array property to store data from _ajax
storedData: [],
getInitialState: function() {
return {
'recent': true,
'ready': false
};
},
componentDidMount: function() {
// preload data on mount
this._ajax(this.props.recent);
this._ajax(this.props.alltime);
},
_ajax: function(url) {
console.log('Fetching new data...');
const callback = (res) => {
// Adds array received from FreeCodeCamp API
this.storedData = this.storedData.concat(res.data);
// Sets ready to true to render first data (recent)
this.setState({'ready': true});
}
axios.get(url)
.then(callback)
.catch(err => {
console.log(err);
});
},
handleRecent: function () {
// Do nothing if Recent is already active
if (this.state.recent) return;
this.setState({'recent': !this.state.recent});
},
handleAlltime: function () {
if (!this.state.recent) return;
this.setState({'recent': !this.state.recent});
},
render: function () {
let recentHeader: string = 'Recent';
let alltimeHeader: string = 'All';
// Adds down arrow next to active sorting header
if (this.state.recent) {
recentHeader += '\u25BE';
} else {
alltimeHeader += '\u25BE';
}
// Contains main table and navbar (for use on small screens and mobile)
return (
<div id="base">
<nav id="mobileNav" className="nav navbar navbar-default navbar-fixed-top">
<div className="container">
<div className="row">
<div className={this.props.columns.position}>#</div>
<div className={this.props.columns.username}>Username</div>
<div className={this.props.columns.points} onClick={this.handleRecent}>{recentHeader}</div>
<div className={this.props.columns.points} onClick={this.handleAlltime}>{alltimeHeader}</div>
</div>
</div>
</nav>
<table id="mainTable" className="container">
<thead className="container">
<tr id="headers" className="row">
<th scope="col" id="leftCorner" className={this.props.columns.position}>#</th>
<th scope="col" className={this.props.columns.username}>Username</th>
<th scope="col" className={this.props.columns.points} onClick={this.handleRecent}>{recentHeader}</th>
<th scope="col" id="rightCorner" className={this.props.columns.points} onClick={this.handleAlltime}>{alltimeHeader}</th>
</tr>
</thead>
<Users data={this.storedData} sorting={this.state.recent ? 'recent' : 'alltime'} columns={this.props.columns} />;
</table>
</div>
);
}
});
const Users = React.createClass({
// Takes array and outputs top 25 user of designated sorting method
_sortArray: function (array, sorting) {
let tempObj = {};
// Populates temp object with the number of points of users as keys
array.forEach((data) => {
tempObj[data[sorting]] = data;
});
// Takes array of points/keys and sorts them from largest to smallest
return Object.keys(tempObj).sort((a, b) => {
// Sort numerically
return (a - b);
}).reverse().map((key, i) => {
// Add position property used for display and key(React)
tempObj[key]['position'] = i + 1;
// Places object into an array
return tempObj[key];
}).splice(0, 25).map(data => {
// returns top 25 users (of set brownie points)
return (
<User
key={data.position}
username={data.username}
avatar={data.img}
recent={data.recent}
alltime={data.alltime}
position={data.position}
columns={this.props.columns}
/>
);
});
},
// Renders 'Loading...' until data array is filled. Then populates table with User components
render: function () {
if (this.props.data.length === 0) return (<tbody><tr><td>Loading...</td></tr></tbody>);
let users = this._sortArray(this.props.data, this.props.sorting);
return (
<tbody className="container">
{users}
</tbody>
);
}
});
const User = React.createClass({
// Renders each row for a user in the table
render: function () {
const userLink = "https://freecodecamp.com/" + this.props.username;
return (
<tr className="row">
<td className={this.props.columns.position}>
{this.props.position}
</td>
<td className={this.props.columns.username}>
<a href={userLink}>
<img alt="avatar" src={this.props.avatar} />
<p className="username">{this.props.username}</p>
</a>
</td>
<td className={this.props.columns.points}>
{this.props.recent}
</td>
<td className={this.props.columns.points}>
{this.props.alltime}
</td>
</tr>
);
}
});
(function () {
const RECENT = 'https://fcctop100.herokuapp.com/api/fccusers/top/recent';
const ALLTIME = 'https://fcctop100.herokuapp.com/api/fccusers/top/alltime';
// Bootstrap widths for Columns of table
const columns = {
position: 'col-md-1 col-sm-1 col-xs-1 positionClass',
username: 'col-md-5 col-sm-5 col-xs-7 usernameClass',
points: 'col-md-3 col-sm-3 col-xs-2 pointsClass'
}
ReactDOM.render(<Leaderboard recent={RECENT} allTime={ALLTIME} columns={columns} />, document.getElementById('react-root'));
})();
<script src="https://fb.me/react-15.1.0.js"></script>
<script src="https://fb.me/react-dom-15.1.0.js"></script>
<script src="https://npmcdn.com/axios/dist/axios.min.js"></script>
$maxScreen: 800px;
body {
// Picture of cartoon brownies
background-image: url("http://static.vecteezy.com/system/resources/previews/000/101/295/original/brownie-vectors.jpg");
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
a {
text-decoration: none;
color: #555;
&:hover {
text-decoration: none;
}
}
img {
width: 32px;
margin: 10px 0 10px 0;
// Decreases margins to allow for less scrolling on smaller screens
@media screen and (max-width: $maxScreen) {
// visibility: hidden;
// width: 24px;
margin: 5px 0 5px 0;
}
}
.username {
// Keeps username inline with avatar
display: inline;
margin: 0 0 0 10px;
}
.positionClass {
text-align: right;
// padding: 5px;
}
.pointsClass {
text-align: center;
}
// Mobile navbar only to be shown on small screens
#mobileNav {
visibility: hidden;
@media screen and (max-width: $maxScreen) {
visibility: visible;
background: purple;
color: white;
// Padding to center text vertically
padding-top: 15px;
box-shadow: 0 1px 2px #777;
}
}
#mainTable {
$borderRadius: 5px;
// Gives purple glow to table
box-shadow: 0 1px 5px purple;
border-radius: $borderRadius $borderRadius 0 0;
background: purple;
opacity: .9;
// Gives offset for main view and mobile view
margin: 50px auto 50px auto;
transition: all 2s ease;
// Keeps leader small if screen is large
@media screen and (min-width: 1080px) {
width: 450px;
}
// Stretches table out to fullscreen on small screens
@media screen and (max-width: $maxScreen) {
width: 100%;
}
// Alternates colors
tbody {
tr:nth-child(even) {
background-color: white;
}
tr:nth-child(odd) {
background-color: #ededed;
}
}
// Data for table headers (cells)
th {
background: purple;
color: white;
padding: 5px;
// Gives roundness to top corner cells
&#leftCorner {
border-radius: $borderRadius 0 0 0;
}
&#rightCorner {
border-radius: 0 $borderRadius 0 0;
}
}
}
tr#headers {
// Removes weird background from messing up shadows
background: none;
// Hides header row when on mobile / small screen
@media screen and (max-width: $maxScreen) {
position: fixed;
top: 0;
visibility: hidden;
}
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment