## # Attrtastic, in its assumption, should be similar to formtastic and # ease displaying AR informations, help create scaffolded show and index # pages. # # @author Boruta Mirosław module Attrtastic class SemanticAttributesBuilder # Only for testing purposes attr_reader :record, :template def initialize(record, template) @record, @template = record, template end ## # Creates block of attributes with optional header. Attributes are surrounded with ordered list. # # @overload attributes(options = {}, &block) # Creates attributes list without header, yields block to include each attribute # # @param [Hash] options Options for formating attributes block # @option options [String] :name (nil) Optional header of attributes section # @option options [String] :class ('') Name of html class to add to attributes block # @option options [String] :header_class ('') Name of html class to add to header # @yield Block which can call #attribute to include attribute value # # @example # <% attr.attributes do %> # <%= attr.attribute :name %> # <%= attr.attribute :email %> # <% end %> # # @example # <% attr.attributes :name => "User" do %> # <%= attr.attribute :name %> # <%= attr.attribute :email %> # <% end %> # # @example # <% attr.attributes :for => :user do |user| %> # <%= user.attribute :name %> # <%= user.attribute :email %> # <% user.attribute :profile do %> # <%= link_to h(user.record.name), user_path(user.record) %> # <% end %> # <% end %> # # @example # <% attr.attributes :for => @user do |user| %> # <%= user.attribute :name %> # <%= user.attribute :email %> # <% user.attribute :profile do %> # <%= link_to h(@user.name), user_path(@user) %> # <% end %> # <% end %> # # @example # <% attr.attributes :for => :posts do |post| %> # <%= post.attribute :author %> # <%= post.attribute :title %> # <% end %> # # @example # <% attr.attributes :for => @posts do |post| %> # <%= post.attribute :author %> # <%= post.attribute :title %> # <% end %> # # @overload attributes(header, options = {}, &block) # Creates attributes list with header and yields block to include each attribute # # @param [String] header Header of attributes section # @param [Hash] options Options for formating attributes block # @option options [String] :class ('') Name of html class to add to attributes block # @option options [String] :header_class ('') Name of html class to add to header # @option optinos [Symbol,Object] :for Optional new record for new builder # passed as argument block. This new record can be symbol of method name for actual # record, or any other object which is passed as new record for builder. # @yield Block which can call #attribute to include attribute value # @yieldparam builder Builder instance holding actual record (retivable via #record) # # @example # <% attr.attributes "User info" do %> # <%= attr.attribute :name" %> # <%= attr.attribute :email %> # <% end %> # # @example # <% attr.attributes "User", :for => :user do |user| %> # <%= user.attribute :name %> # <%= user.attribute :email %> # <% user.attribute :profile do %> # <%= link_to h(user.record.name), user_path(user.record) %> # <% end %> # <% end %> # # @example # <% attr.attributes "User", :for => @user do |user| %> # <%= user.attribute :name %> # <%= user.attribute :email %> # <% user.attribute :profile do %> # <%= link_to h(@user.name), user_path(@user) %> # <% end %> # <% end %> # # @example # <% attr.attributes "Post", :for => :posts do |post| %> # <%= post.attribute :author %> # <%= post.attribute :title %> # <% end %> # # @example # <% attr.attributes "Post", :for => @posts do |post| %> # <%= post.attribute :author %> # <%= post.attribute :title %> # <% end %> # # @overload attributes(*symbols, options = {}) # Creates attributes list without header, attributes are given as list of symbols (record properties) # # @param [Symbol, ...] symbols List of attributes # @param [Hash] options Options for formating attributes block # @option options [String] :name (nil) Optional header of attributes section # @option options [String] :class ('') Name of html class to add to attributes block # @option options [String] :header_class ('') Name of html class to add to header # # @example # <% attr.attributes :name, :email %> # # @example # <% attr.attributes :name, :email, :for => :author %> # # @example # <% attr.attributes :name, :email, :for => @user %> # # @example # <% attr.attributes :title, :for => :posts %> # # @example # <% attr.attributes :title, :for => @posts %> # # @overload attributes(header, *symbols, options = {}) # Creates attributes list with header, attributes are given as list of symbols (record properties) # # @param [String] header Header of attributes section # @param [Symbol, ...] symbols Optional list of attributes # @param [Hash] options Options for formating attributes block # @option options [String] :class ('') Name of html class to add to attributes block # @option options [String] :header_class ('') Name of html class to add to header # # @example # <% attr.attributes "User info" :name, :email %> # # @example # <% attr.attributes "Author", :name, :email, :for => :author %> # # @example # <% attr.attributes "Author", :name, :email, :for => @user %> # # @example # <% attr.attributes "Post", :title, :for => :posts %> # # @example # <% attr.attributes "Post", :title, :for => @posts %> # # @example All together # <% attr.attributes "User info", :name, :email, :class => "user_info", :header_class => "header important" %> # # @example With block # <% attr.attributes "User info" :class => "user_info", :header_class => "header important" do %> # <%= attr.attribute :name %> # <%= attr.attribute :email %> # <% end %> # # @see #attribute def attributes(*args, &block) options = {} if args.last and args.last.kind_of? Hash options = args.last args = args[0 .. -2] end options[:html] ||= {} if args.first and args.first.is_a? String options[:name] = args.shift end if options[:for].blank? attributes_for(record, args, options, &block) else if options[:for].is_a? Symbol for_value = record.send(options[:for]) else for_value = options[:for] end [*for_value].each do |value| attributes_for(value, args, options, &block) end end end ## # Creates list entry for single record attribute # # @overload attribute(method, options = {}) # Creates entry for record attribute # # @param [Symbol] method Attribute name of given record # @param [Hash] options Options # @option options [Hash] :html ({}) Hash with optional :class, :label_class and :value_class names of class for html # @option options [String] :label Label for attribute entry, overrides default label name from symbol # @option options [String] :value Value of attribute entry, overrides default value from record # @option options [Boolean] :display_empty (false) Indicates if print value of given attribute even if it is blank? # # @example # <%= attr.attribute :name %> # # @example # <%= attr.attribute :name, :label => "Full user name" %> # # @example # <%= attr.attribute :name, :value => @user.full_name %> # # @overload attribute(method, options = {}, &block) # Creates entry for attribute given with block # # @param [Symbol] method Attribute name of given record # @param [Hash] options Options # @option options [Hash] :html ({}) Hash with optional :class, :label_class and :value_class names of classes for html # @option options [String] :label Label for attribute entry, overrides default label name from symbol # @yield Block which is executed in place of value for attribute # # @example # <% attr.attribute :name do %> # <%= link_to @user.full_name, user_path(@user) %> # # @overload attribute(options = {}, &block) # Creates entry for attribute with given block, options[:label] is mandatory in this case. # # @param [:Hash] options Options # @option options [Hash] :html ({}) Hash with optional :class, :label_class and :value_class names of classes for html # @option options [String] :label Mandatory label for attribute entry # @yield Block which is executed in place of value for attribute # # @example # <% attr.attribute :label => "User link" do %> # <%= link_to @user.full_name, user_path(@user) %> # # @example # <%= attr.attribute :name, :display_empty => true %> # # @example # <% attr.attribute :label => "User link" do %> # <%= link_to @user.full_name, user_path(@user) %> # def attribute(*args, &block) options = {} if args.last and args.last.kind_of? Hash options = args.last args = args[0 .. -2] end options[:html] ||= {} method = args.shift html_label_class = [ "label", options[:html][:label_class] ].compact.join(" ") html_value_class = [ "value", options[:html][:value_class] ].compact.join(" ") html_class = [ "attribute", options[:html][:class] ].compact.join(" ") label = options.key?(:label) ? options[:label] : label_for_attribute(method) label_content = template.content_tag(:span, label, :class => html_label_class) unless block_given? value = options.key?(:value) ? options[:value] : value_of_attribute(method) value_content = template.content_tag(:span, value, :class => html_value_class) if value.present? or options[:display_empty] content = [ label_content, value_content ].join template.content_tag(:li, content, :class => html_class) end else template.concat(template.tag(:li, {:class => html_class}, true)) template.concat(label_content) template.concat(template.tag(:span, {:class => html_value_class}, true)) yield template.concat("</span>") template.concat("</li>") end end private def attributes_for(object, methods, options, &block) new_builder = self.class.new(object, template) html_class = [ "attributes", options[:html].delete(:class) ].compact.join(" ") html_header_class = [ "legend", options[:html].delete(:header_class) ].compact.join(" ") template.concat(template.tag(:div, {:class => html_class}, true)) header = options[:name] if header.present? template.concat(template.content_tag(:div, header, :class => html_header_class)) end if block_given? template.concat(template.tag(:ol, {}, true)) yield(new_builder) template.concat("</ol>") elsif methods.present? template.concat(template.tag(:ol, {}, true)) attrs = methods.map {|method| new_builder.attribute(method, options)}.compact.join template.concat(attrs) template.concat("</ol>") end template.concat("</div>") end def label_for_attribute(method) if record.class.respond_to?(:human_attribute_name) record.class.human_attribute_name(method.to_s) else method.to_s.send(:humanize) end end def value_of_attribute(method) value = record.send(method) value_methods = [ :to_label, :display_name, :full_name, :name, :title, :username, :login, :value ] value_method = value_methods.find { |m| value.respond_to?(m) } || :to_s value.send(value_method) end end ## # Helper which should be included in ActionView. Adds #semantic_attributes_for # method, which helps printing attributes for given record, similar to # formtastic's sematnic_form_for # # @example # ActionView::Base.send :include, Attrtastic::SemanticAttributesHelper # # @example Example of useage # <% semantic_attributes_for @user do |attr| %> # <% attr.attributes "User info" do %> # <%= attr.attribute :name %> # <%= attr.attribute :email %> # <% end %> # <% attr.attributes "User details" do %> # <%= attr.attribute :weight %> # <%= attr.attribute :height %> # <%= attr.attribute :age %> # <% end %> # <% end %> module SemanticAttributesHelper ## # Creates attributes for given object # # @param[ActiveRecord] record AR instance record for which to display attributes # @param[Hash] options Opions # @option options [Hash] :html ({}) Hash with optional :class html class name for html block # @yield [attr] Block which is yield inside of markup # @yieldparam [SemanticAttributesBuilder] builder Builder for attributes for given AR record # # @example # <% semantic_attributes_for @user do |attr| %> # <% attr.attributes do %> # <%= attr.attribute :name %> # <%= attr.attribute :email %> # <% end %> # <% end %> # def semantic_attributes_for(record, options = {}, &block) options[:html] ||= {} html_class = [ "attrtastic", record.class.to_s.underscore, options[:html][:class] ].compact.join(" ") concat(tag(:div, { :class => html_class}, true)) yield SemanticAttributesBuilder.new(record, self) if block_given? concat("</div>") end end end