Commit b1c4be5f authored by Henrik Joreteg's avatar Henrik Joreteg Committed by Sindre Sorhus

Close GH-1020: Add ampersand.js. Fixes #936

parent 3dd0327c
<!doctype html>
<html lang="en" data-framework="ampersand">
<head>
<meta charset="utf-8">
<title>Ampersand.js • TodoMVC</title>
<link rel="stylesheet" href="todomvc-common/base.css">
</head>
<body>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" data-hook="todo-input" autofocus>
</header>
<section id="main">
<input id="toggle-all" type="checkbox" data-hook="mark-all">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-hook="todo-container"></ul>
</section>
<footer id="footer">
<span id="todo-count" data-hook="todo-count"></span>
<ul id="filters">
<li>
<a class="selected" href="#/" data-hook="all-mode">All</a>
</li>
<li>
<a href="#/active" data-hook="active-mode">Active</a>
</li>
<li>
<a href="#/completed" data-hook="completed-mode">Completed</a>
</li>
</ul>
<button id="clear-completed" data-hook="clear-completed">Clear completed (<span data-hook="completed-count">1</span>)</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Written by <a href="https://twitter.com/henrikjoreteg">Henrik Joreteg</a>, <a href="https://twitter.com/lukekarrys">Luke Karrys</a>, and <a href="https://twitter.com/philip_roberts">Philip Roberts</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="todomvc-common/base.js"></script>
<script src="todomvc.bundle.js"></script>
</body>
</html>
'use strict';
var MainView = require('./views/main');
var Me = require('./models/me');
var Router = require('./router');
window.app = {
init: function () {
// Model representing state for
// user using the app. Calling it
// 'me' is a bit of convention but
// it's basically 'app state'.
this.me = new Me();
// Our main view
this.view = new MainView({
el: document.body,
model: this.me
});
// Create and fire up the router
this.router = new Router();
this.router.history.start();
}
};
window.app.init();
// typically we us a 'me' model to represent state for the
// user of the app. So in an app where you have a logged in
// user this is where we'd store username, etc.
// We also use it to store session properties, which is the
// non-persisted state that we use to track application
// state for this user in this session.
'use strict';
var State = require('ampersand-state');
var Todos = require('./todos');
module.exports = State.extend({
initialize: function () {
// Listen to changes to the todos collection that will
// affect lengths we want to calculate.
this.listenTo(this.todos, 'change:completed change:title add remove', this.handleTodosUpdate);
// We also want to calculate these values once on init
this.handleTodosUpdate();
// Listen for changes to `mode` so we can update
// the collection mode.
this.on('change:mode', this.handleModeChange, this);
},
collections: {
todos: Todos
},
// We used only session properties here because there's
// no API or persistance layer for these in this app.
session: {
activeCount: {
type: 'number',
default: 0
},
completedCount: {
type: 'number',
default: 0
},
totalCount:{
type: 'number',
default: 0
},
allCompleted: {
type: 'boolean',
default: false
},
mode: {
type: 'string',
values: [
'all',
'completed',
'active'
],
default: 'all'
}
},
derived: {
// We produce this as an HTML snippet here
// for convenience since it also has to be
// pluralized it was easier this way.
itemsLeftHtml: {
deps: ['activeCount'],
fn: function () {
var plural = (this.activeCount === 1) ? '' : 's';
return '<strong>' + this.activeCount + '</strong> item' + plural + ' left';
}
}
},
// Calculate and set various lengths we're
// tracking. We set them as session properties
// so they're easy to listen to and bind to DOM
// where needed.
handleTodosUpdate: function () {
var completed = 0;
var todos = this.todos;
todos.each(function (todo) {
if (todo.completed) {
completed++;
}
});
this.set({
completedCount: completed,
activeCount: todos.length - completed,
totalCount: todos.length,
allCompleted: todos.length === completed
});
},
handleModeChange: function () {
this.todos.setMode(this.mode);
}
});
'use strict';
// We're using 'ampersand-state' here instead of 'ampersand-model'
// because we don't need any of the RESTful
// methods for this app.
var State = require('ampersand-state');
module.exports = State.extend({
// Properties this model will store
props: {
title: {
type: 'string',
default: ''
},
completed: {
type: 'boolean',
default: false
}
},
// session properties work the same way as `props`
// but will not be included when serializing.
session: {
editing: {
type: 'boolean',
default: false
}
},
destroy: function () {
if (this.collection) {
this.collection.remove(this);
}
}
});
'use strict';
var Collection = require('ampersand-collection');
var SubCollection = require('ampersand-subcollection');
var debounce = require('debounce');
var Todo = require('./todo');
var STORAGE_KEY = 'todos-ampersand';
module.exports = Collection.extend({
model: Todo,
initialize: function () {
// Attempt to read from localStorage
this.readFromLocalStorage();
// This is what we'll actually render
// it's a subcollection of the whole todo collection
// that we'll add/remove filters to accordingly.
this.subset = new SubCollection(this);
// We put a slight debounce on this since it could possibly
// be called in rapid succession.
this.writeToLocalStorage = debounce(this.writeToLocalStorage, 100);
// We listen for changes to the collection
// and persist on change
this.on('all', this.writeToLocalStorage, this);
},
// Helper for removing all completed items
clearCompleted: function () {
var toRemove = this.filter(function (todo) {
return todo.completed;
});
this.remove(toRemove);
},
// Updates the collection to the appropriate mode.
// mode can 'all', 'completed', or 'active'
setMode: function (mode) {
this.subset.clearFilters();
if (mode !== 'all') {
this.subset.configure({
where: {
completed: mode === 'completed'
}
});
}
},
// The following two methods are all we need in order
// to add persistance to localStorage
writeToLocalStorage: function () {
localStorage[STORAGE_KEY] = JSON.stringify(this);
},
readFromLocalStorage: function () {
var existingData = localStorage[STORAGE_KEY];
if (existingData) {
this.add(JSON.parse(existingData));
}
}
});
'use strict';
/*global app */
var Router = require('ampersand-router');
module.exports = Router.extend({
routes: {
'*filter': 'setFilter'
},
setFilter: function (arg) {
app.me.mode = arg || 'all';
}
});
//-
Note that the use of data-hook is optional. We use is as a
non-css related way to grab items to give desigers ability
the easily edit templates without fear of breaking the app.
This is also one of the big reasons we break templates out
into their own separate files. It makes it easy to find/edit
by a designer without having to dig into the JS too much.
more info: http://ampersandjs.com/learn/data-hook-attribute
li
.view
input.toggle(type="checkbox", data-hook="checkbox")
label(data-hook="title")
button.destroy(data-hook="action-delete")
input.edit(data-hook="input")
'use strict';
/*global app */
var View = require('ampersand-view');
var TodoView = require('./todo');
var ENTER_KEY = 13;
module.exports = View.extend({
events: {
'keypress [data-hook~=todo-input]': 'handleMainInput',
'click [data-hook~=mark-all]': 'handleMarkAllClick',
'click [data-hook~=clear-completed]': 'handleClearClick'
},
// Declaratively bind all our data to the template.
// This means only changed data in the DOM is updated
// with this approach we *only* ever touch the DOM with
// appropriate dom methods. Not just `innerHTML` which
// makes it about as fast as possible.
// These get re-applied if the view's element is replaced
// or if the model isn't there yet, etc.
// Binding type reference:
// http://ampersandjs.com/docs#ampersand-dom-bindings-binding-types
bindings: {
// Show hide main and footer
// based on truthiness of totalCount
'model.totalCount': {
type: 'toggle',
selector: '#main, #footer'
},
'model.completedCount': [
// Hides when there are none
{
type: 'toggle',
hook: 'clear-completed'
},
// Inserts completed count
{
type: 'text',
hook: 'completed-count'
}
],
// Inserts HTML from model that also
// does pluralizing.
'model.itemsLeftHtml': {
type: 'innerHTML',
hook: 'todo-count'
},
// Add 'selected' to right
// element
'model.mode': {
type: 'switchClass',
name: 'selected',
cases: {
'all': '[data-hook=all-mode]',
'active': '[data-hook=active-mode]',
'completed': '[data-hook=completed-mode]',
}
},
// Bind 'checked' state of checkbox
'model.allCompleted': {
type: 'booleanAttribute',
name: 'checked',
hook: 'mark-all'
}
},
// cache
initialize: function () {
this.mainInput = this.queryByHook('todo-input');
this.renderCollection(app.me.todos.subset, TodoView, this.queryByHook('todo-container'));
},
// handles DOM event from main input
handleMainInput: function (e) {
var val = this.mainInput.value.trim();
if (e.which === ENTER_KEY && val) {
app.me.todos.add({title: val});
this.mainInput.value = '';
}
},
// Here we set all to state provided.
handleMarkAllClick: function () {
var targetState = !app.me.allCompleted;
app.me.todos.each(function (todo) {
todo.completed = targetState;
});
},
// Handler for clear click
handleClearClick: function () {
app.me.todos.clearCompleted();
}
});
'use strict';
var View = require('ampersand-view');
var todoTemplate = require('../templates/todo.jade');
var ENTER_KEY = 13;
var ESC_KEY = 27;
module.exports = View.extend({
// note that Ampersand is extrememly flexible with templating.
// This template property can be:
// 1. A plain HTML string
// 2. A function that returns an HTML string
// 3. A function that returns a DOM element
//
// Here we're using a jade template. A browserify transform
// called 'jadeify' lets us require a ".jade" file as if
// it were a module and it will compile it to a function
// for us. This function returns HTML as per #2 above.
template: todoTemplate,
// Events work like backbone they're all delegated to
// root element.
events: {
'change [data-hook=checkbox]': 'handleCheckboxChange',
'click [data-hook=action-delete]': 'handleDeleteClick',
'dblclick [data-hook=title]': 'handleDoubleClick',
'keyup [data-hook=input]': 'handleKeypress',
'blur [data-hook=input]': 'handleBlur'
},
// Declarative data bindings
bindings: {
'model.title': [
{
type: 'text',
hook: 'title'
},
{
type: 'value',
hook: 'input'
}
],
'model.editing': [
{
type: 'toggle',
yes: '[data-hook=input]',
no: '[data-hook=view]'
},
{
type: 'booleanClass'
}
],
'model.completed': [
{
type: 'booleanAttribute',
name: 'checked',
hook: 'checkbox'
},
{
type: 'booleanClass'
}
]
},
render: function () {
// Render this with template provided.
// Note that unlike backbone this includes the root element.
this.renderWithTemplate();
// cache reference to `input` for speed/convenience
this.input = this.queryByHook('input');
},
handleCheckboxChange: function (e) {
this.model.completed = e.target.checked;
},
handleDeleteClick: function () {
this.model.destroy();
},
// Just put us in edit mode and focus
handleDoubleClick: function () {
this.model.editing = true;
this.input.focus();
},
handleKeypress: function (e) {
if (e.which === ENTER_KEY) {
this.input.blur();
} else if (e.which === ESC_KEY) {
this.input.value = this.model.title;
this.input.blur();
}
},
// Since we always blur even in the other
// scenarios we use this as a 'save' point.
handleBlur: function () {
var val = this.input.value.trim();
if (val) {
this.model.set({
title: val,
editing: false
});
} else {
this.model.destroy();
}
}
});
{
"name": "ampersand-todomvc",
"description": "TodoMVC Implementation in Ampersand.js",
"version": "1.0.0",
"author": "Henrik Joreteg <henrik@andyet.net>",
"private": true,
"dependencies": {
"ampersand-collection": "^1.3.16",
"ampersand-router": "^1.0.5",
"ampersand-state": "^4.3.12",
"ampersand-subcollection": "^1.4.3",
"ampersand-view": "^7.1.4",
"debounce": "^1.0.0"
},
"devDependencies": {
"browserify": "^6.0.3",
"jade": "^1.7.0",
"jadeify": "^2.7.0",
"watchify": "^2.0.0"
},
"scripts": {
"build": "browserify js/app.js -t jadeify -o todomvc.bundle.js",
"start": "watchify js/app.js -t jadeify -o todomvc.bundle.js"
}
}
# Ampersand.js TodoMVC Example
> A highly modular, loosely coupled, non-frameworky framework for building advanced JavaScript apps.
> _[Ampersand.js - ampersandjs.com](http://ampersandjs.com)_
## Learning Ampersand.js
The [Ampersand.js website](http://ampersandjs.com) is a great resource for getting started.
Here are some links you may find helpful:
* [Guides](http://ampersandjs.com/learn)
* [API Reference](http://ampersandjs.com/docs)
* [Curated Front-end Modules](http://tools.ampersandjs.com)
Articles and guides from the community:
* [Introducing Ampersand Blogpost](http://blog.andyet.com/2014/06/25/introducing-ampersand-js/)
Get help from other Ampersand.js users:
* #&yet IRC Channel on Freenode ([logs here](https://botbot.me/freenode/andyet/))
* [@ampersandjs](http://twitter.com/ampersandjs)
* [&yet – The team behind Ampersand.js](http://andyet.com)
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
## Implementation
The app spec says to use bower for dependency management unless it goes against the best practices of the framework. Ampersand.js is very specifically uses npm for dependency management so that's what we're using here.
## Credit
This TodoMVC application was created by [@HenrikJoreteg](http://twitter.com/henrikjoreteg), [@LukeKarrys](http://twitter.com/lukekarrys), and [@philip_roberts](https://twitter.com/philip_roberts).
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea url('bg.png');
color: #4d4d4d;
width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
button,
input[type="checkbox"] {
outline: none;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todoapp input::-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
/* Mobile Safari */
border: none;
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all:checked:before {
color: #737373;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
/* Mobile Safari */
border: none;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
content: '✔';
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
#todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
#todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
transition: all 0.2s;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-ms-transform: scale(1.3);
transform: scale(1.3);
}
#todo-list li .destroy:after {
content: '✖';
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: 1;
text-align: center;
}
#footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
float: left;
text-align: left;
}
#filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
#filters li {
display: inline;
}
#filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
#filters li a.selected {
font-weight: bold;
}
#clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
#info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
#info a {
color: inherit;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
height: 40px;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
.hidden {
display: none;
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
-webkit-transition-property: left;
transition-property: left;
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
.learn-bar > .learn {
left: 8px;
}
.learn-bar #todoapp {
width: 550px;
margin: 130px auto 40px auto;
}
}
(function () {
'use strict';
// Underscore's Template Module
// Courtesy of underscorejs.org
var _ = (function (_) {
_.defaults = function (object) {
if (!object) {
return object;
}
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
var iterable = arguments[argsIndex];
if (iterable) {
for (var key in iterable) {
if (object[key] == null) {
object[key] = iterable[key];
}
}
}
}
return object;
}
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
var render;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
return template;
};
return _;
})({});
if (location.hostname === 'todomvc.com') {
window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
}
function redirect() {
if (location.hostname === 'tastejs.github.io') {
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
}
}
function findRoot() {
var base;
[/labs/, /\w*-examples/].forEach(function (href) {
var match = location.href.match(href);
if (!base && match) {
base = location.href.indexOf(match);
}
});
return location.href.substr(0, base);
}
function getFile(file, callback) {
if (!location.host) {
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
}
var xhr = new XMLHttpRequest();
xhr.open('GET', findRoot() + file, true);
xhr.send();
xhr.onload = function () {
if (xhr.status === 200 && callback) {
callback(xhr.responseText);
}
};
}
function Learn(learnJSON, config) {
if (!(this instanceof Learn)) {
return new Learn(learnJSON, config);
}
var template, framework;
if (typeof learnJSON !== 'object') {
try {
learnJSON = JSON.parse(learnJSON);
} catch (e) {
return;
}
}
if (config) {
template = config.template;
framework = config.framework;
}
if (!template && learnJSON.templates) {
template = learnJSON.templates.todomvc;
}
if (!framework && document.querySelector('[data-framework]')) {
framework = document.querySelector('[data-framework]').getAttribute('data-framework');
}
if (template && learnJSON[framework]) {
this.frameworkJSON = learnJSON[framework];
this.template = template;
this.append();
}
}
Learn.prototype.append = function () {
var aside = document.createElement('aside');
aside.innerHTML = _.template(this.template, this.frameworkJSON);
aside.className = 'learn';
// Localize demo links
var demoLinks = aside.querySelectorAll('.demo-link');
Array.prototype.forEach.call(demoLinks, function (demoLink) {
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
});
document.body.className = (document.body.className + ' learn-bar').trim();
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
};
redirect();
getFile('learn.json', Learn);
})();
This diff is collapsed.
......@@ -123,6 +123,9 @@
<li class="routing">
<a href="architecture-examples/mithril/" data-source="http://lhorie.github.io/mithril/" data-content="Mithril is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain.">Mithril</a>
</li>
<li class="routing">
<a href="architecture-examples/ampersand/" data-source="http://ampersandjs.com" data-content="A highly modular, loosely coupled, non-frameworky framework for building advanced JavaScript apps.">Ampersand</a>
</li>
</ul>
</div>
<div class="js-app-list" data-app-list="ctojs">
......
......@@ -290,6 +290,46 @@
}]
}]
},
"ampersand": {
"name": "Ampersand.js",
"description": "A highly modular, loosely coupled, non-frameworky framework for building advanced JavaScript apps.",
"homepage": "http://ampersandjs.com",
"examples": [{
"name": "Architecture Example",
"url": "architecture-examples/ampersand"
}],
"link_groups": [{
"heading": "Official Resources",
"links": [{
"name": "Project Site",
"url": "http://ampersandjs.com"
}, {
"name": "Guides",
"url": "http://ampersandjs.com/learn"
}, {
"name": "API Reference",
"url": "http://ampersandjs.com/docs"
}, {
"name": "Curated Front-end Modules",
"url": "http://tools.ampersandjs.com"
}, {
"name": "#&yet IRC Channel on Freenode",
"url": "https://botbot.me/freenode/andyet/"
}]
}, {
"heading": "Related Materials",
"links": [{
"name": "Human JavaScript (free online book)",
"url": "http://learn.humanjavascript.com"
}, {
"name": "Introducing Ampersand Blogpost",
"url": "http://blog.andyet.com/2014/06/25/introducing-ampersand-js/"
}, {
"name": "&yet – The team behind Ampersand.js",
"url": "http://andyet.com"
}]
}]
},
"batman": {
"name": "Batman.js",
"description": "A client-side framework for Rails developers. Batman.js is a framework for building rich web applications with CoffeeScript.",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment