module Lolita
# Any Lolita::Configuration class that should return visual information about itself
# can include Builder::Base, that provide default methods for _controllers_ to use.
# * #build method is used to render component for class instance.
# * #build_options is method that return specific options for builder, to pass to component,
# such as :color,:postition etc.
# * #builder is setter/getter method for Lolita::Configuration, that accept Hash or Array or single
# String or Symbol for buider.
#
# Usage in your model
# You can change behaviour of any of configuraton elements, to do so you should specify what
# builder you want to use.
# lolita do
# list do
# column do
# name :my_column
# builder :name => "my_columns",:state => :display, :if=>{:state => :display}
# end
# end
# end
# This expample show, how to replace :display component of column by your own :display.
# :if is determine, that column use builder provided to component render, but when state is :display
# then it will be replaced with this on. There are elements, that only have one :display state, than it's
# not necessary to provide :if or :unless state.
module Builder
class Custom
class << self
def create(element, *args)
possible_builder = extract_args(*args)
if possible_builder.is_a?(Hash)
Lolita::Builder::Custom.new(element, possible_builder)
else
possible_builder
end
end
def extract_args(*args)
if args && args.any?
options = args.extract_options! || {}
if args[0] && args[0].is_a?(self)
args[0]
elsif args[0].is_a?(String) || args[0].is_a?(Symbol)
options[:name] = args[0]
if args[1]
options[:state] = args[1]
end
options
elsif options
options
else
raise ArgumentError, "Don't know how to make builder from #{options}."
end
else
return {}
end
end
end
attr_accessor :options,:build_attributes
def initialize(element, attributes_as_hash)
@element = element
@options = {}
@build_attributes = {}
@conditions = {}
set_attributes(attributes_as_hash)
set_default_attribute_values
end
def with(*values)
new_values = self.class.extract_args(*values)
if new_values.is_a?(Hash)
@build_attributes = new_values
else
raise ArgumentError, "Can't build with other builder, use build on that builder!"
end
self
end
def build
path = if conditions?
switch_path do |name,state|
if conditions_met?
[fixed_name(name).to_sym,state.to_sym]
else
[self.name, self.state]
end
end
else
[self.name,self.state]
end
result = path + [merged_options]
return result
end
def name
result = if build_attributes[:name].to_s.size > 0
fixed_name(build_attributes[:name])
else
fixed_name(@name,build_attributes[:name])
end
result.to_sym
end
def state
result = if build_attributes[:state] && build_attributes[:state].to_s.size > 0
build_attributes[:state]
else
@state
end
result.to_sym
end
private
def switch_path
old_name = @name
old_state = @state
@name = nil
@state = default_state
result = yield old_name,old_state
@name = old_name
@state = old_state
result
end
def conditions?
@conditions.any?
end
def conditions_met?
@conditions_met = if conditions?
if @conditions[:if]
compare(@conditions[:if],true)
elsif @conditions[:unless]
compare(@conditions[:unless],false)
end
end
@conditions_met
end
def compare(pattern,predicate)
result = true
pattern.each do |key,value|
result &&= ((value.to_sym == self.send(key)) == predicate)
end
result
end
def fixed_name(name_to_fix, first_part = nil)
unless name_to_fix.to_s[0] == "/"
"/#{first_part.to_s.size > 0 ? first_part : default_name}/#{name_to_fix}".gsub(/\/{2,}/,"/").gsub(/\/$/,"")
else
name_to_fix
end
end
def default_name
@element.builder_default_name
end
def default_state
@element.builder_default_state
end
def attributes
[:name,:state]
end
def conditions
[:if,:unless]
end
def merged_options
result = {}
(@build_attributes || {}).merge(@options).each do |key,value|
unless attributes.include?(key.to_sym)
result[key.to_sym] = value
end
end
result
end
def set_attributes(attributes_as_hash)
attributes_as_hash.each do |attr_name, value|
if attributes.include?(attr_name.to_sym)
instance_variable_set(:"@#{attr_name}",value)
elsif conditions.include?(attr_name.to_sym)
@conditions[attr_name.to_sym] = value
else
@options[attr_name.to_sym] = value
end
end
end
def set_default_attribute_values
@state ||= default_state
end
end
def builder *args
if args && args.any?
set_builder(*args)
else
@builder||=set_builder(nil)
@builder
end
end
def builder=(*args)
set_builder(*args)
end
def build *values
result = builder.with(*values).build
result[result.size-1].merge!(default_options)
result
end
def default_options
{builder_local_variable_name => self}
end
def builder_default_name
self.class.to_s.split("::").map(&:underscore).join("/").to_sym
end
alias :builder_name :builder_default_name
def builder_default_state
:display
end
alias :default_build_state :builder_default_state
private
def set_builder *args
@builder=Lolita::Builder::Custom.create(self,*args)
end
def builder_local_variable_name
self.class.to_s.split("::").last.underscore.to_sym
end
end
end