require 'nano/inflect' require 'nitro/compiler/morphing' module Nitro # A collection of useful Javascript helpers. This modules # integrates helpers for the following javascript libraries: # # * behaviour.js # * prototype.js # * effects.js # * dragdrop.js # * controls.js module JavascriptHelper # Insert an anchor to execute a given function when the link is followed. # Call with the name of the link, and the function to be called: # link_to_function "Do it." :go def link_to_function(name, function) %{#{name}} end # -- older stuff -- unless const_defined? :DEFAULT_JAVASCRIPT_FILES DEFAULT_JAVASCRIPT_FILES = [ 'js/behaviour.js', 'js/prototype.js', 'js/effects.js', 'js/dragdrop.js', 'js/controls.js' ] end # :section: behaviour.js # # Behaviour.js is a third-party library for keeping HTML clean of javascript. # The +behaviour+ method provides an interface to that library. # To learn more about the concept, visit the distributor: # http://bennolan.com/behaviour/ # Register javascript code with an HTML element of a given id with the # +behaviour+ method. # # Example: # # behaviour '#alert', %{ # el.onclick = function() { # alert('Hello world'); # } # } def behaviour(id, js) @_behaviours ||= [] @_behaviours << [id, js] return nil end # :section: prototype.js # # Prototype.js is a third-party library that provides a number of functions # for AJAX-style interaction with the browser. It depends on the Behaviour.js # library. Prototype's homepage is http://prototype.conio.net/ # A live, or asynchronous, request is one that does not bring the user to a # new page. It is used to send data back to the web server while the user is # still interacting with a document. # # Call +live+ with the id of an achor element as a string or a symbol. # Alternatively, add async="true" to the anchor (A) element. Specify the # anchor to be called either as a second parameter to the +live+ method, or # in the HREF option of the anchor element. # # Examples: # # live :id_of_anchor_element [:method] # Go! def live_request(id, options = {}) __append_script_file__ 'js/behaviour.js' __append_script_file__ 'js/prototype.js' if href = options.delete(:href) behaviour "##{id}", %{ el.onclick = function() { new Ajax.Request('#{href}', #{hash_to_js(options)}); return false; } } else behaviour "##{id}", %{ el.onclick = function() { new Ajax.Request(el.href, #{hash_to_js(options)}); return false; } } end return nil end alias_method :live, :live_request alias_method :async, :live_request # Clicking the element will make it disappear. If you want it to reappear, # you'll have to call toggle(). def toggleable(id, options = {}) __append_script_file__ 'js/prototype.js' behaviour "##{id}", %{ el.onclick = function() { Element.toggle('#{id}'); return false; } } return nil end # :section: script.aculo.us dragdrop.js # The user may click and drag the element about the screen. def draggable(id, options = {}) __append_script_file__ 'js/behaviour.js' __append_script_file__ 'js/prototype.js' __append_script_file__ 'js/effects.js' __append_script_file__ 'js/dragdrop.js' __append_script__ "\nnew Draggable('#{id}', #{hash_to_js(options)});" return nil end # :section: script.aculo.us controls.js # Add autocomplete functionality to a text field. def auto_complete(id, options = {}) update = options[:update] || "#{id}_auto_complete" url = options[:url] || "#{id}_auto_complete" __append_script_file__ 'js/behaviour.js' __append_script_file__ 'js/prototype.js' __append_script_file__ 'js/effects.js' __append_script_file__ 'js/controls.js' __append_script__ "\nnew Ajax.Autocompleter('#{id}', '#{update}', '#{url}');" # Turn off the browser's autocomplete functionality to avoid # interference. behaviour "##{id}", %{ el.autocomplete = 'off'; } return nil end # :section: styling. # Style border. def style_border end # Round element corners using the nifty corners technique. def round_corners(id, options = {}) o = { :bg => '#fff', :fg => '#ccc', }.update(options) __append_script_file__ 'js/prototype.js' __append_script_file__ 'js/round.js' __append_script__ %{ Box.round('#{id}', '#{o[:fg]}', '#{o[:bg]}'); } __append_css__ %{ .rtop,.rbottom{display:block} .rtop *,.rbottom *{display:block;height: 1px;overflow: hidden} .r1{margin: 0 5px} .r2{margin: 0 3px} .r3{margin: 0 2px} .r4{margin: 0 1px;height: 2px} } return nil end # Generalized border decoration. def decorate_borders(options = {}) o = { :bg => '#fff', :fg => '#ccc', :klass => 'w' }.update(options) klass = o[:klass] __append_script_file__ 'js/prototype.js' __append_script_file__ 'js/styler.js' __append_script__ %{ decorateBorders('#{klass}'); } __append_css__ %{ .#{klass}t { background: #{o[:bg]}; background-image: url(m/#{klass}t.gif); background-repeat: repeat-x; background-position: top; } .#{klass}r { background-image: url(m/#{klass}r.gif); background-repeat: repeat-y; background-position: right; } .#{klass}b { background-image: url(m/#{klass}b.gif); background-repeat: repeat-x; background-position: bottom; } .#{klass}l { background-image: url(m/#{klass}l.gif); background-repeat: repeat-y; background-position: left; } .#{klass}tl { background-image: url(m/#{klass}tl.gif); background-repeat: no-repeat; background-position: top left; } .#{klass}tr { background-image: url(m/#{klass}tr.gif); background-repeat: no-repeat; background-position: top right; } .#{klass}bl { background-image: url(m/#{klass}bl.gif); background-repeat: no-repeat; background-position: bottom left; } .#{klass}br { background-image: url(m/#{klass}br.gif); background-repeat: no-repeat; background-position: bottom right; } .#{klass}c { padding: 25px; } } return nil end # ----------------------------------------------------------- # :section: general javascript helpers. # Inserts links to the .js files necessary for your page. Call it from within # HEAD. Add other script files as arguments if desired. def include_script(*files) return if @_script_files.nil? and files.empty? code = '' for file in files code << %|\n| end if files for file in @_script_files code << %|\n| end if @_script_files return code end # Escape carrier returns and single and double quotes for JavaScript segments. def escape_javascript(js) (js || '').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" } end # Converts a Ruby hash to a Javascript hash. def hash_to_js(options) '{' + options.map {|k, v| "#{k}:#{v}"}.join(', ') + '}' end # Emits the aggregated css. def emit_css return unless @_css %{} end alias_method :helper_css, :emit_css # Call this in your template/page to include the javascript statements that # link your HTML to the javascript libraries. Must be called after the HTML # elements involved, i.e., at the bottom of the page. #-- # FIXME: refactor this! #++ def emit_script code = %| | end alias_method :helper_script, :emit_script # ... def js_distance_of_time_in_words(time) time = time.utc.strftime("%a, %d %b %Y %H:%M:%S GMT") %|#{time}| end # :section: internal helpers. def __append_script_file__(file) @_script_files ||= [] @_script_files << file unless @_script_files.include?(file) end def __append_script__(script) @_script ||= [] @_script << script unless @_script.include?(script) end def __append_css__(css) @_css ||= [] @_css << css unless @_css.include?(css) end end # :section: Javascript related morphers. # Transform a normal achor into an asynchronous request: # ... # becomes # ... class AsyncMorpher < Morpher def before_start(buffer) href = @attributes['href'] @attributes['href'] = '#' @attributes['onclick'] = "new Ajax.Request('#{href}'); return false;" @attributes.delete(@key) end end class LocalMorpher < Morpher def before_start(buffer) @attributes['href'] = '#' @attributes['onclick'] = "ngs#{@value.camelcase(true)}();" @attributes.delete(@key) end end # Install the morphers. Morphing.add_morpher :async, AsyncMorpher Morphing.add_morpher :local, LocalMorpher end # * George Moschovitis