module UnobtrusiveJavascript PLUGIN_NAME = 'unobtrusive_javascript' PLUGIN_PATH = "#{RAILS_ROOT}/vendor/plugins/#{PLUGIN_NAME}" PLUGIN_ASSET_PATH = "#{PLUGIN_PATH}/assets" PLUGIN_CONTROLLER_PATH = "#{PLUGIN_PATH}/lib/controllers" class Settings # Turns fake script links on or off. # By default, the URL for the generated javascript # behaviours used in your pages' +script+ tags # wil be /unobtrusive_javascript/generate. Setting # +use_fake_script_links+ to true will change # this to /behaviours/dynamic.js, disguising # the origin of your pages' behaviours. You can set this # in your environment.rb file. # # UnobtrusiveJavascript::Setting.use_fake_script_links = true cattr_accessor :use_fake_script_links # All elements with attached behaviours that do not # have an HTML +id+ attribute will have one # generated automatically, using the form _prefix_x_, # where the default prefix is "uj_element_" and x is an # automatically incremented number. You can set the # generated prefix to anything you like by setting it in your # environment.rb file: # # UnobtrusiveJavascript::Settings.generated_id_prefix = "my_prefix_" cattr_accessor :generated_id_prefix @@use_fake_script_links = false @@generated_id_prefix = "uj_element_" # Returns the URL to the external behaviours file def self.behaviours_url @@use_fake_script_links ? '/behaviours/dynamic.js' : '/unobtrusive_javascript/generate' end end def self.use_fake_script_links self::Settings.use_fake_script_links = true ActionController::Routing::Routes.add_route "/behaviours/dynamic.js", :controller => "unobtrusive_javascript", :action => "generate" end module ControllerMethods def self.included(base) base.class_eval do before_filter :initialise_js_behaviours after_filter :store_js_behaviours end end # Lets you register javascript behaviours from within # the controller. For a description of the different options # available, see UnobtrusiveJavascript::Helpers#apply_behaviour (note: # this function does not take a block like the view helper version) def apply_behaviour(selector, behaviour, opts={}) @js_behaviours ||= [] @js_behaviours << { :selector => selector, :behaviour => behaviour } end # register_js_behaviour is now deprecated in favour of apply_behaviour # American and English spellings both work alias_method :register_js_behavior, :apply_behaviour alias_method :register_js_behaviour, :apply_behaviour alias_method :apply_behavior, :apply_behaviour protected # Initialises the javascript behaviours array def initialise_js_behaviours @js_behaviours = [] end # Clears the array of registered javascript behaviours def reset_js_behaviours session[:js_behaviours] = nil end # Returns the registered javascript behaviours stored in the session def js_behaviours session[:js_behaviours] end # Stores all registered javascript behaviours in the session def store_js_behaviours session[:js_behaviours] = @js_behaviours end end module BehaviourBuilder # Renders behaviour block and rules for external # behaviours file. def behaviour_block(rules) if rules && !rules.empty? "Event.addBehavior({\n#{rules_for(rules)}\n});" else '' end end # Renders behaviour rule javascript for the behaviours file def behaviour_rule(selector, behaviour) "\"#{selector}\": function(event) {\n#{behaviour}\n}" end # Renders a collection of behaviour rules in javascript format def rules_for(rules) rules.collect { |b| behaviour_rule(b[:selector], b[:behaviour]) }.join(",\n") end end module Helpers include BehaviourBuilder # Includes the scripts and behaviours necessary for # unobtrusive javascript functionality. # # This function is deprecated. Use: # <%= javascript_include_tag :unobtrusive %> def unobtrusive_javascript_files javascript_include_tag :unobtrusive end # This is the core functionality of the plugin; # it allows you to attach javascript behaviour to your page # elements in an unobtrusive fashion. It takes three options: # * +selector+ - CSS selector and event. For a full overview of # the selector syntax, see the event:Selectors website. # http://encytemedia.com/event-selectors # * +behaviour+ - The javascript that you want to attach to the element # and event specified by +selector+ as a string of javascript. # * +opts+ - A hash of additional options. # # Attaching a behaviour to an element on your page is as simple as # specifying the element and the event you want the behaviour attached # to, using the CSS selector format, and passing in a string of javascript: # # <% apply_behaviour "#coollink:click", "alert('Hello World')" %> # # You can also make use of any of the built-in Rails helpers that # generate Javascript: # # <% apply_behaviour "#coolink:click", visual_effect(:highlight, "coollink") %> # # You will have access to two javascript variables inside your javascript string: # * +this+: returns a reference to the HTML element that the behaviour was attached to # * +event+: the event the behaviour was attached to: # # <% apply_behaviour "#coollink:click", "alert('You clicked ' this.id); Event.stop(event)" %> # # The following options can be set using the opts hash: # # * :external - If true, the behaviour will be attached to the external behaviour file. # If false, it will be rendered directly in the page inside +script+ tags. Defaults to true. # # When setting :external to false, you must call the register_js_behaviour from # within ERb output blocks. If :external is true, you can use either output or non-outputĀ blocks. # # You can also pass a block to the function instead of a string of javascript - # the block will be passed in a JavascriptGenerator object (+page+), the element (optional) # and the event (optional). You can use the Javascript generator # to write your attached behaviour using Ruby: # # <% apply_behaviour "#coollink:click" do |page, element, event| # page.alert("Hi there, I'm going to fade away...") # page.visual_effect :fade, element # end %> def apply_behaviour(selector, behaviour='', opts={}, &block) #:yields: page, element, event opts[:external] = opts[:external].nil? ? true : opts[:external] if block_given? page = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template) { } element = JavascriptProxies::JavascriptArgumentProxy.new(ActionView::Helpers::JavaScriptVariableProxy, page, 'this') event = JavascriptProxies::JavascriptArgumentProxy.new(JavascriptProxies::JavascriptEventProxy, page, 'event') args = [page, element, event][0..block.arity-1] @template.instance_exec(*args, &block) behaviour = page.to_s end if @controller.request.xhr? or !opts[:external] register_inline_behaviour_block(selector, behaviour, opts) else @controller.register_js_behaviour(selector, behaviour, opts) # ensure that the method doesn't return jibberish if <%= is accidentally used. return '' end end # register_js_behaviour is now deprecated in favour of apply_behaviour # American and English spellings both work alias_method :register_js_behavior, :apply_behaviour alias_method :register_js_behaviour, :apply_behaviour alias_method :apply_behavior, :apply_behaviour protected # Renders a block of javascript behaviours inside +script+ tags # directly within the page def register_inline_behaviour_block(selector, behaviour, opts) javascript_tag(behaviour_block([ :selector => selector, :behaviour => behaviour ])) end end end