require 'glue/configuration'
require 'nitro/helper/xhtml'
module Nitro
# :section: Property controls.
module Form
# A Form control.
class Control
include Nitro::XhtmlHelper
# Fetch the instance vars in a nice way use either rel
# or prop.
#
# values/value contain the contents of the prop or rel,
# (values reads better for relations)
attr_reader :prop
alias_method :rel, :prop
attr_reader :obj
attr_reader :value
alias_method :values, :value
# setup instance vars to use in methods
def initialize(obj, key, options = {})
@obj = obj
@prop = key
@value = options[:value] || obj.send(key.name.to_sym)
@options = options
end
# Main bulk of the control. Overide to customise
def render
"No view for this control"
end
# Label item. Override to customise
def label
%{}
end
# Custom callback to process the request information
# posted back from the form
#
# When Property.populate_object (or fill) is called
# with the :preprocess => true option then this
# method will get called before the value is pushed
# onto the object that is getting 'filled'
#
# Overide this on controls that require special mods
# to the incoming values
def on_populate(val)
return val
end
private
def emit_style
if prop.respond_to?(:control_style)
style = prop.control_style
elsif self.class.respond_to?(:style)
style = self.class.style
else
style = nil
end
style ? %{ style="#{style}"} : ''
end
# add support to your controls for being disabled
# by including an emit_disabled on form items
# or testing for is_disabled? on more complex controls
def emit_disabled
is_disabled? ? %{ disabled="disabled"} : ''
end
def is_disabled?
return false if @options[:all]
@options[:disable_controls] || @prop.disable_control
end
end
# Fixnum
class FixnumControl < Control
setting :style, :default => 'width: 100px', :doc => 'The default style'
def render
style = prop.control_style ||self.class.style
%{
}
end
def step
1
end
end
# Float
class FloatControl < FixnumControl
setting :style, :default => 'width: 100px', :doc => 'The default style'
def step
0.1
end
end
# Text
class TextControl < Control
setting :style, :default => 'width: 250px', :doc => 'The default style'
def render
%{}
end
end
# Password
class PasswordControl < Control
setting :style, :default => 'width: 250px', :doc => 'The default style'
def render
%{}
end
end
# Textarea
class TextareaControl < Control
setting :style, :default => 'width: 500px; height: 100px', :doc => 'The default style'
def render
%{}
end
end
# CheckboxControl < Control
class CheckboxControl < Control
setting :style, :default => '', :doc => 'The default style'
def render
checked = value == true ? ' checked="checked"':''
%{}
end
end
# ArrayControl
class ArrayControl < Control
def render
str = emit_container_start
str << emit_js
if values.empty?
str << emit_array_element(:removable => false)
else
removable = values.size != 1 ? true : false
values.each do |item|
str << emit_array_element()
end
end
str << emit_container_end
end
def emit_array_element(options={})
removable = options.fetch(:removable, true)
%{
}
end
def emit_container_start
%{
}
end
def emit_container_end
%{
}
end
def emit_js
%{
}
end
end
# :section: Relation controls.
# RefersTo. Also used for BelongsTo.
class RefersToControl < Control
def render
%{
}
end
def emit_options
objs = rel.target_class.all
selected = selected.pk if selected = value
%{
#{options(:labels => objs.map{|o| o.to_s}, :values => objs.map{|o| o.pk}, :selected => selected)}
}
end
end
# HasMany, ManyToMany and JoinsMany
class HasManyControl < Control
#pre :do_this, :on => :populate_object
def render
str = emit_container_start
str << emit_js
if selected_items.empty?
str << emit_selector(:removable => false)
else
removable = selected_items.size != 1 ? true : false
selected_items.each do |item|
str << emit_selector(:selected => item.pk)
end
end
str << emit_container_end
end
private
# these parts are seperated from render to make it easier
# to extend and customise the HasManyControl
def all_items
return @all_items unless @all_items.nil?
@all_items = rel.target_class.all
end
def selected_items
values
end
def emit_container_start
%{
}
end
def emit_container_end
%{
}
end
# :removable controls wether the minus button is active
# :selected denotes the oid to flag as selected in the list
def emit_selector(options={})
removable = options.fetch(:removable, true)
selected = options.fetch(:selected, nil)
%{
}
end
# Inline script: override this to change behavior
def emit_js
%{
}
end
end
# The controls map.
class Control
# Setup the mapping of names => control classes
setting :map, :doc => 'Mappings of control names => classes', :default => {
:fixnum => FixnumControl,
:integer => FixnumControl,
:float => FloatControl,
:boolean => CheckboxControl,
:checkbox => CheckboxControl,
:string => TextControl,
:password => PasswordControl,
:textarea => TextareaControl,
:true_class => CheckboxControl,
:array => ArrayControl,
:refers_to => RefersToControl,
:has_one => RefersToControl,
:belongs_to => RefersToControl,
:has_many => HasManyControl,
:many_to_many => HasManyControl,
:joins_many => HasManyControl
}
# Fetch a control, setup for 'obj' for a property, or relation
# or :symbol defaults to Control if not found
def self.fetch(obj, key, options={})
if key.kind_of? Og::Relation
control_sym = key[:control] || key.class.to_s.demodulize.underscore.to_sym
elsif key.kind_of? Property
control_sym = key[:control] || key.klass.to_s.underscore.to_sym
else
control_sym = key.to_sym
end
default_to = options.fetch(:default, Control)
self.map.fetch(control_sym, Control).new(obj, key, options)
end
end
end
end
# * George Moschovitis
# * Chris Farmiloe