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