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