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
def active(binder = nil) #:nodoc:
@active ||= []
if binder
@active << binder
@active.uniq!
end
@active
end
module_function :active
class Base
extend Bowline::Watcher::Base
extend Bowline::Desktop::Bridge::ClassMethods
js_expose
module Async
protected
def async(*methods)
if block_given?
Thread.new(callback_proc) do |proc|
begin
self.callback_proc = proc
yield
rescue => e
Bowline::Logging.log_error(e)
end
end
else
methods.each do |method|
class_eval(<<-EOS, __FILE__, __LINE__)
def #{method}_with_async(*args, &block)
async { #{method}_without_async(*args, &block) }
end
EOS
alias_method_chain method, :async
end
end
end
end
extend Async
include Async
class << self; extend Async; end
class << self
def callback_proc(proc = nil) #:nodoc:
Thread.current[:callback] = proc if proc
Thread.current[:callback]
end
alias_method :callback_proc=, :callback_proc
def callback(result = nil)
result = yield if block_given?
callback_proc.call(result)
end
def js_invoke(window, callback, method, *args) #:nodoc:
self.callback_proc = callback
if method == :setup
setup(window)
else
send(method, *args)
end
end
def instance_invoke(id, method, *args) #:nodoc:
self.new(id).send(method, *args)
end
# An array of window currently bound.
def windows(window = nil)
@windows ||= []
if window
@windows << window
@windows.uniq!
end
@windows
end
def setup(window) #:nodoc:
Binders.active(self)
windows(window)
populate
callback(true)
end
# Populate initial items
def populate(items = initial)
self.items = items if items
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
# 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 destroyed(item)
bowline.destroyed(
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
end
# Instance of the bound class' record
attr_reader :item
def initialize(id, *args) #:nodoc:
@item = self.class.find(id)
end
protected
def callback_proc(*args) #:nodoc
self.class.callback_proc(*args)
end
alias_method :callback_proc=, :callback_proc
def callback(*args, &block)
self.class.callback(*args, &block)
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