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 private 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 def behaviour(id, js) @_behaviours ||= [] @_behaviours << [id, js] return nil end # :section: prototype.js 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 # Denotes an element as toggleable. 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 # Make the element dragable. 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. # Include external javascript file. 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 # Emits the aggregated helper javascript. #-- # 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 end # * George Moschovitis