Maximize code reuse, minimize DOM management, optimize happiness. All the features you need to build stunning HTML5 web and mobile applications with efficiency and ease. Lightweight, high performance, incredibly flexible, insanely powerful.
Base.js is a project designed to combine the very best feautres of backbone, angular, and ember with inspiration from many other great javascript MVC frameworks into one cohesive, highly performant, maximally flexible and extensible package.
Built with Backbone, jQuery, and Ractive
Sorry to those eager to use this project. I have since been unable to maintain this project and instead would personally recommend using React instead
HTML (DOM updates automatically on model changes)
<body base-app="myApp">
<h1>{{user.name}}</h1>
<div class="controls">
<button on-click="set: 'mode', 'grid' " class="grid {{ mode == 'grid' ? 'active' : 'inactive' }}"></button>
<button on-click="set: 'mode', 'grid' " class="single {{ mode == 'single' ? 'active' : 'inactive' }}"></button>
</div>
<base-view type="grid">
{{#picts}}
<img outlet="pict" src="{{url}}" on-click="set: 'activePict', 'pict' ">
{{/picts}}
</base-view>
<base-view type="lightbox" visible="{{!!activePict}}" on-click="hide: true ">
<img src="{{activePict.url}}" outlet="pict">
</base-view>
</body>
JS (in Coffeescript):
class App extends Base.App
plugins:
lazyLoadImages: true
animateImagesOnLoad:
type: 'fade'
class Grid extends Base.View
plugins:
masonry: true
defaults:
mode: 'grid'
class Lightbox extends Base.View
defaults:
showLightbox: false
onChangeShowLightbox: -> @doSomething()
CSS (in Stylus):
[ data-view = pict ]
[ data-mode = single ] &
position relative
All base templates update automatically as your views and models change, no more jquery is needed! No, seriously, stop using .append(), .clone(), .addClass(), .removeClass(), etc. Just like angular, meteor, derby, and the many other fantastic live template libraries, once you use it you will not look back. No more spaghetti code, no more exponentially rising DOM manipulation complexity, no more rerendering entire views just to update one list.
Base live templates are built on top of Ractive, check out the examples for amazing examples of using live templates for all kinds of applications and purposes. And see below for how Base extends Ractive for additional power and flexibility including app level binding, route binding, singleton binding, and more.
{{someGlobalProperty}}
<!-- 'EACH' -->
{{#someArray}}
{{title}}
<!-- The dot indicates relative to the array-->
{{.someArrayProperty}}
<!-- This renders the array item itself, typically used if the value is a string in an array of strings -->
{{.}}
{{/someArray}}
<!-- 'IF' -->
{{#someNonArray}}
<h1> {{someNonArray}} is truthy! </h1>
{{/someNonArray}}
<!-- 'UNLESS' -->
{{^someNonArray}}
<h1> {{someNonArray}} is falsey! </h1>
{{/someNonArray}}
<!-- Expressions -->
{{ name ? name : 'You have no name!' }}
<div class="button {{ active ? 'active' : 'inactive' }}"></div>
<div name="{{name}}"></div>
<!-- Block expressions -->
{{# foo == 'bar' && bar == 'foo' }}
Foo is bar and bar is foo!
{{/}}
{{^ typeof bar is 'string'}}
Bar is not a string!
{{/}}
<!-- Current view methods -->
{{ $view.getProduct( product ) }}
<!-- Filters -->
{{ $filter.capitalize( name ) }}
{{ $filter.orderBy( someArray, 'name' ) }}
<!-- Parent view -->
{{ $parent.foo.bar }}
{{ $parent.$parent.foo.bar }}
<!-- App -->
{{ $app.foo == 'bar' ? foo : bar }}
<!-- Singletons -->
{{# $user.type == 'brand' }}
You're a brand, here are some brand options!
{{/}}
{{# $user.type == 'publisher' }}
Hello publisher!
{{/}}
<!-- Routes -->
{{# $router.path[0] == 'share' && $router.params.foo == 'bar' }}
We're on the shar epage and foo is bar!
{{/}}
{{# $router.route == 'share/photo' }}
You're sharing a photo!
{{/}}
<!-- Mix and match -->
{{# shareType == 'photo' && $user.type == 'brand' }}
Something special just for brands shareing photos
{{/}}
Base extends Ractive to allow multiple arguments with object getters
<button on-click="someButtonWasClicked"></button>
<button on-hover="set: 'foo', bar"></button>
<button on-touchend="activate: foo"></button>
class View extends Base.View
someButtonWasClicked: (e) ->
$clickedButton = $ e.currentTarget
activate: (name) ->
name # => 'foo'
<button outlet="foo"></button>
class View extends Base.View
construcotr: ->
super
# First way to bind
@on 'click:foo', ->
bind:
# Second way to bind
'click:foo', ->
# Simplest way to bind
onClickFoo: (e) ->
Extend the functionality of any type of module. Configurable at the Base (global) level, app level, and the per module level (also by module type). The ultimate goal is to distill applications development to basic configuration, through the use of building and applying reusable components. This is heavily inspired by grunt.
The ultimate goal here is to maximize code reusability across applications, provide basic utilities that help this process (modeled after the grunt apis). Ultimately this library will be broken down into the Base core and a suite of plugins to assemble the features you want (e.g. ractive templating, state handling, etc). Eventually nearly every feature herein should be moved to a plugin so applications can be assembled with as much or as little as they want/choose, and can swap out any part or piece at any time (e.g. use another template library, use a different state handler, etc)
Base.plugins.view.defaults.lazyLoad = true
App.plugins.defaults.state = true
class View extends Base.View
plugins:
ractive: true
lazyLoadImages: true
fadeInImages:
className: 'fade'
selector: '.lazy-loaded'
# Plugin code runs on initialize, is called in the context of the module
# it is being applied to, and can retutn methods to apply to the module
Base.view.plugin 'ractive', (view, config) ->
@ractive = new Ractive el: @el, template: @template, data: @toJSON()
@ractive.bind Ractive.adaptors.backboneAssociatedModel @state
@on 'render', => @ractive.render()
# Applies to all classes
Base.plugin 'state', (module, config) ->
@state = new Base.State
@state.on 'all', (eventName, args…) =>
@trigger.apply @, ["state:#{eventName}"].concat args
# You can return methods to apply to the module
getState: (name) -> @state.get name
setState: (name, value) -> @state.set name, value
App.view.plugin 'fadeInImages', (view, config) ->
@on 'render', ->
# You can use config.className or apply config as a function to set defaults
config = config className: 'hide', selector: 'img'
$images = @$ config.selector
$images.addClass config.className
$images.on 'load', (e) => $(e.target).removeClass config.className
# Plugins can also apply to specific module types
App.plugin ['view', 'collection', 'model'], 'state', ->
# Plugins can also have dependences
App.view.plugin 'ractive', ['view:state'], ->
Web components are custom HTML tags with special behaviors for making application markup dead simple. These can range from basic simplications (e.g. '' as a simpler form of typing ) to highly dynamic components (e.g. that automatically creates and destroys subviews as a paired collection changes)
<base-view type="foo" foo="bar"></base-view>
<base-collection subject="picts" view="pict"></base-collection>
<base-icon name="foo"></base-icon>
<base-switch name="bar"></base-switch>
# add child views with simple markup
Base.component 'view', ($el, attributes) ->
view = new app.views[attributes.type] _.extend attributes, parent: @
@insertView view
# For a dynamic list of views that updates when a collection changes
Base.component 'collection', ($el, attributes) ->
View = app.views[attributes.view]
collection = @get attributes.subject
@listenTo collection,
add: (model) => @insertView new View parent: @, model: model
remove: (model) => @destroyView model: model
reset: => @destroyViews()
# For simpler markup of sprited icons generated by grunt-glue
Base.component 'icon', ($el, attributes) ->
$el.append "<i class='icon sprite-#{attributes.name}'></i>"
# For IOS style switches
Base.component 'switch', ($el, attributes) ->
$input = $ "<input type='checkbox' type='switch' name='#{attributes.name}>'"
$input.on 'click', => $el.prop 'checked', $input.prop 'checked'
class View extends Base.View
constructor: ->
render: ->
super
@insertView new SomeView
@insertView '.some-selector', new SomeView
@subView new SomeView
<!--
This is equivalent to parnetView.subView new MyViewName foo: 'bar'
-->
<base-view type="MyViewName" foo="bar"></base-view>
class MyView extends Base.View
render: ->
super
# broadcasts an event to all children
@broadcast 'rendered'
# emits an event to all children
@emit 'rendered'
# broadcasts and emits an event to all parents and children
@trigger 'rendered'
# Runs when any child emits 'rendered'
onChildRendered: ->
# Runs when any child named 'myOtherView' emits 'rendered'
onChildMyOtherViewRendered: ->
# Runs when any immediate child emits 'rendered'
onFirstChildRendered: ->
# Runs when any view (parent or child) nadmed 'myOtherView' rendered
onMyOtherViewRendered: ->
onChildChangeActive: ->
class MyOtherView extends Base.View
render: ->
super
@emit 'rendered'
# These are all valid ways of binding to the same events described above
@on 'child:rendered', ->
@on 'firstChild:rendered', ->
@on 'child:myThirdView:rendered', ->
@on 'myThirdView:rendered', ->
@on 'child:change:active', ->
class MyThirdView extends Base.View
rener: ->
@emit 'rendered'
# Runs when any parent broadcasts 'rendered'
onParentRendered: ->
# Runs when a parent named 'MyView' broadcasts 'rendered'
onParentMyViewRendered: ->
# Runs only when this views first (closest) parent broadcasts 'rendered'
onFirstParentRende: ->red
# All emitted and broadcasted events inject a first
# argument, a Base.Event (similar to a DOM event object)
# that gives listeners some extra information and actions
class View extends Base.View
onChildChangeActive: (e) ->
if e.target.is 'listItem'
# Stop this event from further propagating (to parents
# if the event was emitted, to children if the event was
# broadcasted)
e.stopPropagation()
# Sets e.defaultPrevented to true
e.preventDefault()
# in this case the currentTarget is this view
if e.currentTarget is @
true
view.children # => Base.List (evented array) of children
view.parent # => view's immediate parent
view.findView 'name' # => first view named 'name'
view.findViews 'name' # => array of subviews named 'name'
view.childView 'name' # => first immediate child named 'name'
view.childViews 'name' # => array of immediate children with name 'name'
view.parentView 'name' # => first parent with name 'name'
view.parentViews 'name' # => array of parents with name 'name'
# All view accesors also take objects
view.findViews model: model
view.parentView foo: 'bar', bar: 'foo'
# All view accessors can also take iterators (functions)
view.childView (view) -> view.isActive()
view.parentViews (view) -> view.
# Or you can always loop through children yourself
# view.children inherits from Base.List, so it supports
# all native array methods as well as all underscore
# array and collection methods
view.children.map (child) -> child.toJSON()
view.children.reduce (child, lastVal) -> lastValue += 1 if child.isActive()
view.children.isEmpty()
view.children.max (child) -> child.get 'height'
view.children.sortBy (child) -> child.isActive()
view.children.last()
view.children.on 'add', (childView) -> # a new child view as added
view.children.on 'remove', (childView) -> # a child view was removed
view.childre.non 'reset', -> # children were reset
class PhotoModel extensd Base.Model
constructor: ->
super
# Relations can be added dynamically
@addRelation 'activeProduct', ProductModel
# Relations can be configured
relations:
productsTagged: ProductsCollection
class Model extends Base.Model
constructor: ->
super
@set 'photos', [ url: 'hi.png' ]
@get 'photos' # => Photo list with one photo model in it
@get 'photos[0]' # => a Photo model
@get 'photos[0].url' # => 'hola.png'
@set 'photos[0].url, 'foo.com/bar.png
@get('photos').add url: 'hello.png'
@get('photos').reset()
@on 'add:photos', -> # a photo model was added
@on 'reset:photos', -> # the photos collection was reset
@on 'remove:photos', -> # a photo was removed
@on 'change:photos[0]', -> # this first photo model changed
@on 'change:photos[*]', -> # any photo model changed
@on 'change:photos[0].url', ->
@on 'change:photos[*].url', ->
# Infinite nestings are supported
@get 'photos[0].productsTagged[0].id'
@set 'photos[0].productsTagged[0].id', newId
@on 'change:photos[0].productsTagged[0].id', ->
# Syntax sugar for listening for above events
onChangePhotosUrl: ->
onAddPhotos: ->
onResetPhotos: ->
onRemovePhotos: ->
# Defining relations
relations:
photos: PhotoList
# Views also support relations
class View extends Base.View
relations:
photo: PhotoModel
onChangePhotoUrl: ->
onChangePhoto: ->
Nearly all Base classes support state models (view, router, model, collection, app, etc). This lets you attach properties to models, collections, routers, etc without clashing with data you want synced with your backend (or other persistence layer such as localStorage)
model.state # => Base.State instance that inherits from Base.Model
model.state.get 'active'
# view getters and setters forward to the view state model
view.get 'active' # equivalent to view.state.get 'active'
view.set 'active', true # equivalent to view.state.set 'active', true
view.toJSON() # equivalent to view.state.toJSON()
view.toggle 'active' # equivalent to view.state.toggle 'active'
class Collection extends Base.Collection
# configure state defaults
# this is valid for all stated classes (e.g. router, model, collection, etc)
stateDefaults:
active: false
class View extends Base.View
# delegates to state.defaults
defaults:
# delegates to state.relations
relations:
foo: Foo
All foolowing methods work for all stated classes (routers, models, views, collections, etc)
model.setState 'active', true # equivalent to model.state.set 'active', true
model.getState 'active' # equivalent to model.state.get 'active'
model.toggleState 'active' # equivalent to model.state.toggle 'active'
# other model methods supported
model.changedState 'active'
model.cloneState()
model.unsetState 'active'
model.clearSate()
class Model extends Base.Model
constructor: ->
super
# all state events bubble to their parent prefixed by 'state:'
@on 'state:change:active', ->
onStateChangeActive: ->
<!-- properties in templates are view state properties -->
{{hello}}
<!-- bind to models that are nested in state (using relations) -->
{{model.property}}
<!-- bind to model state -->
{{model.$state.active}}
Any event on any evented object (model, view, collection, etc) can be subscribed to directly by camelizing the event name.
class View extends Base.View
onChange: (stateModel) ->
# triggers on 'change:active'
onChangeActive: (stateModel) ->
# triggers on 'child:change:active'
onChildChangeActive: (e) ->
# triggers on 'firstParent:render
onFirstParentRender: (e) ->
# Triggers when @$el was clicked
onClick: (e) ->
# Triggers when a dom element where outlet="myButton" fired a 'mouseenter' event
onMouseenterMyButton: (e) ->
class Collection extends Base.Collection
# triggers on 'add'
onAdd: (model) ->
# triggers on 'remove'
onRemove: (model) ->
NOTE: this feature is a work in progress, it is not yet ready for usage in development or production
Anything can be stored as a module at the app or base level (though the app level is most recommended).
Base.app 'Pict', ->
class Pict extends Base.App
constructor: ->
super
Base.apps.pict is Pict # => true
Base.app('pict') is Pict # => true
app.view 'Photo', ->
class Photo extends Base.View
constructor: ->
super
app.view('Photo') is Photo # => true
app.views.Photo is Photo # => true
app.model 'MyData' ->
class MyData extends Base.Model
constructor: ->
super
app.service 'http', ->
(
get: (url, callback) ->
$.get url, callback
)
Module functions are executed as soon as all dependency requirements are met. At this point the module is set to the value of the function called and any modules whose last remaining dependency is the result of the function just called while also get called.
# Simplest method. A 'PhotoView' arg looks for app.views.Photo,
# 'httpService' arg looks for app.services.http, etc
app.view 'Foo' (PhotoView, httpService, MyDataModel) ->
class Foo extends Base.View
get: (url, callback) ->
httpService.get url, callback
# To avoid minification issues, you can instead separately define
# an array of dependencies
app.view 'Foo', ['PhotoView', 'httpService'], (Photo, http) ->
class Foo extends Base.View
constructor: ->
super
Base.define 'foobar', (require) ->
# Base can figure out your dependencies. It will first
# look in the 'app' context for your module, followed by the
# global (Base) context
foo = require 'foo'
bar = app.require 'bar'
baz = Base.require 'baz'
app.define 'FooView', ->
class Foo extends
# Inherits from and supports full Base.View API (below)
class App extends Base.App
constructor: ->
super
@get('picts').fetch()
defaults:
mode: 'grid'
relations:
picts: PictsCollection
class View extends Base.View
# View state defaults
defaults:
visible: false
# Model relations
relations:
pict: PictModel
constructor: ->
super
# DOM Events can be bound via method syntax sugar
onClick: ->
onKeypress: ->
onWindowResize: ->
onDocumentKeypress: ->
# Or using dom element outlet names
onClickMainImage: ->
# When parent or child emits or broadcasts an event
onParentFoo: ->
onChildFoo: ->
# When a parent or child with name 'grid' emit or change events
onParentGridChangeActive: ->
onChildGridChangeVisible: ->
# Listen to change event in property visible
onChangeVisible: ->
# Listen to a state property of related model pict
onChangePictStateActive: ->
# Called after render
afterRender: ->
# Actions to run before a view is destroyed
cleanup: ->
# Send a response to a child view requesting some information
onRequestSomeQuestion: ->
view.subView new View # add a nested view
view.insertView '.foo', view # add a nested view at selector
view.insertView view # add a nested view at @$el
view.findView 'photoGrid' # find a nested view named 'photoGrid'
view.findView model: model # find nested view that matches property keyvals
view.findView (view) -> # find view via a function
view.childView 'foo' # only search immediate children
view.is 'foo' # view matches a string, object, or function
view.parent # the views parent view
view.children # list of subviews - inherits from Base.List
view.parentView 'foo'
view.childViews 'foo'
view.findViews 'foo'
view.parentViews 'foo'
# emit an event to all parents, seen by parents as 'child:foo'
view.emit 'foo', arg1, arg2
# broadcast an event to all children, seen as 'parent:foo'
view.broadcast 'foo', arg1, arg2
# callback when a parent broadcasts event 'foo'
view.on 'parent:foo', ->
# callback when a parent named 'parentViewName' broadcasts event 'foo'
view.on 'parent:parentViewName:foo', (e, args…) ->
# callback when a child emits an event 'foo'
view.on 'child:foo', (e, args…) ->
# callback when a child emits an event 'foo'
view.on 'child:childViewName:foo', (e, args…) ->
# Destroy a view, unbind all listeners, and cleanup
view.destroy()
# Request a response from a parent, bubbles up to all parents
# until one parent has an on 'request:someQuestion' handler or
# an onRequestSomeQuestion method. The request is sent to the
# first parent with a handler and then the request stops propagating
view.request 'someQuestion', (response) ->
# Models are inherited from backbone models
class Model extends Base.Model
stateDefaults:
active: false
defaults:
price: 0
# models support computed properties that auto update on change
# of other properties
compute:
# 'priceString' will update on every change of 'price' and/or 'currencyCode'
priceString: (price, currencyCode) ->
getCurrencyString(currencyCode) + price
# Confogire nested model associations
relations:
pict: PictModel
# State Syntax Sugar
model.state # => sate model (inherited from Base.State)
model.setState 'foo', bar # equivalent of model.state.set 'foo', bar
model.getState 'foo' # => 'bar'
model.toggle 'foo' # same as model.set 'foo', !model.get 'foo'
model.toggleState 'active' # equivalent of model.state.toggle 'active'
model.addRelation 'pict', PictModel # Add a nested model
model.set 'pict', foo: 'bar' # Creates a new pict model
model.get 'pict' # => pictModel object
model.get 'pict.foo' # => 'bar'
model.set 'pict.foo', 'baz'
# Nested Events
model.on 'change:pict.foo', -> # valid as expected
model.on 'change:pict.products[0].foo', -> # also valid
# State Events
model.on 'state:change:foo.bar', -> # same as model.state.on 'change:foo.bar'
<!-- Update DOM on model state changes -->
{{#model.$state.active }}
<h1>I am active!</h1>
{{/}}
Singletons inherit from Base.Model and are accessible via the app object and anywhere via templates
JS (in Coffeescript)
app.singleton ->
class User extends Base.Singleton
defaults:
name: 'You have no name!'
app.mySingleton is MySingleton # => true
HTML
<!-- All singletons are accessible in templates prefixed by $ -->
<h1>{{$user.name}}</h1>
# Collections are inherited from backbone collections
class Collection extends Base.Collection
stateDefaults:
synced: false
# State Syntax Sugar
collection.state # => sate model (inherited from Base.State)
collection.setState 'synced', true # same as collection.state.set 'foo', bar
collection.getState 'synced' # => true
collection.toggleState 'synced' # same as collection.state.toggle 'active'
collection.on 'state:change:synced', -> # same as model.state.on 'change:foo'
<!-- Update DOM on collection state changes -->
{{#collection.$state.synced }}
<h1>I've been synced!</h1>
{{/}}
An evented array, similar to a backbone collection, but can store any type of data. Used internally to store view children (view.children) and listen to events and changes
class List extends Base.List
# Any class (constructor) can be a model that new additions
# passed to the list are constructed by. That or set no model
# And
model: Base.View
stateDefaults:
active: false
# Like any class, a list can support custom methods
getActiveChild: (e) ->
@find (child) -> child.active
# And you can override methods as expected
find: (e) ->
log 'someone is looking for something!'
super
list = new Base.List
list.on 'add', -> log 'added!'
list.push 'hello!' # triggers the 'add' event
# Lists are just typical arrays, you have access to all native
# array methods ('forEach', 'map', 'indexOf', etc) and all
# underscore array and collection methods as well ('find', 'contains', etc)
list[0] # => 'hello'
list.find (item) -> _.isString item # => 'hello'
list.contains 'hello' # => true
list.isEmpty() # => false
# Event bubbling (similar to backbone collections)
view = new Base.View
list.add view
list.on 'anEvent', (e) -> log 'a child triggered an event!'
# triggers the above log
view.trigger 'anEvent'
# You can create models of any type, just pass any class (constructor)
# as a list's model property
list = new Base.List
list.model = ListItemView
list.push tagName: 'li'
list[0] # => a new ListItemView with tagName: 'li'
# Lists also support all state methods
list.setState 'active', false
list.getState 'active'
list.toggleState 'active'
list.hasState 'active'
list.state.toJSON()
list.on 'state:change:active', ->
# Inherits from Backbone.Router
class Router extends Base.Router
stateDeafults:
firstRoute: true
routes:
'*': (route) -> @setState 'firstRoute', false
onChangeFirstRoute: (stateModel, value, options) ->
# Supports all stated methods
router.setState 'firstRoute', true
router.getState 'firstRoute'
router.toggleState 'firstRoute'
Inherits from Base.Model The state model used by Base classes. Bubbles all events received to parent as 'state:#{eventName}', so, for example, on its parent you can listen to 'state:change:someAttribute'
State models must be inited with a parent (the owner of the state model in which the state model describes the state of). E.g.
class MyStatedClass
constructor: ->
@state = new State parent: @
@state.set 'inited', true
@state.get 'inited' # => true
@listenTo @state, 'change:inited', -> # valid
@on 'state:change:inited', -> # also valid
Easier wasy of creating a new stated object. Inherits from Base.Object
class Stated extends Base.Stated
constructor: ->
super
@toggleState 'inited'
@setState 'active', true
@getState 'active' # => true
stateDefaults:
inited: false
Simple evented object contrsuctor. Supports full Backbone events API 'on', 'off', 'listenTo', etc
class MyObject extends Base.Object
constructor: ->
super
@on 'foobar', ->
Constructor for base events. Every bubbled and broadcasted view event injects a first argument that is an instanceof Base.Event which supports
view.on 'child:change:foo', (e) ->
e.preventDefault() # sets e.defaultPrevented to true
e.stopPropagation() # prevents this event from further propagating
e.target # reference to view that first triggered the event
e.currentTarget # reference to the current view handling the event
Despite the examples herein being in coffeescript, like any other coffeescript library base.js does not require that you write any code in coffeescript. Just use the .extend() method to subclass Base classes
var View = Base.View.extend({
initialize: function () {
// Do stuff
},
someMethod: function () {
// The JS way of calling super (if you ever find you need it)
Base.View.prototype.someMethod.apply(this, arguments);
// Alternatively, you can use Base._super to help out a bit
return Base._super(this, 'someMethod', arguments);
}
});