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