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