lib/presentation/grid.rb in presenting-2.0.0 vs lib/presentation/grid.rb in presenting-2.0.1

- old
+ new

@@ -1,160 +1,162 @@ -module Presentation - # TODO: ability to render a hash - # TODO: custom css classes for rows and/or cells - # TODO: document or complain for required options -- id and fields - # TODO: make fields= accept an ActiveRecord::Base.columns array for a default field set - class Grid < Base - # The id for this presentation. Required. - attr_accessor :id - - # The display title for this presentation. Will default based on the id. - attr_writer :title - def title - @title ||= self.id.titleize - end - - # Paradigm Example: - # Grid.new(:fields => [ - # :email, - # {"Full Name" => proc{|r| [r.first_name, r.last_name].join(' ')}}, - # {"Roles" => {:value => :roles, :type => :collection}} - # ]) - # - # Is equivalent to: - # g = Grid.new - # g.fields << :email - # g.fields << {"Full Name" => proc{|r| [r.first_name, r.last_name].join(' ')}, - # g.fields << {"Roles" => {:value => :roles, :type => :collection}} - def fields=(args) - args.each do |field| - self.fields << field - end - end - - def fields - @fields ||= Presenting::FieldSet.new(Field, :name, :value) - end - - def colspan - @colspan ||= fields.size + (record_links.empty? ? 0 : 1) - end - - def iname; :grid end - - class Field < Presenting::Attribute - # Defines how this field sorts. This means two things: - # 1. whether it sorts - # 2. what name it uses to sort - # - # Examples: - # - # # The field is sortable and assumes the sort_name of "first_name". - # # This is the default. - # Field.new(:sortable => true, :name => "First Name") - # - # # The field is sortable and assumes the sort_name of "first". - # Field.new(:sortable => 'first', :name => 'First Name') - # - # # The field is unsortable. - # Field.new(:sortable => false) - def sortable=(val) - @sort_name = case val - when TrueClass: self.id - when FalseClass, NilClass: nil - else val.to_s - end - end - - # if the field is sortable at all - def sortable? - self.sortable = Presenting::Defaults.grid_is_sortable unless defined? @sort_name - !@sort_name.blank? - end - - attr_reader :sort_name - - # is this field sorted in the given request? - def is_sorted?(request) - @is_sorted ||= if sortable? and sorting = request.query_parameters["sort"] and sorting[sort_name] - sorting[sort_name].to_s.match(/desc/i) ? 'desc' : 'asc' - else - false - end - end - - # for the view -- modifies the current request such that it would sort this field. - def sorted_url(request) - if current_direction = is_sorted?(request) - next_direction = current_direction == 'desc' ? 'asc' : 'desc' - else - next_direction = 'desc' - end - request.path + '?' + request.query_parameters.merge("sort" => {sort_name => next_direction}).to_param - end - - ## - ## Planned - ## - - # TODO: discover "type" from data class (ActiveRecord) if available - # TODO: decorate a Hash object so type is specifiable there as well - # PLAN: type should determine how a field renders. custom types for custom renders. this should be the second option to present(). - # attr_accessor :type - - # PLAN: a field's description would appear in the header column, perhaps only visibly in a tooltip - # attr_accessor :description - - # PLAN: any field may be linked. this would happen after :value and :type. - # attr_accessor :link - end - - # Links are an area where I almost made the mistake of too much configuration. Presentations are configured in the view, - # and all of the view helpers are available. When I looked at the (simple) configuration I was building and realized that - # I could just as easily take the result of link_to, well, I felt a little silly. - # - # Compare: - # - # @grid.links = [ - # {:name => 'Foo', :url => foo_path, :class => 'foo'} - # ] - # - # vs: - # - # @grid.links = [ - # link_to('Foo', foo_path, :class => 'foo') - # ] - # - # Not only is the second example (the supported example, by the way) shorter and cleaner, it encourages the developer - # to stay in touch with the Rails internals and therefore discourages a configuration-heavy mindset. - def links=(set) - set.compact.each do |link| - raise ArgumentError, "Links must be strings, such as the output of link_to()." unless link.is_a?(String) - links << link - end - end - def links - @links ||= [] - end - - # Like links, except the link will appear for each record. This means that the link must be a block that accepts the - # record as its argument. For example: - # - # @grid.record_links = [ - # proc{|record| link_to("Foo", foo_path(record), :class => 'foo') } - # ] - # - def record_links=(set) - set.compact.each do |link| - raise ArgumentError, "Record links must be blocks that accept the record as an argument." unless link.respond_to?(:call) and link.arity == 1 - record_links << link - end - end - def record_links - @record_links ||= [] - end - - def paginate? - defined? WillPaginate and (presentable.is_a? WillPaginate::Collection or presentable.respond_to?(:total_entries)) - end - end -end +module Presentation + # TODO: ability to render a hash + # TODO: custom css classes for rows and/or cells + # TODO: document or complain for required options -- id and fields + # TODO: make fields= accept an ActiveRecord::Base.columns array for a default field set + class Grid < Base + # The id for this presentation. Required. + attr_accessor :id + + # The display title for this presentation. Will default based on the id. + attr_writer :title + def title + @title ||= self.id.titleize + end + + # Paradigm Example: + # Grid.new(:fields => [ + # :email, + # {"Full Name" => proc{|r| [r.first_name, r.last_name].join(' ')}}, + # {"Roles" => {:value => :roles, :type => :collection}} + # ]) + # + # Is equivalent to: + # g = Grid.new + # g.fields << :email + # g.fields << {"Full Name" => proc{|r| [r.first_name, r.last_name].join(' ')}, + # g.fields << {"Roles" => {:value => :roles, :type => :collection}} + def fields=(args) + args.each do |field| + self.fields << field + end + end + + def fields + @fields ||= Presenting::FieldSet.new(Field, :name, :value) + end + + def colspan + @colspan ||= fields.size + (record_links.empty? ? 0 : 1) + end + + def iname; :grid end + + class Field < Presenting::Attribute + # Defines how this field sorts. This means two things: + # 1. whether it sorts + # 2. what name it uses to sort + # + # Examples: + # + # # The field is sortable and assumes the sort_name of "first_name". + # # This is the default. + # Field.new(:sortable => true, :name => "First Name") + # + # # The field is sortable and assumes the sort_name of "first". + # Field.new(:sortable => 'first', :name => 'First Name') + # + # # The field is unsortable. + # Field.new(:sortable => false) + def sortable=(val) + @sort_name = case val + when TrueClass, FalseClass, NilClass + val + else + val.to_s + end + end + + # if the field is sortable at all + def sortable? + self.sortable = Presenting::Defaults.grid_is_sortable unless defined? @sort_name + self.sortable = self.id if @sort_name == true + !@sort_name.blank? + end + + attr_reader :sort_name + + # is this field sorted in the given request? + def is_sorted?(request) + @is_sorted ||= if sortable? and sorting = request.query_parameters["sort"] and sorting[sort_name] + sorting[sort_name].to_s.match(/desc/i) ? 'desc' : 'asc' + else + false + end + end + + # for the view -- modifies the current request such that it would sort this field. + def sorted_url(request) + if current_direction = is_sorted?(request) + next_direction = current_direction == 'desc' ? 'asc' : 'desc' + else + next_direction = 'desc' + end + request.path + '?' + request.query_parameters.merge("sort" => {sort_name => next_direction}).to_param + end + + ## + ## Planned + ## + + # TODO: discover "type" from data class (ActiveRecord) if available + # TODO: decorate a Hash object so type is specifiable there as well + # PLAN: type should determine how a field renders. custom types for custom renders. this should be the second option to present(). + # attr_accessor :type + + # PLAN: a field's description would appear in the header column, perhaps only visibly in a tooltip + # attr_accessor :description + + # PLAN: any field may be linked. this would happen after :value and :type. + # attr_accessor :link + end + + # Links are an area where I almost made the mistake of too much configuration. Presentations are configured in the view, + # and all of the view helpers are available. When I looked at the (simple) configuration I was building and realized that + # I could just as easily take the result of link_to, well, I felt a little silly. + # + # Compare: + # + # @grid.links = [ + # {:name => 'Foo', :url => foo_path, :class => 'foo'} + # ] + # + # vs: + # + # @grid.links = [ + # link_to('Foo', foo_path, :class => 'foo') + # ] + # + # Not only is the second example (the supported example, by the way) shorter and cleaner, it encourages the developer + # to stay in touch with the Rails internals and therefore discourages a configuration-heavy mindset. + def links=(set) + set.compact.each do |link| + raise ArgumentError, "Links must be strings, such as the output of link_to()." unless link.is_a?(String) + links << link + end + end + def links + @links ||= [] + end + + # Like links, except the link will appear for each record. This means that the link must be a block that accepts the + # record as its argument. For example: + # + # @grid.record_links = [ + # proc{|record| link_to("Foo", foo_path(record), :class => 'foo') } + # ] + # + def record_links=(set) + set.compact.each do |link| + raise ArgumentError, "Record links must be blocks that accept the record as an argument." unless link.respond_to?(:call) and link.arity == 1 + record_links << link + end + end + def record_links + @record_links ||= [] + end + + def paginate? + defined? WillPaginate and (presentable.is_a? WillPaginate::Collection or presentable.respond_to?(:total_entries)) + end + end +end