-
-
Save gotomypc/3968027 to your computer and use it in GitHub Desktop.
Bus.js
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
# Bus 0.1(alpha) | |
# (c) 2010 John Wright, QuickLeft Inc. | |
# Bus may be freely distributed under the MIT license. | |
# For all details and documentation: | |
# http://github.com/mrjjwright/Bus | |
# | |
# | |
# | |
# Bus would not be possible without Jeremy Ashkenas who wrote CoffeeScript, the language | |
# in which Bus is written, Underscore, and Backbone.js, all which Bus uses or borrows from heavily. | |
# Save a reference to the global object. | |
root = this | |
# Save the previous value of the `Bus` variable. | |
previousBus = root.Bus | |
# The top-level namespace. All public Bus classes and modules will | |
# be attached to this. Exported for both CommonJS and the browser. | |
if exports? then Bus = exports | |
else Bus = root.Bus = {} | |
# Current version of the library. Keep in sync with `package.json`. | |
Bus.VERSION = '0.1-pre' | |
# Require Underscore, if we're on the server, and it's not already present. | |
_ = root._ | |
if (!_ and require?) then _ = require('underscore')._ | |
# For Bus's purposes, jQuery or Zepto owns the `$` variable. | |
$ = root.jQuery || root.Zepto | |
# Runs Bus.js in *noConflict* mode, returning the `Bus` variable | |
# to its previous owner. Returns a reference to this Bus object. | |
Bus.noConflict = -> | |
root.Bus = previousBus | |
return this | |
# Bus.Events * stolen from Backbone.Events and ported to CoffeeScript | |
# ----------------- | |
# A module that can be mixed in to *any object* in order to provide it with | |
# custom events. You may `bind` or `unbind` a callback function to an event | |
# `trigger`-ing an event fires all callbacks in succession. | |
# | |
# var object = {} | |
# _.extend(object, Bus.Events) | |
# object.bind('expand', function(){ alert('expanded') }) | |
# object.trigger('expand') | |
# | |
class Bus.Events | |
# Bind an event, specified by a string name, `ev`, to a `callback` function. | |
# Passing `"all"` will bind the callback to all events fired. | |
bind: (ev, callback) -> | |
calls = @_callbacks or (@_callbacks = {}) | |
list = calls[ev] or (calls[ev] = []) | |
list.push(callback) | |
return @ | |
# Remove one or many callbacks. If `callback` is null, removes all | |
# callbacks for the event. If `ev` is null, removes all bound callbacks | |
# for all events. | |
unbind: (ev, callback) -> | |
if not ev | |
@_callbacks = {} | |
else if (calls = @_callbacks) | |
if (!callback) | |
calls[ev] = [] | |
else | |
list = calls[ev] | |
if not list then return @ | |
for i in [0..list.length] | |
if callback is list[i] | |
list[i] = null | |
break | |
return @ | |
# Trigger an event, firing all bound callbacks. Callbacks are passed the | |
# same arguments as `trigger` is, apart from the event name. | |
# Listening for `"all"` passes the true event name as the first argument. | |
trigger: (eventName) -> | |
both = 2 | |
if not (calls = @._callbacks) then return @ | |
while both-- | |
ev = if both then eventName else 'all' | |
if list = calls[ev] | |
l = list.length | |
for i in [0..l] | |
unless (callback = list[i]) | |
list.splice(i, 1) | |
i-- | |
l-- | |
else | |
args = if both then Array.prototype.slice.call(arguments, 1) else arguments | |
callback.apply(@, args) | |
return @ | |
# Pubsub semantics for bind/unbind/trigger | |
Bus.publish = Bus.Events.prototype.trigger | |
Bus.subscribe = Bus.Events.prototype.bind | |
Bus.unsubscribe = Bus.Events.prototype.unbind | |
# Bus Core methods | |
# Start the bus | |
Bus.start = () -> | |
bindBusLinks() | |
bindStaticLinks() | |
# Handle buttons or hrefs intended to control view controllers | |
bindBusLinks = -> | |
$("a.push-vc, button.push-vc").live 'click', (event) -> | |
event.preventDefault() | |
vcid = $(this).attr('href') | |
vcid = vcid or $(this).attr('data-vcid') | |
if Bus.hasViewController(vcid) | |
Bus.pushViewController(vcid) | |
else | |
Bus.publish('vc/missing', vcid) | |
return false | |
$("a.pop-vc, button.pop-vc").live 'click', (event) -> | |
event.preventDefault() | |
vcid = $(this).attr('href') | |
vcid = vcid or $(this).attr('data-vcid') | |
if Bus.hasViewController(vcid) | |
Bus.popViewController(vcid) | |
console.log('got here') | |
return false | |
$("a.show-vc, button.pop-vc").live 'click', (event) -> | |
event.preventDefault() | |
vcid = $(this).attr('href') | |
vcid = vcid or $(this).attr('data-vcid') | |
if Bus.hasViewController(vcid) | |
Bus.showViewController(vcid) | |
else | |
Bus.publish('vc/missing', vcid) | |
return false | |
# If hrefs have class of 'static', load their | |
# href into dom element with id of the attr 'data-target-id' | |
bindStaticLinks = -> | |
$('a.static').live 'click', (e) -> | |
target = $(this).attr('data-target-id') | |
$("##{target}").load $(this).attr('href') if target | |
e.preventDefault() | |
return false | |
# View Controller management | |
Bus.pushSupport = root.history? and root.history.pushState? | |
# we store a viewController stack if the env doesn't have pushState support | |
Bus.viewControllerStack = [] | |
# A map of every view controller managed by the bus | |
Bus.viewControllers = {} | |
if Bus.pushSupport | |
window.onpopstate = (event) -> | |
# window.afterFirstLoad is a hack to make sure that this function is not called twice on page load | |
if event.state? and window.afterFirstLoad | |
Bus.popViewController(event.state) | |
window.afterFirstLoad = true | |
# Push a view controller onto the viewController stack and show it | |
Bus.pushViewController = (viewController, replace) -> | |
if _.isString(viewController) | |
vcid = viewController | |
viewController = Bus.viewControllers[vcid] or null | |
else vcid = viewController.vcid() | |
throw 'Missing viewController' unless viewController? | |
throw 'Missing vcid' unless vcid? | |
if Bus.pushSupport | |
if replace then window.history.replaceState(vcid, document.title, url) | |
else window.history.pushState(vcid, document.title, "/#{vcid}") | |
else | |
Bus.viewControllerStack.push(vcid) | |
viewController.show() | |
Bus.publish("vc/pushed", viewController.vcid()) | |
# pops a view controller from the viewController stack and hides it | |
Bus.popViewController = (state) -> | |
state = state ? Bus.viewController.stack.pop() | |
return unless state.vcid? | |
viewController = Bus.viewControllers[state.vcid] | |
viewController.hide() | |
# Show a viewController and alert the bus. Pass a vcid or a viewController. | |
Bus.showViewController = (viewController) -> | |
if _.isString(viewController) | |
vcid = viewController | |
viewController = Bus.viewControllers[vcid] or null | |
else vcid = viewController.vcid() | |
throw 'Missing viewController' unless viewController? | |
throw 'Missing vcid' unless vcid? | |
viewController.show() | |
Bus.registerViewController = (viewController) -> | |
throw 'Must supply vcid' unless viewController and viewController.vcid? | |
Bus.viewControllers[viewController.vcid()] = viewController | |
Bus.hasViewController = (viewController) -> | |
vcid = if _.isString(viewController) then viewController else viewController.vcid() | |
return Bus.viewControllers[vcid]? | |
# | |
# Bus.View | |
# | |
# Simply Bus's View ported to CoffeeScript with some unnecssary stuff removed | |
class Bus.View | |
constructor: (@options) -> | |
@._configure(options or {}) | |
@.initialize(options) | |
@addStyle() | |
# Static props | |
@selectorDelegate: (selector) -> | |
$(selector, @el) | |
# Cached regex to split keys for `delegate`. | |
@eventSplitter = /^(\w+)\s*(.*)$/ | |
# Attach the `selectorDelegate` function as the `$` property. | |
$: View.selectorDelegate | |
# Initialize is an empty function by default. Override it with your own | |
# initialization logic. | |
initialize : -> | |
# **render** is the core function that your view should override. The | |
# convention is for **render** to always return `this`. | |
render: -> | |
return @ | |
# Uses underscore.css to add style | |
addStyle: -> | |
if @style and not @styleLoaded | |
$("<style type='text/css'>#{_.toCSS(@style)}</style>").appendTo("head") | |
@styleLoaded = true | |
# Set callbacks, where `@callbacks` is a hash of | |
# | |
# *{"event selector": "callback"}* | |
# | |
# { | |
# 'mousedown .title': 'edit', | |
# 'click .button': 'save' | |
# } | |
# | |
# pairs. Callbacks will be bound to the view, with `this` set properly. | |
# Uses event delegation for efficiency. | |
# Omitting the selector binds the event to `@el`. | |
# This only works for delegate-able events: not `focus`, `blur`, and | |
# not `change`, `submit`, and `reset` in Internet Explorer. | |
# Warning, unlike Backbone.View delegateEvents this method does not unbind | |
# events on the current element before delegating the new ones. | |
# This is so view controllers and views can both respond to events | |
delegateEvents: (obj, events) -> | |
obj = obj || this | |
events = events || @events | |
el = @el | |
return unless events | |
return unless el | |
for key of events | |
methodName = events[key] | |
match = key.match(View.eventSplitter) | |
eventName = match[1] | |
selector = match[2] | |
method = _.bind(obj[methodName], obj) | |
if selector is '' | |
$(el).bind(eventName, method) | |
else | |
$(el).delegate(selector, eventName, method) | |
# Performs the initial configuration of a View with a set of options. | |
_configure: (options) -> | |
if (@options) then options = _.extend({}, @options, options) | |
if (options.el) then @el = options.el | |
@options = options | |
# Bus.ViewController | |
# -------------- | |
class Bus.ViewController | |
constructor: -> | |
@active = false | |
# get a view by default | |
@view = new Bus.View() | |
@initialize() | |
Bus.registerViewController(@) | |
# Subclasses should override to publish and subscribe to the bus and other initialization | |
initialize: -> | |
# Display the view controller | |
show: -> | |
@active = true | |
@renderView() | |
Bus.publish('vc/show', @vcid) | |
# Subclasses should call this from renderView if active. | |
# View controllers also can overwrite this in order to attach their views to the dom any way they want. | |
# By default this function will cause the implicit view's el to occupy its $parent, taking up it's whole html. | |
attachView: -> | |
if @view and @view.$parent and @view.el | |
@inactivateNodeResident(@view.$parent) | |
@view.$parent.html(@view.el) | |
@occupyNode(@view.$parent) | |
@.delegateEvents() | |
# Inactive the current view controller that is rendering into this node, if one | |
inactivateNodeResident: ($node) -> | |
currentVcid = $node.attr('data-vcid') | |
currentViewController = Bus.viewControllers[currentVcid] if currentVcid | |
currentViewController.active = false if currentViewController | |
# Set the vcid on the suppled node as a data attr | |
occupyNode: ($node) -> | |
$node.attr('data-vcid', @vcid()) | |
# Convenience function to delegate events on the implicit view to the view controller | |
delegateEvents: (events) -> | |
events = events || @events | |
return unless events | |
return unless @view | |
@view.delegateEvents(@, events) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment