module Olelo
# Include module to add attribute editor to a class.
module Attributes
def self.included(base)
base.extend(ClassMethods)
end
# Attribute data structure
# @api private
class Attribute
include Util
# @api private
attr_reader :key, :name
def initialize(parent, name)
@name = name.to_s
@key = ['attribute', parent.path, name].compact.join('_')
end
def label
@label ||= Locale.translate(key, fallback: titlecase(name))
end
def label_tag
type = self.class.name.split('::').last.downcase
title = Locale.translate("type_#{type}", fallback: titlecase(type))
%{}
end
def build_form(attr)
"#{label_tag}#{field_tag(attr)} "
end
class String < Attribute
def field_tag(attr)
%{}
end
def parse(params)
value = params[key]
value if !value.blank?
end
end
class List < Attribute
def field_tag(attr)
%{}
end
def parse(params)
value = params[key]
value.split(/\s*,\s*/) if !value.blank?
end
end
class Integer < Attribute
def field_tag(attr)
%{}
end
def parse(params)
value = params[key]
value.to_i if !value.blank?
end
end
class Boolean < Attribute
def field_tag(attr)
%{}
end
def build_form(attr)
"
#{field_tag(attr)}#{label_tag}
\n"
end
def parse(params)
value = params[key]
true if value == 'true'
end
end
class Enum < Attribute
def initialize(parent, name, values = {})
super(parent, name)
raise 'Values must be Proc, Hash or Array' unless Proc === values || Hash === values || Array === values
@values = values
end
def field_tag(attr)
html = %{'
end
def parse(params)
value = params[key]
value if values.include?(value)
end
private
def values
if Proc === @values
@values = @values.call
raise 'Values must be Hash or Array' unless Hash === @values || Array === @values
end
@values = Hash[*@values.zip(@values)] if Array === @values
@values
end
end
class Suggestions < Enum
def field_tag(attr)
%{
}
end
def parse(params)
value = params[key]
inverted_values[value] || value if !value.blank?
end
def inverted_values
@inverted_values ||= values.invert
end
end
end
# Data structure for group of attributes
# @api private
class AttributeGroup
include Util
# @api private
attr_reader :name, :path, :children
def initialize(parent = nil, name = nil)
@name = name.to_s
@path = parent ? [parent.path, name].compact.join('_') : nil
@children = {}
end
def label
@label ||= name.blank? ? '' : Locale.translate("group_#{path}", fallback: titlecase(name))
end
# Build form for this group
# @return [String] html
# @api private
#
def build_form(attr)
html = label.blank? ? '' : "
#{escape_html label}
\n"
html << children.sort_by do |name, child|
[Attribute === child ? 0 : 1, child.label]
end.map do |name, child|
child.build_form(attr ? attr[name] : nil)
end.join
end
# Parse params and return attribute hash for this group
# @return [Hash]
# @api private
#
def parse(params)
attr = {}
children.each_pair do |name, child|
value = child.parse(params)
attr[name] = value if value
end
attr.empty? ? nil : attr
end
end
# DSL class used to initialize AttributeGroup
class AttributeDSL
include Util
# Initialize DSL with `group`
#
# @param [Olelo::Attributes::AttributeGroup] AttributeGroup to modify in this DSL block
# @yield DSL block
#
def initialize(group, &block)
@group = group
instance_eval(&block)
end
def string(name, values = nil, &block)
@group.children[name.to_s] = if values || block
Attribute::Suggestions.new(@group, name, block ? block : values)
else
Attribute::String.new(@group, name)
end
end
def integer(name)
@group.children[name.to_s] = Attribute::Integer.new(@group, name)
end
def boolean(name)
@group.children[name.to_s] = Attribute::Boolean.new(@group, name)
end
def list(name)
@group.children[name.to_s] = Attribute::List.new(@group, name)
end
def enum(name, values = nil, &block)
@group.children[name.to_s] = Attribute::Enum.new(@group, name, block ? block : values)
end
# Define attribute group
#
# @yield DSL block
# @return [void]
# @api public
#
def group(name, &block)
AttributeDSL.new(@group.children[name.to_s] ||= AttributeGroup.new(@group, name), &block)
end
end
# Extends class with attribute editor DSL
module ClassMethods
# Root attribute group
#
# @return [AttributeGroup] Root editor group
# @api private
#
def attribute_group
@attribute_group ||= AttributeGroup.new
end
# Add attribute to the attribute editor
#
# @yield DSL block
# @return [void]
# @api public
#
# @example add string attribute title
# attributes do
# string :title
# end
#
# @example add group with multiple attributes
# attributes do
# group :acl do
# list :read
# list :write
# end
#
def attributes(&block)
AttributeDSL.new(attribute_group, &block)
end
end
# Parse attributes from params hash
#
# @param [Hash] params submitted params hash
# @return [Hash] Attributes
# @api public
#
def update_attributes(params)
self.attributes = self.class.attribute_group.parse(params)
end
# Generate attribute editor form
#
# @param [Hash] default_values to use for the form
# @return [String] Generated html form
# @api public
#
def attribute_editor
self.class.attribute_group.build_form(attributes).html_safe
end
end
end