Commit 07898f7f authored by Sindre Sorhus's avatar Sindre Sorhus

Merge pull request #319 from paulmillr/topics/chaplin

Add Chaplin application.
parents faa859eb 00ca0421
# editorconfig.org
root = true
[*.coffee]
indent_style = space
indent_size = 2
# NPM packages folder.
node_modules/
# Brunch folder for temporary files.
tmp/
# Brunch with Chaplin TODOMVC
Brunch with Chaplin is a skeleton (boilerplate) for [Brunch](http://brunch.io)
based on [Chaplin](https://github.com/chaplinjs/chaplin) framework.
The application is based on the skeleton.
## Getting started
* Install [Brunch](http://brunch.io) if you hadn’t already (`npm install -g brunch`).
* Execute `npm install` in the root directory once.
* Execute `brunch build` in the root directory to build app every time. That’s all.
* Execute `brunch watch` if you want to continiously rebuild the app
on every change. To run the app then, you will need to open `public/index.html` in your browser (assuming the root is `/todomvc/` root or so).
## Author
The stuff was made by [@paulmillr](http://paulmillr.com).
Chaplin = require 'chaplin'
mediator = require 'mediator'
routes = require 'routes'
HeaderController = require 'controllers/header-controller'
FooterController = require 'controllers/footer-controller'
TodosController = require 'controllers/todos-controller'
Todos = require 'models/todos'
Layout = require 'views/layout'
# The application object
module.exports = class Application extends Chaplin.Application
# Set your application name here so the document title is set to
# “Controller title – Site title” (see Layout#adjustTitle)
title: 'Chaplin • TodoMVC'
initialize: ->
super
# Initialize core components
@initDispatcher controllerSuffix: '-controller'
@initLayout()
@initMediator()
# Application-specific scaffold
@initControllers()
# Register all routes and start routing
@initRouter routes, pushState: no
# You might pass Router/History options as the second parameter.
# Chaplin enables pushState per default and Backbone uses / as
# the root per default. You might change that in the options
# if necessary:
# @initRouter routes, pushState: false, root: '/subdir/'
# Freeze the application instance to prevent further changes
Object.freeze? this
# Override standard layout initializer
# ------------------------------------
initLayout: ->
# Use an application-specific Layout class. Currently this adds
# no features to the standard Chaplin Layout, it’s an empty placeholder.
@layout = new Layout {@title}
# Instantiate common controllers
# ------------------------------
initControllers: ->
# These controllers are active during the whole application runtime.
# You don’t need to instantiate all controllers here, only special
# controllers which do not to respond to routes. They may govern models
# and views which are needed the whole time, for example header, footer
# or navigation views.
# e.g. new NavigationController()
new HeaderController()
new FooterController()
new TodosController()
# Create additional mediator properties
# -------------------------------------
initMediator: ->
# Create a user property
mediator.user = null
# Add additional application-specific properties and methods
mediator.todos = new Todos()
mediator.todos.fetch()
# Seal the mediator
mediator.seal()
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Chaplin • TodoMVC</title>
<link rel="stylesheet" href="../../../../assets/base.css">
<!--[if IE]>
<script src="../../../assets/ie.js"></script>
<![endif]-->
<link rel="stylesheet" href="stylesheets/app.css">
<!-- Usually all these files are concatenated automatically
by brunch and you need just to import `vendor.js` -->
<script src="../../../../assets/base.js"></script>
<script src="../../../../assets/jquery.min.js"></script>
<script src="../../../../assets/lodash.min.js"></script>
<script src="../../../../assets/handlebars.min.js"></script>
<script src="javascripts/vendor.js"></script>
<script src="javascripts/app.js"></script>
<script>require('initialize');</script>
</head>
<body>
<section id="todoapp">
<header id="header"></header>
<section id="main"></section>
<footer id="footer"></footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://paulmillr.com">Paul Miller</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</body>
</html>
Chaplin = require 'chaplin'
module.exports = class Controller extends Chaplin.Controller
Controller = require 'controllers/base/controller'
FooterView = require 'views/footer-view'
mediator = require 'mediator'
module.exports = class FooterController extends Controller
initialize: ->
super
@view = new FooterView collection: mediator.todos
Controller = require 'controllers/base/controller'
HeaderView = require 'views/header-view'
mediator = require 'mediator'
module.exports = class HeaderController extends Controller
initialize: ->
super
@view = new HeaderView collection: mediator.todos
Controller = require 'controllers/base/controller'
module.exports = class IndexController extends Controller
title: 'Todo list'
list: (options) ->
@publishEvent 'todos:filter', options.filterer?.trim() ? 'all'
Controller = require 'controllers/base/controller'
TodosView = require 'views/todos-view'
mediator = require 'mediator'
module.exports = class TodosController extends Controller
initialize: ->
super
@view = new TodosView collection: mediator.todos
Application = require 'application'
# Initialize the application on DOM ready event.
$ ->
app = new Application()
app.initialize()
Chaplin = require 'chaplin'
utils = require 'lib/utils'
# Application-specific feature detection
# --------------------------------------
# Delegate to Chaplin’s support module
support = utils.beget Chaplin.support
# _(support).extend
# someMethod: ->
module.exports = support
Chaplin = require 'chaplin'
# Application-specific utilities
# ------------------------------
# Delegate to Chaplin’s utils module
utils = Chaplin.utils.beget Chaplin.utils
# _(utils).extend
# someMethod: ->
module.exports = utils
mediator = require 'mediator'
utils = require 'chaplin/lib/utils'
# Application-specific view helpers
# ---------------------------------
# http://handlebarsjs.com/#helpers
# Conditional evaluation
# ----------------------
# Choose block by user login status
Handlebars.registerHelper 'if_logged_in', (options) ->
if mediator.user
options.fn(this)
else
options.inverse(this)
# Map helpers
# -----------
# Make 'with' behave a little more mustachey
Handlebars.registerHelper 'with', (context, options) ->
if not context or Handlebars.Utils.isEmpty context
options.inverse(this)
else
options.fn(context)
# Inverse for 'with'
Handlebars.registerHelper 'without', (context, options) ->
inverse = options.inverse
options.inverse = options.fn
options.fn = inverse
Handlebars.helpers.with.call(this, context, options)
# Evaluate block with context being current user
Handlebars.registerHelper 'with_user', (options) ->
context = mediator.user?.serialize() or {}
Handlebars.helpers.with.call(this, context, options)
module.exports = require('chaplin').mediator
Chaplin = require 'chaplin'
Model = require 'models/base/model'
module.exports = class Collection extends Chaplin.Collection
# Use the project base model per default, not Chaplin.Model
model: Model
# Mixin a synchronization state machine
# _(@prototype).extend Chaplin.SyncMachine
Chaplin = require 'chaplin'
module.exports = class Model extends Chaplin.Model
# Mixin a synchronization state machine
# _(@prototype).extend Chaplin.SyncMachine
Model = require 'models/base/model'
module.exports = class Todo extends Model
defaults:
title: ''
completed: no
initialize: ->
super
@set 'created', Date.now() if @isNew()
toggle: ->
@set completed: not @get('completed')
isVisible: ->
isCompleted = @get('completed')
Collection = require 'models/base/collection'
Todo = require 'models/todo'
module.exports = class Todos extends Collection
model: Todo
localStorage: new Store 'todos-chaplin'
allAreCompleted: ->
@getCompleted().length is @length
getCompleted: ->
@where completed: yes
getActive: ->
@where completed: no
comparator: (todo) ->
todo.get('created')
module.exports = (match) ->
match ':filterer', 'index#list'
match '', 'index#list'
Chaplin = require 'chaplin'
View = require 'views/base/view'
module.exports = class CollectionView extends Chaplin.CollectionView
# This class doesn’t inherit from the application-specific View class,
# so we need to borrow the method from the View prototype:
getTemplateFunction: View::getTemplateFunction
Chaplin = require 'chaplin'
require 'lib/view-helper' # Just load the view helpers, no return value
module.exports = class View extends Chaplin.View
# Precompiled templates function initializer.
getTemplateFunction: ->
@template
View = require 'views/base/view'
template = require 'views/templates/footer'
module.exports = class FooterView extends View
autoRender: yes
el: '#footer'
template: template
initialize: ->
super
@subscribeEvent 'todos:filter', @updateFilterer
@modelBind 'all', @renderCounter
@delegate 'click', '#clear-completed', @clearCompleted
render: =>
super
@renderCounter()
updateFilterer: (filterer) =>
filterer = '' if filterer is 'all'
@$('#filters a')
.removeClass('selected')
.filter("[href='#/#{filterer}']")
.addClass('selected')
renderCounter: =>
total = @collection.length
active = @collection.getActive().length
completed = @collection.getCompleted().length
@$('#todo-count > strong').html active
countDescription = (if active is 1 then 'item' else 'items')
@$('.todo-count-title').text countDescription
@$('#completed-count').html "(#{completed})"
@$('#clear-completed').toggle(completed > 0)
@$el.toggle(total > 0)
clearCompleted: ->
@publishEvent 'todos:clear'
View = require 'views/base/view'
template = require 'views/templates/header'
module.exports = class HeaderView extends View
autoRender: yes
el: '#header'
template: template
initialize: ->
super
@delegate 'keypress', '#new-todo', @createOnEnter
createOnEnter: (event) =>
ENTER_KEY = 13
title = $(event.currentTarget).val().trim()
return if event.keyCode isnt ENTER_KEY or not title
@collection.create {title}
@$('#new-todo').val ''
Chaplin = require 'chaplin'
# Layout is the top-level application ‘view’.
module.exports = class Layout extends Chaplin.Layout
initialize: ->
super
@subscribeEvent 'todos:filter', @changeFilterer
changeFilterer: (filterer = 'all') ->
$('#todoapp').attr 'class', "filter-#{filterer}"
<span id="todo-count">
<strong></strong>
<span class="todo-count-title">items</span>
left
</span>
<ul id="filters">
<li>
<a href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button id="clear-completed">
Clear completed
<span id="completed-count"></span>
</button>
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
<div class="view">
<input class="toggle" type="checkbox"{{#if completed}} checked{{/if}}>
<label>{{title}}</label>
<button class="destroy"></button>
</div>
<input class="edit" value="{{title}}">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul>
View = require 'views/base/view'
template = require 'views/templates/todo'
module.exports = class TodoView extends View
template: template
tagName: 'li'
initialize: ->
super
@modelBind 'change', @render
@delegate 'click', '.destroy', @destroy
@delegate 'dblclick', 'label', @edit
@delegate 'keypress', '.edit', @save
@delegate 'click', '.toggle', @toggle
@delegate 'blur', '.edit', @save
render: =>
super
# Reset classes, re-add the appropriate ones.
@$el.removeClass 'active completed'
className = if @model.get('completed') then 'completed' else 'active'
@$el.addClass className
destroy: =>
@model.destroy()
toggle: =>
@model.toggle().save()
edit: =>
@$el.addClass 'editing'
@$('.edit').focus()
save: (event) =>
ENTER_KEY = 13
title = $(event.currentTarget).val().trim()
return @model.destroy() unless title
return if event.type is 'keypress' and event.keyCode isnt ENTER_KEY
@model.save {title}
@$el.removeClass 'editing'
CollectionView = require 'views/base/collection-view'
template = require 'views/templates/todos'
TodoView = require 'views/todo-view'
module.exports = class TodosView extends CollectionView
el: '#main'
itemView: TodoView
listSelector: '#todo-list'
template: template
initialize: ->
super
@subscribeEvent 'todos:clear', @clear
@modelBind 'all', @renderCheckbox
@delegate 'click', '#toggle-all', @toggleCompleted
render: =>
super
@renderCheckbox()
renderCheckbox: =>
@$('#toggle-all').prop 'checked', @collection.allAreCompleted()
@$el.toggle(@collection.length isnt 0)
toggleCompleted: (event) =>
isChecked = event.currentTarget.checked
@collection.each (todo) -> todo.save completed: isChecked
clear: ->
@collection.getCompleted().forEach (model) ->
model.destroy()
exports.config =
# See http://brunch.readthedocs.org/en/latest/config.html for documentation.
files:
javascripts:
joinTo:
'javascripts/app.js': /^app/
'javascripts/vendor.js': /^vendor/
'test/javascripts/test.js': /^test[\\/](?!vendor)/
'test/javascripts/test-vendor.js': /^test[\\/](?=vendor)/
order:
# Files in `vendor` directories are compiled before other files
# even if they aren't specified in order.before.
before: [
'vendor/scripts/console-helper.js',
'vendor/scripts/jquery-1.8.2.js',
'vendor/scripts/underscore-1.4.2.js',
'vendor/scripts/backbone-0.9.2.js'
]
stylesheets:
joinTo:
'stylesheets/app.css': /^(app|vendor)/
'test/stylesheets/test.css': /^test/
order:
before: ['vendor/styles/normalize-2.0.1.css']
after: ['vendor/styles/helpers.css']
templates:
joinTo: 'javascripts/app.js'
{
"author": "Paul Miller (http://paulmillr.com/)",
"name": "brunch-with-chaplin-todomvc",
"description": "Brunch with Chaplin TODOMVC app",
"version": "0.0.1",
"engines": {
"node": "0.8 || 0.9"
},
"scripts": {
"start": "brunch watch --server",
"test": "brunch test"
},
"dependencies": {
"javascript-brunch": ">= 1.0 < 1.5",
"coffee-script-brunch": ">= 1.0 < 1.5",
"css-brunch": ">= 1.0 < 1.5",
"stylus-brunch": ">= 1.0 < 1.5",
"handlebars-brunch": ">= 1.0 < 1.5",
"uglify-js-brunch": ">= 1.0 < 1.5",
"clean-css-brunch": ">= 1.0 < 1.5"
},
"devDependencies": {
"chai": "~1.2.0",
"sinon": "~1.4.2",
"sinon-chai": "~2.1.2"
}
}
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Chaplin • TodoMVC</title>
<link rel="stylesheet" href="../../../../assets/base.css">
<!--[if IE]>
<script src="../../../assets/ie.js"></script>
<![endif]-->
<link rel="stylesheet" href="stylesheets/app.css">
<!-- Usually all these files are concatenated automatically
by brunch and you need just to import `vendor.js` -->
<script src="../../../../assets/base.js"></script>
<script src="../../../../assets/jquery.min.js"></script>
<script src="../../../../assets/lodash.min.js"></script>
<script src="../../../../assets/handlebars.min.js"></script>
<script src="javascripts/vendor.js"></script>
<script src="javascripts/app.js"></script>
<script>require('initialize');</script>
</head>
<body>
<section id="todoapp">
<header id="header"></header>
<section id="main"></section>
<footer id="footer"></footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://paulmillr.com">Paul Miller</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</body>
</html>
#todoapp.filter-active #todo-list .completed {
display: none; }
#todoapp.filter-completed #todo-list .active {
display: none; }
/**
* Backbone localStorage Adapter
* https://github.com/jeromegn/Backbone.localStorage
*/
(function() {
// A simple module to replace `Backbone.sync` with *localStorage*-based
// persistence. Models are given GUIDS, and saved into a JSON object. Simple
// as that.
// Hold reference to Underscore.js and Backbone.js in the closure in order
// to make things work even if they are removed from the global namespace
var _ = this._;
var Backbone = this.Backbone;
// Generate four random hex digits.
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
// Generate a pseudo-GUID by concatenating random hexadecimal.
function guid() {
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
};
// Our Store is represented by a single JS object in *localStorage*. Create it
// with a meaningful name, like the name you'd give a table.
// window.Store is deprectated, use Backbone.LocalStorage instead
Backbone.LocalStorage = window.Store = function(name) {
this.name = name;
var store = this.localStorage().getItem(this.name);
this.records = (store && store.split(",")) || [];
};
_.extend(Backbone.LocalStorage.prototype, {
// Save the current state of the **Store** to *localStorage*.
save: function() {
this.localStorage().setItem(this.name, this.records.join(","));
},
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own.
create: function(model) {
if (!model.id) {
model.id = guid();
model.set(model.idAttribute, model.id);
}
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
this.records.push(model.id.toString());
this.save();
return model.toJSON();
},
// Update a model by replacing its copy in `this.data`.
update: function(model) {
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
if (!_.include(this.records, model.id.toString())) this.records.push(model.id.toString()); this.save();
return model.toJSON();
},
// Retrieve a model from `this.data` by id.
find: function(model) {
return JSON.parse(this.localStorage().getItem(this.name+"-"+model.id));
},
// Return the array of all models currently in storage.
findAll: function() {
return _(this.records).chain()
.map(function(id){return JSON.parse(this.localStorage().getItem(this.name+"-"+id));}, this)
.compact()
.value();
},
// Delete a model from `this.data`, returning it.
destroy: function(model) {
this.localStorage().removeItem(this.name+"-"+model.id);
this.records = _.reject(this.records, function(record_id){return record_id == model.id.toString();});
this.save();
return model;
},
localStorage: function() {
return localStorage;
}
});
// localSync delegate to the model or collection's
// *localStorage* property, which should be an instance of `Store`.
// window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead
Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options, error) {
var store = model.localStorage || model.collection.localStorage;
// Backwards compatibility with Backbone <= 0.3.3
if (typeof options == 'function') {
options = {
success: options,
error: error
};
}
var resp;
switch (method) {
case "read": resp = model.id != undefined ? store.find(model) : store.findAll(); break;
case "create": resp = store.create(model); break;
case "update": resp = store.update(model); break;
case "delete": resp = store.destroy(model); break;
}
if (resp) {
options.success(resp);
} else {
options.error("Record not found");
}
};
Backbone.ajaxSync = Backbone.sync;
Backbone.getSyncMethod = function(model) {
if(model.localStorage || (model.collection && model.collection.localStorage))
{
return Backbone.localSync;
}
return Backbone.ajaxSync;
};
// Override 'Backbone.sync' to default to localSync,
// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
Backbone.sync = function(method, model, options, error) {
return Backbone.getSyncMethod(model).apply(this, [method, model, options, error]);
};
})();
// Make it safe to do console.log() always.
(function (con) {
var method;
var dummy = function() {};
var methods = ('assert,count,debug,dir,dirxml,error,exception,group,' +
'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' +
'time,timeEnd,trace,warn').split(',');
while (method = methods.pop()) {
con[method] = con[method] || dummy;
}
})(window.console = window.console || {});
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