Created
July 19, 2012 08:00
-
-
Save derek/3141472 to your computer and use it in GitHub Desktop.
App example - Github Contributors
This file contains 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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Example: GitHub Contributors - YUI Library</title> | |
<meta name="viewport" content="width=960" id="meta-viewport"> | |
<script> | |
if (screen.width < 768) { | |
document.getElementById('meta-viewport').setAttribute('content', 'width=768'); | |
} | |
</script> | |
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=PT+Sans:400,700,400italic,700italic"> | |
<link rel="stylesheet" href="http://yui.yahooapis.com/3.5.1/build/cssgrids/cssgrids-min.css"> | |
<link rel="stylesheet" href="http://yuilibrary.com/combo/css?main-min.css"> | |
<link rel="stylesheet" href="http://yuilibrary.com/vendor/prettify/prettify-min.css"> | |
<link rel="stylesheet" href="http://yuilibrary.com/css/docs-min.css"> | |
<script>var YUI_config={"filter":"min","maxURLLength":1024,"groups":{"site":{"combine":true,"comboBase":"/combo/js?","root":"","modules":{"hoverable":{"path":"hoverable-min.js","requires":["event-hover","node-base","node-event-delegate"]},"search":{"path":"search-min.js","requires":["autocomplete","autocomplete-highlighters","node-pluginhost"]},"api-filter":{"path":"apidocs/api-filter-min.js","requires":["autocomplete-base","autocomplete-highlighters","autocomplete-sources"]},"api-list":{"path":"apidocs/api-list-min.js","requires":["api-filter","api-search","event-key","node-focusmanager","tabview"]},"api-search":{"path":"apidocs/api-search-min.js","requires":["autocomplete-base","autocomplete-highlighters","autocomplete-sources","escape"]}}}}};</script> | |
<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script> | |
</head> | |
<body class="yui3-skin-sam"> | |
<div id="github-app"></div> | |
<style scoped> | |
.scrollable pre { | |
overflow-y: auto; | |
max-height: 40em; | |
-webkit-overflow-scrolling: touch; | |
} | |
.screenshot { | |
display: block; | |
width: 584px; | |
margin: 0 auto; | |
} | |
</style> | |
<section> | |
<div class="example"> | |
<style scoped> | |
/*-- Override Styles ---------------------------------------------------------*/ | |
.example { | |
padding: 0 !important; | |
} | |
/*-- Common Styles -----------------------------------------------------------*/ | |
#github-app h1 { | |
padding: 0; | |
} | |
#github-app .avatar, | |
#github-app .avatar img { | |
display: block; | |
width: 76px; | |
height: 76px; | |
border: 0; | |
-webkit-border-radius: 3px; | |
-moz-border-radius: 3px; | |
border-radius: 3px; | |
} | |
#github-app .avatar { | |
display: inline-block; | |
*display: inline; zoom: 1; | |
height: 76px; | |
padding: 2px; | |
} | |
#github-app .avatar:hover, | |
#github-app .user-avatar { | |
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.25); | |
-moz-box-shadow: 0 0 2px rgba(0,0,0,0.25); | |
box-shadow: 0 0 2px rgba(0,0,0,0.25); | |
} | |
#github-app .back, | |
#github-app .view-on-github { | |
margin-top: 0; | |
} | |
#github-app .view-on-github { | |
text-align: right; | |
} | |
#github-app .info h1 { | |
margin: 0; | |
} | |
#github-app .info .user-avatar, | |
#github-app .info .user-login, | |
#github-app .info .user-name, | |
#github-app .info .repo-name { | |
vertical-align: middle; | |
} | |
#github-app .info .user-avatar { | |
margin-right: 10px; | |
} | |
#github-app .info .user-name { | |
font-weight: 400; | |
} | |
#github-app .stats ul { | |
margin: 0; | |
padding: 8px; | |
color: #30418C; | |
background: #F0F1F8; | |
border: 1px solid #D4D8EB; | |
-webkit-border-radius: 3px; | |
-moz-border-radius: 3px; | |
border-radius: 3px; | |
text-align: center; | |
list-style: none; | |
} | |
#github-app .stats b { | |
display: block; | |
font-size: 36px; | |
line-height: 44px; | |
} | |
/*-- HomePage Styles ---------------------------------------------------------*/ | |
#github-app .home-page { | |
padding: 1em; | |
} | |
#github-app .home-page h1, | |
#github-app .home-page p { | |
display: block; | |
text-align: center; | |
} | |
#github-app .home-page label, | |
#github-app .home-page input, | |
#github-app .home-page button { | |
font-size: 20px; | |
} | |
#github-app .home-page label { | |
color: #A6A6A6; | |
} | |
#github-app .home-page input { | |
display: inline-block; | |
*display: inline; zoom: 1; | |
font-family: Helvetica, sans-serif; | |
line-height: normal; | |
margin: 5px auto 0; | |
padding: 6px; | |
width: 200px; | |
text-align: left; | |
} | |
/*-- UserPage Styles ---------------------------------------------------------*/ | |
#github-app .user-page { | |
padding: 1em; | |
} | |
#github-app .repos { | |
padding-left: 0; | |
list-style: none; | |
border-top: 1px solid #E5E6F1; | |
margin: 0 -13px; | |
} | |
#github-app .repo { | |
margin: 1px 0; | |
padding: 0 13px 6px; | |
color: #30418C; | |
background: #F9F9FC; | |
cursor: pointer; | |
border-bottom: 1px solid #E5E6F1; | |
} | |
#github-app .repo:hover { | |
background: #F1F1F4; | |
} | |
#github-app .repo-name h3 { | |
margin: 6px 0 0 0; | |
} | |
#github-app .repo-stats { | |
margin-top: 6px; | |
background: url(../assets/app/arrow.gif) right center no-repeat; | |
} | |
#github-app .repo-stats ul { | |
list-style: none; | |
padding-left: 0; | |
height: 47px; | |
line-height: 47px; | |
} | |
#github-app .repo-stats b { | |
display: block; | |
} | |
#github-app .repo-stats span { | |
display: block; | |
font-size: 11px; | |
color: #B6BCD7; | |
color: rgba(48, 65, 140, 0.40); | |
} | |
#github-app .repo-stats .repo-watchers, | |
#github-app .repo-stats .repo-forks { | |
text-align: center; | |
line-height: 11px; | |
margin-top: 15px; | |
} | |
#github-app .repo-desc { | |
margin: 10px 20px 0 0; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
color: #B6BCD7; | |
color: rgba(48, 65, 140, 0.40); | |
} | |
/*-- RepoPage Styles ---------------------------------------------------------*/ | |
#github-app .repo-page { | |
padding: 1em; | |
} | |
#github-app .contributors { | |
margin-left: -5px; | |
margin-right: -5px; | |
padding-left: 0; | |
list-style: none; | |
} | |
#github-app .contributor { | |
margin: 5px; | |
display: inline-block; | |
*display: inline; zoom: 1; | |
} | |
#github-app .contributor .avatar { | |
position: relative; | |
display: block; | |
} | |
#github-app .contributor .avatar:link, | |
#github-app .contributor .avatar:visited, | |
#github-app .contributor .avatar:hover, | |
#github-app .contributor .avatar:active { | |
text-decoration: none; | |
} | |
#github-app .contributor-name, | |
#github-app .contributor-contributions { | |
position: absolute; | |
left: 0; | |
margin: 2px; | |
padding: 0 4px; | |
width: 68px; | |
font-size: 11px; | |
color: #fff; | |
background: #000; | |
background: rgba(0,0,0, 0.2); | |
text-shadow: 0 1px 0 rgba(0,0,0, 0.4); | |
} | |
#github-app .contributor-name { | |
bottom: 0; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
-webkit-border-bottom-right-radius: 3px; | |
-webkit-border-bottom-left-radius: 3px; | |
-moz-border-radius-bottomright: 3px; | |
-moz-border-radius-bottomleft: 3px; | |
border-bottom-right-radius: 3px; | |
border-bottom-left-radius: 3px; | |
} | |
#github-app .contributor-contributions { | |
top: 0; | |
-webkit-border-top-left-radius: 3px; | |
-webkit-border-top-right-radius: 3px; | |
-moz-border-radius-topleft: 3px; | |
-moz-border-radius-topright: 3px; | |
border-top-left-radius: 3px; | |
border-top-right-radius: 3px; | |
} | |
</style> | |
<!-- | |
HomePage Template: | |
Template HTML used by the <code>HomePageView</code> to render the app's home page. | |
--> | |
<script id="t-home" type="text/x-handlebars-template"> | |
<h1>Contributors of GitHub Projects</h1> | |
<p> | |
<input id="github-app-username" type="text" value="{{login}}" placeholder="GitHub Username" /> | |
<button class="yui3-button">Show Repos</button> | |
</p> | |
<p> | |
<label for="github-app-username">(e.g. yui, davglass, rgrove)</label> | |
</p> | |
</script> | |
<!-- | |
User Template: | |
Template HTML used by the <code>UserPageView</code> to render the header section of a | |
user's page in the app; this displays info and stats about the GitHub user. | |
--> | |
<script id="t-user" type="text/x-handlebars-template"> | |
<div class="yui3-g"> | |
<p class="back yui3-u-1-2"> | |
<a href="#/">« Choose Someone Different</a> | |
</p> | |
<p class="view-on-github yui3-u-1-2"> | |
<a href="{{html_url}}">View on GitHub</a> | |
</p> | |
</div> | |
<div class="yui3-g"> | |
<div class="info yui3-u-2-3"> | |
<h1> | |
<span class="avatar user-avatar"> | |
<img src="{{avatar_url}}" alt="{{login}}'s avatar" /> | |
</span> | |
<span class="user-login">{{login}}</span> | |
{{#if name}} | |
<span class="user-name">({{name}})</span> | |
{{/if}} | |
</h1> | |
</div> | |
<div class="stats yui3-u-1-3"> | |
<ul class="yui3-g"> | |
<li class="user-repos yui3-u-1-2"> | |
<b>{{public_repos}}</b> {{public_repos_label}} | |
</li> | |
<li class="user-followers yui3-u-1-2"> | |
<b>{{followers}}</b> {{followers_label}} | |
</li> | |
</ul> | |
</div> | |
</div> | |
</script> | |
<!-- | |
RepoList Template: | |
Template HTML used by the <code>UserPageView</code> to render the main section of a user's | |
page in the app; this displays the list of project repos for the GitHub user. | |
--> | |
<script id="t-repo-list" type="text/x-handlebars-template"> | |
<h2 class="no-toc">Public Repositories</h2> | |
<ul class="repos"> | |
{{#each repos}} | |
<li id="{{clientId}}" class="repo yui3-g"> | |
<div class="repo-name yui3-u-2-3"> | |
<h3 class="no-toc"> | |
<a href="#/github/{{owner.login}}/{{name}}/">{{name}}</a> | |
</h3> | |
<p class="repo-desc">{{description}}</p> | |
</div> | |
<div class="repo-stats yui3-u-1-3"> | |
<ul class="yui3-g"> | |
<li class="repo-lang yui3-u-1-3"> | |
<b>{{language}}</b> | |
</li> | |
<li class="repo-watchers yui3-u-1-3"> | |
<b>{{watchers}}</b> | |
<span>{{watchers_label}}</span> | |
</li> | |
<li class="repo-forks yui3-u-1-3"> | |
{{#if forks}} | |
<b>{{forks}}</b> | |
<span>{{forks_label}}</span> | |
{{/if}} | |
</li> | |
</ul> | |
</div> | |
</li> | |
{{/each}} | |
</ul> | |
</script> | |
<!-- | |
Repo Template: | |
Template HTML used by the <code>RepoPageView</code> to render the header section of a | |
repo page in the app; this displays info and stats about a GitHub user's repo. | |
--> | |
<script id="t-repo" type="text/x-handlebars-template"> | |
<div class="yui3-g"> | |
<p class="back yui3-u-1-2"> | |
<a href="#/github/{{owner.login}}/">« Select Repository</a> | |
</p> | |
<p class="view-on-github yui3-u-1-2"> | |
<a href="{{html_url}}">View on GitHub</a> | |
</p> | |
</div> | |
<div class="yui3-g"> | |
<div class="info yui3-u-2-3"> | |
<h1> | |
<span class="avatar user-avatar"> | |
<img src="{{owner.avatar_url}}" alt="{{owner.login}}'s avatar" /> | |
</span> | |
<span class="user-login">{{owner.login}}</span> / | |
<span class="repo-name">{{name}}</span> | |
</h1> | |
</div> | |
<div class="stats yui3-u-1-3"> | |
<ul class="yui3-g"> | |
<li class="repo-watchers yui3-u-1-2"> | |
<b>{{watchers}}</b> {{watchers_label}} | |
</li> | |
<li class="repo-forks yui3-u-1-2"> | |
<b>{{forks}}</b> {{forks_label}} | |
</li> | |
</ul> | |
</div> | |
</div> | |
</script> | |
<!-- | |
ContributorList Template: | |
Template HTML used by the <code>RepoPageView</code> to render the main section of a repo | |
page in the app; this displays the list of contributors to the GitHub project. | |
--> | |
<script id="t-contributor-list" type="text/x-handlebars-template"> | |
<h2 class="no-toc">Project Contributors ({{num}})</h2> | |
<ul class="contributors"> | |
{{#each contributors}} | |
<li class="contributor"> | |
<a class="avatar" href="#/github/{{login}}/" | |
title="{{login}} has {{contributions}} {{contributions_label}}"> | |
<img alt="{{login}}'s avatar" src="{{avatar_url}}" /> | |
<span class="contributor-name">{{login}}</span> | |
<span class="contributor-contributions">{{contributions}}</span> | |
</a> | |
</li> | |
{{/each}} | |
</ul> | |
</script> | |
<script> | |
YUI().use('app', 'handlebars', 'jsonp', 'cssbutton', function (Y) { | |
var User, Repo, RepoList, Contributor, ContributorList, | |
UserView, RepoView, RepoListView, ContributorListView, | |
HomePageView, UserPageView, RepoPageView, | |
ContributorsApp; | |
// -- Utility Functions -------------------------------------------------------- | |
// A helper function used by the Views to create labels for numerical fields | |
// which are correctly pluralized, and puts the label back on the <code>data</code> object. | |
function addLabel(data, field, label) { | |
var num = data[field]; | |
data[field] || (data[field] = '0'); | |
data[field + '_label'] = num === 1 ? label : (label + 's'); | |
} | |
// -- GithubSync --------------------------------------------------------------- | |
function GithubSync() {} | |
GithubSync.API_ORIGIN = 'https://api.github.com'; | |
GithubSync.prototype = { | |
// Intended to be overridden with a GitHub API endpoint URL. | |
githubURL: '/', | |
// Can be overridden to customize the actual URL used when making the | |
// request; this way the <code>githubURL</code> can be more of a template, and this | |
// method can substitute place-holders in the template with specific values. | |
buildURL: function () { | |
return this.githubURL; | |
}, | |
// Provides an implementation for a Model/ModelList's <code>sync()</code> method which | |
// can read data from GitHub's API. | |
sync: function (action, options, callback) { | |
Y.Lang.isFunction(callback) || (callback = function () {}); | |
// Enforce ready-only constraint. | |
if (action !== 'read') { | |
return callback('Read-only'); | |
} | |
// Creates the API URL and adds params for 100 items per-page and a | |
// JSONP callback placeholder. | |
var url = this.buildURL() + '?per_page=100&callback={callback}'; | |
// Performs JSONP request to GitHub's API. | |
Y.jsonp(GithubSync.API_ORIGIN + url, function (res) { | |
var meta = res.meta, | |
data = res.data; | |
// Check for a successful response, otherwise return the error | |
// message returned by the GitHub API. | |
if (meta.status >= 200 && meta.status < 300) { | |
callback(null, res); | |
} else { | |
callback(data.message, res); | |
} | |
}); | |
}, | |
// Provides an implementation for a Model/ModelList's <code>parse()</code> method which | |
// simply returns the <code>data</code> object or array from the response JSON. | |
parse: function (res) { | |
return res.data; | |
} | |
}; | |
// -- User --------------------------------------------------------------------- | |
User = Y.Base.create('user', Y.Model, [GithubSync], { | |
githubURL: '/users/{user}', | |
buildURL: function () { | |
return Y.Lang.sub(this.githubURL, { | |
user: this.getAsURL('login') | |
}); | |
} | |
}, { | |
// These attributes correspond to the GitHub user data we care about: | |
// http://developer.github.com/v3/users/ | |
ATTRS: { | |
login : {value: null}, | |
name : {value: null}, | |
html_url : {value: null}, | |
avatar_url : {value: null}, | |
public_repos : {value: 0}, | |
followers : {value: 0} | |
} | |
}); | |
// -- Repo --------------------------------------------------------------------- | |
Repo = Y.Base.create('repo', Y.Model, [], { | |
// The <code>id</code> attribute for this Model will be an alias for <code>name</code>. | |
idAttribute: 'name' | |
}, { | |
// These attributes correspond to the GitHub repo data we care about: | |
// http://developer.github.com/v3/repos/ | |
ATTRS: { | |
name : {value: null}, | |
html_url : {value: null}, | |
description: {value: null}, | |
watchers : {value: 0}, | |
forks : {value: 0}, | |
language : {value: null}, | |
owner : {value: null} | |
} | |
}); | |
// -- RepoList ----------------------------------------------------------------- | |
RepoList = Y.Base.create('repoList', Y.ModelList, [GithubSync], { | |
model : Repo, | |
githubURL: '/users/{user}/repos', | |
buildURL: function () { | |
// This list's <code>user</code> is used to build the API URL. | |
return Y.Lang.sub(this.githubURL, { | |
user: this.get('user').getAsURL('login') | |
}); | |
}, | |
// Override the default <code>comparator()</code> so items are sorted by most number of | |
// watchers to least number of watchers. | |
comparator: function (repo) { | |
// Note the minus sign. | |
return -repo.get('watchers'); | |
} | |
}, { | |
ATTRS: { | |
// A <code>RepoList</code> has a <code>User</code> associated with it, this allows for a | |
// loose-coupling between a user and set of repositories. | |
user: {value: null} | |
} | |
}); | |
// -- Contributor -------------------------------------------------------------- | |
Contributor = Y.Base.create('contributor', User, [], {}, { | |
ATTRS: { | |
contributions: {value: 0} | |
} | |
}); | |
// -- ContributorList ---------------------------------------------------------- | |
ContributorList = Y.Base.create('contributorList', Y.ModelList, [GithubSync], { | |
model : Contributor, | |
githubURL: '/repos/{user}/{repo}/contributors', | |
buildURL: function () { | |
// This list's <code>repo</code> is used to build the API URL. | |
var repo = this.get('repo'); | |
return Y.Lang.sub(this.githubURL, { | |
user: repo.getAsURL('owner.login'), | |
repo: repo.getAsURL('name') | |
}); | |
}, | |
// Override the default <code>comparator()</code> so items are sorted by most number of | |
// contributors to least number of contributors. | |
comparator: function (contributor) { | |
// Note the minus sign. | |
return -contributor.get('contributions'); | |
} | |
}, { | |
ATTRS: { | |
// A <code>ContributorList</code> has a <code>Repo</code> associated with it, this allows for | |
// a loose-coupling between a repo and set of contributors. | |
repo: {value: null} | |
} | |
}); | |
// -- User View ---------------------------------------------------------------- | |
UserView = Y.Base.create('userView', Y.View, [], { | |
// Compiles the User Template into a reusable Handlebars template. | |
template: Y.Handlebars.compile(Y.one('#t-user').getHTML()), | |
render: function () { | |
// Retrieves all of the model instance's data as a simple JSON struct. | |
var user = this.get('model').toJSON(), | |
content; | |
// Add proper pluralized labels for numerical data fields. | |
addLabel(user, 'public_repos', 'Public Repo'); | |
addLabel(user, 'followers', 'Follower'); | |
// Applies the <code>User</code> model data to the User Template and sets the | |
// resulting HTML as the contents of this view's container. | |
content = this.template(user); | |
this.get('container').setHTML(content); | |
return this; | |
} | |
}); | |
// -- Repo View ---------------------------------------------------------------- | |
RepoView = Y.Base.create('repoView', Y.View, [], { | |
// Compiles the Repo Template into a reusable Handlebars template. | |
template: Y.Handlebars.compile(Y.one('#t-repo').getHTML()), | |
render: function () { | |
// Retrieves all of the model instance's data as a simple JSON struct. | |
var repo = this.get('model').toJSON(), | |
content; | |
// Add proper pluralized labels for numerical data fields. | |
addLabel(repo, 'watchers', 'Watcher'); | |
addLabel(repo, 'forks', 'Fork'); | |
// Applies the <code>Repo</code> model data to the Repo Template and sets the | |
// resulting HTML as the contents of this view's container. | |
content = this.template(repo); | |
this.get('container').setHTML(content); | |
return this; | |
} | |
}); | |
// -- RepoList View ------------------------------------------------------------ | |
RepoListView = Y.Base.create('repoListView', Y.View, [], { | |
// Compiles the RepoList Template into a reusable Handlebars template. | |
template: Y.Handlebars.compile(Y.one('#t-repo-list').getHTML()), | |
// Attach DOM events for the view. The <code>events</code> object is a mapping of | |
// selectors to an object containing one or more events to attach to the | |
// node(s) matching each selector. | |
events: { | |
'.repo': { | |
click: 'selectRepo' | |
} | |
}, | |
initializer: function () { | |
// The <code>selectRepo</code> event is fired when the user chooses a GitHub repo | |
// to view its contributors. This event will bubble up to the | |
// <code>ContributorsApp</code> via the <code>UserPageView</code> view when it is the app's | |
// <code>activeView</code>. | |
this.publish('selectRepo', {preventable: false}); | |
}, | |
render: function () { | |
var repos = this.get('modelList'), | |
reposData, content; | |
// Iterates over all <code>Repo</code> models in the list and retrieves each model | |
// instance's data as a simple JSON structs and collects it in an array. | |
reposData = repos.map(function (repo) { | |
var data = repo.toJSON(); | |
// Add <code>clientId</code> to the data, this is ignored by <code>toJSON()</code>. This | |
// will be used by the template and is an easy way to regain access | |
// to the associated Repo model. | |
data.clientId = repo.get('clientId'); | |
// Add proper pluralized labels for numerical data fields. | |
addLabel(data, 'watchers', 'Watcher'); | |
addLabel(data, 'forks', 'Fork'); | |
return data; | |
}); | |
// Applies the <code>RepoList</code> data to the RepoList Template and sets the | |
// resulting HTML as the contents of this view's container. | |
content = this.template({repos: reposData}); | |
this.get('container').setHTML(content); | |
return this; | |
}, | |
// Called when the user clicks anywhere on a repo line-item in our list | |
// of repos. This will retreive the corresponding Repo model instance | |
// from the RepoList via the <code>clientId</code>. | |
selectRepo: function (e) { | |
// Noop when the element clicked is an anchor, we'll let the link | |
// preform its default action. | |
if (e.target.test('a')) { | |
return; | |
} | |
// Because we've rendered our list of repos with the <code>clientId</code> as the | |
// <code>id</code> attribute for each repo node, we can use the client id to | |
// retreive the Repo model instance from the RepoList. | |
var repos = this.get('modelList'), | |
repo = repos.getByClientId(e.currentTarget.get('id')); | |
if (repo) { | |
this.fire('selectRepo', {repo: repo}); | |
} | |
} | |
}); | |
// -- ContributorList View ----------------------------------------------------- | |
ContributorListView = Y.Base.create('contributorListView', Y.View, [], { | |
// Compiles the ContributorList Template into a reusable Handlebars template. | |
template: Y.Handlebars.compile(Y.one('#t-contributor-list').getHTML()), | |
render: function () { | |
var contributors = this.get('modelList'), | |
contributorsData, content; | |
// Iterates over all <code>Contributor</code> models in the list and retrieves a | |
// sub-set of each model instance's data as a simple JSON structs and | |
// collects it in an array. | |
contributorsData = contributors.map(function (contributor) { | |
// Only a few of the <code>Contributor</code> data attributes are needed. | |
var data = contributor.getAttrs(['login', 'avatar_url', 'contributions']); | |
// Add proper pluralized labels for numerical data fields. | |
addLabel(data, 'contributions', 'Contribution'); | |
return data; | |
}); | |
// Applies the <code>ContributorList</code> data to the ContributorList Template | |
// along with the total number of contributors for the repo. | |
content = this.template({ | |
num : contributors.size(), | |
contributors: contributorsData | |
}); | |
// Sets the resulting HTML from apply the data to the template as the | |
// contents of this view's container. | |
this.get('container').setHTML(content); | |
return this; | |
} | |
}); | |
// -- HomePage View ------------------------------------------------------------ | |
HomePageView = Y.Base.create('homePageView', Y.View, [], { | |
// Compiles the HomePage Template into a reusable Handlebars template. | |
template: Y.Handlebars.compile(Y.one('#t-home').getHTML()), | |
// Attach DOM events for the view. The <code>events</code> object is a mapping of | |
// selectors to an object containing one or more events to attach to the | |
// node(s) matching each selector. | |
events: { | |
'button': { | |
click: 'changeUser' | |
}, | |
'input': { | |
keypress: 'enter' | |
} | |
}, | |
initializer: function () { | |
// The <code>changeUser</code> event is fired when the user chooses a GitHub user | |
// to start browsing. This event will bubble up to the <code>ContributorsApp</code> | |
// when this view is the app's <code>activeView</code>. | |
this.publish('changeUser', {preventable: false}); | |
}, | |
render: function () { | |
// Retrieves just the <code>login</code> of the <code>User</code> model instance and applies | |
// it to the HomePage Template. | |
var user = this.get('model'), | |
content = this.template(user.getAttrs(['login'])); | |
// Adds the "home-page" CSS class to aid styling and sets the resulting | |
// HTML as the contents of this view's container. | |
this.get('container').addClass('home-page').setHTML(content); | |
return this; | |
}, | |
// Called when the user clicks the "Show Repos" button. This will retrieve | |
// the GitHub username from the text <code><input></code> and fire the <code>changeUser</code> | |
// event, passing on the username to the app. | |
changeUser: function () { | |
var user = this.get('container').one('input').get('value'); | |
if (user) { | |
this.fire('changeUser', {user: user}); | |
} | |
}, | |
// Called when the user types inside the text <code><input></code>; when the "enter" | |
// key is pressed, this will call the <code>changeUser()</code> method. | |
enter: function (e) { | |
// Check for "enter" keypress. | |
if (e.keyCode === 13) { | |
this.changeUser(); | |
} | |
} | |
}); | |
// -- UserPage View ------------------------------------------------------------ | |
UserPageView = Y.Base.create('userPageView', Y.View, [], { | |
initializer: function () { | |
var user = this.get('model'), | |
repos = this.get('modelList'); | |
// This view serves as a "page"-level view containing two sub-views to | |
// which it delegates rendering and stitches together the resulting UI. | |
// Sub-views are created to render the <code>User</code> and <code>RepoList</code>. | |
this.userView = new UserView({model: user}); | |
this.repoListView = new RepoListView({modelList: repos}); | |
// This will cause the sub-views' custom events to bubble up to here. | |
this.userView.addTarget(this); | |
this.repoListView.addTarget(this); | |
}, | |
// This destructor is specified so this view's sub-views can be properly | |
// destroyed and cleaned up. | |
destructor: function () { | |
this.userView.destroy(); | |
this.repoListView.destroy(); | |
delete this.userView; | |
delete this.repoListView; | |
}, | |
render: function () { | |
// A document fragment is created to hold the resulting HTML created | |
// from rendering the two sub-views. | |
var content = Y.one(Y.config.doc.createDocumentFragment()); | |
// This renders each of the two sub-views into the document fragment, | |
// then sets the fragment as the contents of this view's container. | |
content.append(this.userView.render().get('container')); | |
content.append(this.repoListView.render().get('container')); | |
// Adds the "user-page" CSS class to aid styling and sets the document | |
// fragment containing the two rendered sub-views as the contents of | |
// this view's container. | |
this.get('container').addClass('user-page').setHTML(content); | |
return this; | |
} | |
}); | |
// -- RepoPage View ------------------------------------------------------------ | |
RepoPageView = Y.Base.create('repoPageView', Y.View, [], { | |
initializer: function () { | |
var repo = this.get('model'), | |
contributors = this.get('modelList'); | |
// This view serves as a "page"-level view containing two sub-views to | |
// which it delegates rendering and stitches together the resulting UI. | |
// Sub-views are created to render the <code>RepoView</code> and | |
// <code>ContributorListView</code>. | |
this.repoView = new RepoView({model: repo}); | |
this.contributorListView = new ContributorListView({modelList: contributors}); | |
// This will cause the sub-views' custom events to bubble up to here. | |
this.repoView.addTarget(this); | |
this.contributorListView.addTarget(this); | |
}, | |
// This destructor is specified so this view's sub-views can be properly | |
// destroyed and cleaned up. | |
destructor: function () { | |
this.repoView.destroy(); | |
this.contributorListView.destroy(); | |
delete this.repoView; | |
delete this.contributorListView; | |
}, | |
render: function () { | |
// A document fragment is created to hold the resulting HTML created | |
// from rendering the two sub-views. | |
var content = Y.one(Y.config.doc.createDocumentFragment()); | |
// This renders each of the two sub-views into the document fragment, | |
// then sets the fragment as the contents of this view's container. | |
content.append(this.repoView.render().get('container')); | |
content.append(this.contributorListView.render().get('container')); | |
// Adds the "repo-page" CSS class to aid styling and sets the document | |
// fragment containing the two rendered sub-views as the contents of | |
// this view's container. | |
this.get('container').addClass('repo-page').setHTML(content); | |
return this; | |
} | |
}); | |
// -- Contributors App --------------------------------------------------------- | |
ContributorsApp = Y.Base.create('contributorsApp', Y.App, [], { | |
// This is where we can declare our page-level views and define the | |
// relationship between the "pages" of our application. We can later use the | |
// <code>showView()</code> method to create and display these views. | |
views: { | |
homePage: { | |
type: HomePageView | |
}, | |
userPage: { | |
type: UserPageView | |
}, | |
repoPage: { | |
type : RepoPageView, | |
parent: 'userPage' | |
} | |
}, | |
initializer: function () { | |
// Here we register a listener for the <code>HomePageView</code>'s <code>changeUser</code> | |
// event. When the <code>HomePageView</code> is the <code>activeView</code>, its events will | |
// bubble up to this app instance. | |
this.on('*:changeUser', this.navigateToUser); | |
// Here we register a listener for the <code>RepoListView</code>'s <code>selectRepo</code> | |
// event. The <code>RepoListView</code>'s events bubble up to the <code>UserPageView</code>, | |
// so when it is the <code>activeView</code>, its events will bubble up to this | |
// app instance. | |
this.on('*:selectRepo', this.navigateToRepo); | |
// Once our app is ready, we'll either dispatch to our route-handlers if | |
// the current URL matches one of our routes, or we'll simply show the | |
// <code>HomePageView</code>. | |
this.once('ready', function (e) { | |
if (this.hasRoute(this.getPath())) { | |
this.dispatch(); | |
} else { | |
this.showHomePage(); | |
} | |
}); | |
}, | |
// -- Event Handlers ------------------------------------------------------- | |
// When called, this will navigate the application to the user-page for the | |
// GitHub username specified on the event facade. This will cause our app to | |
// dispatch to its route-handlers along with updating the URL. | |
navigateToUser: function (e) { | |
var activeView = this.get('activeView'); | |
// We want to add a history entry for "/" when we're showing the home | |
// page, but the app's current path isn't already "/". This provides | |
// proper back button support and helps get around a side effect of | |
// using hash based URLs in this example. | |
if (activeView instanceof HomePageView && this.getPath() !== '/') { | |
// Adds history entry for "/" so we can get back to the home page | |
// via the back button. | |
this.save('/'); | |
} | |
this.navigate('/github/' + e.user + '/'); | |
}, | |
// When called, this will navigate the application to the repo-page for the | |
// GitHub repository specified on the event facade. This will cause our app | |
// to dispatch to its route-handlers along with updating the URL. | |
navigateToRepo: function (e) { | |
var repo = e.repo; | |
this.navigate('/github/' + repo.get('owner.login') + '/' + repo.get('name') + '/'); | |
}, | |
// -- Route Handlers ------------------------------------------------------- | |
// This will be called for all URLs which start with: "/github/:user/"; its | |
// job is to make sure a <code>User</code> model for the specified GitHub username is | |
// fully-loaded and placed on the request object for other route-handlers. | |
handleUser: function (req, res, next) { | |
var username = req.params.user, | |
user = this.get('user'), | |
self = this; | |
// When the current <code>User</code> model set on the app is new or the specified | |
// GitHub username from the URL is different, a new user model instance | |
// is created, loaded, and set on this app before adding it to the | |
// request object. | |
if (username === user.get('login') && !user.isNew()) { | |
// Places a reference to the user model on the request object before | |
// delegating to the next route-handler. | |
req.user = user; | |
next(); | |
} else { | |
// Create a new <code>User</code> model instance using the specified GitHub | |
// username from the URL. | |
user = new User({login: username}); | |
// Load the user's data from the GitHub API, then sent the user | |
// model on both this app and the request object before delegating | |
// to the next route-handler. | |
user.load(function () { | |
self.set('user', user); | |
req.user = user; | |
next(); | |
}); | |
} | |
}, | |
// This will be called for all URLs which start with: "/github/:user/"; its | |
// job is to make sure a fully-loaded <code>RepoList</code> instance is placed on the | |
// request object. It is assumed that <code>handleUser()</code> route-handler has | |
// already placed the <code>User</code> model on the request object. | |
handleRepos: function (req, res, next) { | |
var user = req.user, | |
repos = this.get('repos'); | |
// Adds a reference to this app's <code>RepoList</code> to the request object. | |
req.repos = repos; | |
// This makes sure the <code>RepoList</code> is loaded for the current user. | |
if (user === repos.get('user')) { | |
next(); | |
} else { | |
// A fade transition is preferred when we've switched users, so it | |
// is added to the response object. | |
res.transition = 'fade'; | |
// Sets the current user model on the <code>RepoList</code> instance and loads | |
// the repos for the given user before continuing. | |
repos.set('user', user).load(function () { | |
next(); | |
}); | |
} | |
}, | |
// This will be called for URLs which match: "/github/:user/:repo/"; its job | |
// is to make sure a fully-loaded <code>ContributorList</code> instance is placed on | |
// the request object. It is assumed that the <code>handleUser()</code> and | |
// <code>handleRepos()</code> route-handlers have already placed the <code>User</code> model and | |
// <code>RepoList</code> model-list on the request object. | |
handleRepo: function (req, res, next) { | |
// This uses data from the request object to look for a <code>Repo</code> model | |
// instance in the <code>RepoList</code>. | |
var repoId = req.params.repo, | |
repos = req.repos, | |
repo = repos.getById(repoId), | |
contributors = this.get('contributors'); | |
// We error-out when the specified repo name does not exist in the list | |
// of repos for the current GitHub user. | |
if (!repo) { | |
return next('GitHub repository was not found.'); | |
} | |
// Adds a reference to the <code>Repo</code> model and <code>ContributorList</code> to the | |
// request object. | |
req.repo = repo; | |
req.contributors = contributors; | |
// This makes sure the <code>ContributorList</code> is loaded for the current repo. | |
if (repo === contributors.get('repo')) { | |
next(); | |
} else { | |
// Sets the current repo model on the <code>ContributorList</code> instance and | |
// loads the contributors before continuing. | |
contributors.set('repo', repo).load(function () { | |
next(); | |
}); | |
} | |
}, | |
// This is called when the URL is "/" and will show our app's home page. | |
showHomePage: function (req) { | |
this.showView('homePage', { | |
model: this.get('user') | |
}); | |
}, | |
// This is the final route for "/github/:user/" URLs and will show our app's | |
// user page which lists the user's repos. | |
showUserPage: function (req, res) { | |
this.showView('userPage', { | |
model : req.user, | |
modelList: req.repos | |
}, { | |
// Overrides the default transition with the preferred one, if set. | |
transition: res.transition | |
}); | |
}, | |
// This is the final route for "/github/:user/:repo/" URLs and will show our | |
// app's repo page which lists the contributors to the repo/project. | |
showRepoPage: function (req, res) { | |
this.showView('repoPage', { | |
model : req.repo, | |
modelList: req.contributors | |
}, { | |
// Overrides the default transition with the preferred one, if set. | |
transition: res.transition | |
}); | |
} | |
}, { | |
ATTRS: { | |
// These attributes will be used by the app to hold its current state, | |
// and they will be accessed and modified by our route-handlers. | |
user : {value: new User()}, | |
repos : {value: new RepoList()}, | |
contributors: {value: new ContributorList()}, | |
// Our app will use more advanced routing features where multiple | |
// route-handlers will be used to fulfill a "request", allowing us to | |
// encapsulate and reuse our data processing logic. Note: the order the | |
// route-handlers are defined in is significant. | |
routes: { | |
value: [ | |
{path: '/', callback: 'showHomePage'}, | |
{path: '/github/:user/*', callback: 'handleUser'}, | |
{path: '/github/:user/*', callback: 'handleRepos'}, | |
{path: '/github/:user/', callback: 'showUserPage'}, | |
{path: '/github/:user/:repo/', callback: 'handleRepo'}, | |
{path: '/github/:user/:repo/', callback: 'showRepoPage'} | |
] | |
} | |
} | |
}); | |
// -- Go-go-gadget App! -------------------------------------------------------- | |
// Create and render a new instance of our <code>ContributorsApp</code>! | |
new ContributorsApp({ | |
// We force this to false for this example app because there is no server. | |
serverRouting: false, | |
// Here we set our app's rendering container, and restrict which links on | |
// the page should cause the app to navigate. | |
container : '#github-app', | |
linkSelector: '#github-app a', | |
// Enable view transitions when users are navigating around the app. | |
transitions: true, | |
// We'll define the default GitHub user to be "yui". | |
user: new User({login: 'yui'}) | |
}).render(); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment