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