module Bowline
module Binders
# Binders are a central part of Bowline. They perform two main functions:
# 1) Bind a model to the view, so any changes to the model get automatically
# reflected in the view.
# 2) View abstraction of the model. You can define view specific class & instance
# methods, and easily call them from bound JavaScript objects.
#
# To use a binder, you first need to bind it to a model using the bind method.
# Example:
# class UsersBinder < Bowline::Binders::Base
# bind User
# end
#
# Once a class is bound, any updates to the model automatically update any bound HTML.
# The class names in the HTML are tied to the model's attribute names.
# You can bind HTML using the bowline.js bindup function.
# Example:
#
#
#
# =Class methods
#
# You can define class methods on your binder, and call them using JavaScript
# using the invoke function on the bound HTML element.
# Example:
#
#
# =Instance methods
#
# You can call your binders instance method from JavaScript by calling the invoke
# function on the generated HTML elements. Your binder's instance methods have access
# to an 'element' variable, which is the jQuery element, and a 'item' variable, which
# is the bound model's instance record.
#
# Example:
# class UsersBinder < Bowline::Binders::Base
# bind User
# def charge!
# #...
# end
# end
#
#
#
# For more documentation on Bowline's JavaScript API, see bowline.js
class Base
extend Bowline::Watcher::Base
extend Bowline::Desktop::Bridge::ClassMethods
js_expose
class << self
# An array of window currently bound.
def windows
@windows ||= []
end
def setup(window) #:nodoc:
self.windows << window
if initial_items = initial
self.items = initial_items
end
Hash.new # Empty options
end
# Called by a window's JavaScript whenever that window is bound to this Binder.
# This method populates the window's HTML with all bound class' records.
# Override this if you don't want to send all the class' records to the window.
# Example:
# def initial
# klass.all(:limit => 10)
# end
def initial
end
def js_invoke(window, method, *args) #:nodoc:
if method == :setup
setup(window)
else
send(method, *args)
end
end
def instance_invoke(id, meth, *args) #:nodoc:
self.new(id).send(meth, *args)
end
# Calls .find on the klass sent to the bind method.
# This is used internally, to find records when the page
# invoke instance methods.
def find(id)
klass.find(id)
end
# Set the binder's items. This will replace all items, and update the HTML.
def items=(items)
bowline.replace(name, items.to_js).call
end
# Add a new item to the binder, updating the HTML.
# This method is normally only called internally by
# the bound class's after_create callback.
def created(item)
bowline.created(
name,
item.id,
item.to_js
).call
end
# Update an item on the binder, updating the HTML.
# This method is normally only called internally by
# the bound class's after_update callback.
def updated(item)
bowline.updated(
name,
item.id,
item.to_js
).call
end
# Remove an item from the binder, updating the HTML.
# This method is normally only called internally by
# the bound class's after_destroy callback.
def removed(item)
bowline.removed(
name,
item.id
).call
end
# Returns class set by the 'bind' method
def klass
@klass || raise("klass not set - see bind method")
end
# JavaScript proxy to the page.
# See Bowline::Desktop::Proxy for more information.
# Example:
# page.myFunc(1,2,3).call
def page
Bowline::Desktop::Proxy.new(
windows.length == 1 ? windows.first : windows
)
end
# JavaScript proxy to the Bowline object.
# See Bowline::Desktop::Proxy for more information.
# Example:
# bowline.log("msg").call
def bowline
page.Bowline
end
# Javascript proxy to jQuery.
# See Bowline::Desktop::Proxy for more information.
# Example:
# jquery.getJSON("http://example.com").call
def jquery
page.jQuery
end
# See Bowline::logger
def logger
Bowline::logger
end
# Trigger events on all elements
# bound to this binder.
# Example:
# trigger(:reload, {:key => :value})
def trigger(event, data = nil)
bowline.trigger(
name,
format_event(event),
data
).call
end
# Helper method to trigger a loading
# event either side of a block:
# loading {
# # Slow code, e.g. http call
# }
def loading(&block)
trigger(:loading, true)
yield
trigger(:loading, false)
end
def format_event(name) #:nodoc:
name.is_a?(Array) ?
name.join('.') :
name.to_s
end
end
# jQuery element object
attr_reader :element
# Instance of the bound class' record
attr_reader :item
def initialize(id, *args) #:nodoc:
@element = self.class.bowline.element(
self.class.name, id
)
@item = self.class.find(id)
end
protected
# Trigger jQuery events on this element.
# Example:
# trigger(:highlight)
def trigger(event, data = nil)
element.trigger(
self.class.format_event(event),
data
).call
end
# Remove element from the view
def remove!
self.class.removed(item)
end
# Shortcut methods
# See self.class.page
def page
self.class.page
end
# See self.class.jquery
def jquery
self.class.jquery
end
# See self.class.logger
def logger
self.class.logger
end
end
end
end