presenting 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. data/LICENSE +20 -20
  2. data/README +10 -10
  3. data/Rakefile +22 -22
  4. data/app/assets/javascripts/search.js +13 -13
  5. data/app/assets/stylesheets/details-color.css +7 -7
  6. data/app/assets/stylesheets/details.css +10 -10
  7. data/app/assets/stylesheets/form.css +1 -1
  8. data/app/assets/stylesheets/grid-color.css +71 -71
  9. data/app/assets/stylesheets/grid.css +64 -64
  10. data/app/assets/stylesheets/search-color.css +16 -16
  11. data/app/assets/stylesheets/search.css +45 -45
  12. data/app/controllers/presentation/assets_controller.rb +42 -42
  13. data/app/views/presentations/_details.erb +11 -11
  14. data/app/views/presentations/_field_search.erb +14 -14
  15. data/app/views/presentations/_form.erb +20 -20
  16. data/app/views/presentations/_grid.erb +70 -70
  17. data/app/views/presentations/_search.erb +7 -7
  18. data/lib/presentation/base.rb +31 -31
  19. data/lib/presentation/details.rb +21 -21
  20. data/lib/presentation/field_search.rb +67 -67
  21. data/lib/presentation/form.rb +149 -149
  22. data/lib/presentation/grid.rb +162 -160
  23. data/lib/presentation/search.rb +9 -9
  24. data/lib/presenting/attribute.rb +51 -51
  25. data/lib/presenting/configurable.rb +10 -10
  26. data/lib/presenting/defaults.rb +10 -10
  27. data/lib/presenting/field_set.rb +26 -26
  28. data/lib/presenting/form_helpers.rb +51 -51
  29. data/lib/presenting/helpers.rb +114 -114
  30. data/lib/presenting/sanitize.rb +19 -19
  31. data/lib/presenting/search.rb +185 -185
  32. data/lib/presenting/sorting.rb +87 -87
  33. data/test/attribute_test.rb +61 -61
  34. data/test/configurable_test.rb +20 -20
  35. data/test/details_test.rb +68 -68
  36. data/test/field_search_test.rb +102 -102
  37. data/test/field_set_test.rb +46 -46
  38. data/test/grid_test.rb +246 -239
  39. data/test/helpers_test.rb +72 -72
  40. data/test/presenting_test.rb +15 -15
  41. data/test/r3/Gemfile +7 -7
  42. data/test/r3/Gemfile.lock +86 -82
  43. data/test/r3/config/application.rb +12 -12
  44. data/test/r3/config/boot.rb +6 -6
  45. data/test/r3/config/database.yml +22 -22
  46. data/test/r3/config/environment.rb +5 -5
  47. data/test/r3/config/environments/test.rb +35 -35
  48. data/test/r3/db/test.sqlite3 +0 -0
  49. data/test/r3/log/test.log +336 -0
  50. data/test/r3/public/javascripts/presenting/grid.js +0 -0
  51. data/test/r3/public/javascripts/presenting/search.js +13 -13
  52. data/test/r3/public/stylesheets/presenting/details-color.css +7 -7
  53. data/test/r3/public/stylesheets/presenting/details.css +10 -10
  54. data/test/r3/public/stylesheets/presenting/form.css +1 -1
  55. data/test/r3/public/stylesheets/presenting/grid-color.css +71 -71
  56. data/test/r3/public/stylesheets/presenting/grid.css +64 -64
  57. data/test/r3/public/stylesheets/presenting/search-color.css +16 -16
  58. data/test/r3/public/stylesheets/presenting/search.css +45 -45
  59. data/test/sanitize_test.rb +15 -15
  60. data/test/search_conditions_test.rb +137 -137
  61. data/test/search_test.rb +30 -30
  62. data/test/sorting_test.rb +63 -63
  63. metadata +74 -74
@@ -1,149 +1,149 @@
1
- module Presentation
2
- class Form < Base
3
- # TODO
4
- # field type extra details?
5
- # * text
6
- # * text_area
7
- # * password
8
- # * check_box checked/unchecked values
9
- # * radio (= dropdown) options
10
- # * dropdown (= radio) options
11
- # * multi-select options
12
- # * recordselect url?
13
- # * calendar constraints
14
- # * time constraints
15
- # * date
16
- # * datetime
17
- #
18
- # other
19
- # - example / description / help text
20
- # - nested fields
21
-
22
- # Fields may be grouped. Groups may or may not have names. Here's how:
23
- #
24
- # Presentation::Form.new(:groups => [
25
- # [:a, :b], # creates a nameless group with fields :a and :b
26
- # {"foo" => [:c, :d]} # creates a group named "foo" with fields :c and :d
27
- # ])
28
- #
29
- # Note that if you don't need groups it'll be simpler to just use fields= instead.
30
- def groups
31
- @groups ||= GroupSet.new
32
- end
33
- def groups=(args)
34
- args.each do |group|
35
- groups << group
36
- end
37
- end
38
-
39
- class GroupSet < Array
40
- def <<(val)
41
- if val.is_a? Hash
42
- opts = {:name => val.keys.first, :fields => val.values.first}
43
- else
44
- opts = {:fields => val}
45
- end
46
- super Group.new(opts)
47
- end
48
- end
49
-
50
- class Group
51
- include Presenting::Configurable
52
-
53
- # a completely optional group name
54
- attr_accessor :name
55
-
56
- # the fields in the group
57
- def fields
58
- @fields ||= Presenting::FieldSet.new(Field, :name, :type)
59
- end
60
- def fields=(args)
61
- args.each do |field|
62
- fields << field
63
- end
64
- end
65
- end
66
-
67
- # Used to define fields in a group-less form.
68
- def fields
69
- if groups.empty?
70
- groups << []
71
- end
72
- groups.first.fields
73
- end
74
- def fields=(args)
75
- args.each do |field|
76
- fields << field
77
- end
78
- end
79
-
80
- # The url where the form posts. May be anything that url_for accepts, including
81
- # a set of records.
82
- def url
83
- @url ||= presentable
84
- end
85
- attr_writer :url
86
-
87
- # What method the form should use to post. Should default intelligently enough from
88
- # the presentable. Not sure what use case would require it being set manually.
89
- def method
90
- @method ||= presentable.new_record? ? :post : :put
91
- end
92
- attr_writer :method
93
-
94
- # the text on the submit button
95
- def button
96
- @button ||= presentable.new_record? ? 'Create' : 'Update'
97
- end
98
- attr_writer :button
99
-
100
- # a passthrough for form_for's html. useful for classifying a form for ajax behavior (e.g. :html => {:class => 'ajax'})
101
- attr_accessor :html
102
-
103
- class Field
104
- include Presenting::Configurable
105
-
106
- # the display label of the field
107
- def label
108
- @label ||= name.to_s.titleize
109
- end
110
- attr_writer :label
111
-
112
- # the parameter name of the field
113
- attr_accessor :name
114
-
115
- # where the value for this field comes from.
116
- # - String: a fixed value
117
- # - Symbol: a method on the record (no arguments)
118
- # - Proc: a custom block that accepts the record as an argument
119
- def value
120
- @value ||= name.to_sym
121
- end
122
- attr_writer :value
123
-
124
- def value_from(obj) #:nodoc:
125
- v = case value
126
- when Symbol: obj.send(value)
127
- when String: value
128
- when Proc: value.call(obj)
129
- end
130
- end
131
-
132
- # the widget type for the field. use type_options to pass arguments to the widget.
133
- def type
134
- @type ||= :string
135
- end
136
- attr_writer :type
137
-
138
- # unrestricted options storage for the widget type. this could be a list of options for a select, or extra configuration for a calendar widget.
139
- attr_accessor :type_options
140
- end
141
-
142
- def iname; :form end
143
-
144
- delegate :request_forgery_protection_token, :allow_forgery_protection, :to => :controller
145
- def protect_against_forgery? #:nodoc:
146
- allow_forgery_protection && request_forgery_protection_token
147
- end
148
- end
149
- end
1
+ module Presentation
2
+ class Form < Base
3
+ # TODO
4
+ # field type extra details?
5
+ # * text
6
+ # * text_area
7
+ # * password
8
+ # * check_box checked/unchecked values
9
+ # * radio (= dropdown) options
10
+ # * dropdown (= radio) options
11
+ # * multi-select options
12
+ # * recordselect url?
13
+ # * calendar constraints
14
+ # * time constraints
15
+ # * date
16
+ # * datetime
17
+ #
18
+ # other
19
+ # - example / description / help text
20
+ # - nested fields
21
+
22
+ # Fields may be grouped. Groups may or may not have names. Here's how:
23
+ #
24
+ # Presentation::Form.new(:groups => [
25
+ # [:a, :b], # creates a nameless group with fields :a and :b
26
+ # {"foo" => [:c, :d]} # creates a group named "foo" with fields :c and :d
27
+ # ])
28
+ #
29
+ # Note that if you don't need groups it'll be simpler to just use fields= instead.
30
+ def groups
31
+ @groups ||= GroupSet.new
32
+ end
33
+ def groups=(args)
34
+ args.each do |group|
35
+ groups << group
36
+ end
37
+ end
38
+
39
+ class GroupSet < Array
40
+ def <<(val)
41
+ if val.is_a? Hash
42
+ opts = {:name => val.keys.first, :fields => val.values.first}
43
+ else
44
+ opts = {:fields => val}
45
+ end
46
+ super Group.new(opts)
47
+ end
48
+ end
49
+
50
+ class Group
51
+ include Presenting::Configurable
52
+
53
+ # a completely optional group name
54
+ attr_accessor :name
55
+
56
+ # the fields in the group
57
+ def fields
58
+ @fields ||= Presenting::FieldSet.new(Field, :name, :type)
59
+ end
60
+ def fields=(args)
61
+ args.each do |field|
62
+ fields << field
63
+ end
64
+ end
65
+ end
66
+
67
+ # Used to define fields in a group-less form.
68
+ def fields
69
+ if groups.empty?
70
+ groups << []
71
+ end
72
+ groups.first.fields
73
+ end
74
+ def fields=(args)
75
+ args.each do |field|
76
+ fields << field
77
+ end
78
+ end
79
+
80
+ # The url where the form posts. May be anything that url_for accepts, including
81
+ # a set of records.
82
+ def url
83
+ @url ||= presentable
84
+ end
85
+ attr_writer :url
86
+
87
+ # What method the form should use to post. Should default intelligently enough from
88
+ # the presentable. Not sure what use case would require it being set manually.
89
+ def method
90
+ @method ||= presentable.new_record? ? :post : :put
91
+ end
92
+ attr_writer :method
93
+
94
+ # the text on the submit button
95
+ def button
96
+ @button ||= presentable.new_record? ? 'Create' : 'Update'
97
+ end
98
+ attr_writer :button
99
+
100
+ # a passthrough for form_for's html. useful for classifying a form for ajax behavior (e.g. :html => {:class => 'ajax'})
101
+ attr_accessor :html
102
+
103
+ class Field
104
+ include Presenting::Configurable
105
+
106
+ # the display label of the field
107
+ def label
108
+ @label ||= name.to_s.titleize
109
+ end
110
+ attr_writer :label
111
+
112
+ # the parameter name of the field
113
+ attr_accessor :name
114
+
115
+ # where the value for this field comes from.
116
+ # - String: a fixed value
117
+ # - Symbol: a method on the record (no arguments)
118
+ # - Proc: a custom block that accepts the record as an argument
119
+ def value
120
+ @value ||= name.to_sym
121
+ end
122
+ attr_writer :value
123
+
124
+ def value_from(obj) #:nodoc:
125
+ v = case value
126
+ when Symbol: obj.send(value)
127
+ when String: value
128
+ when Proc: value.call(obj)
129
+ end
130
+ end
131
+
132
+ # the widget type for the field. use type_options to pass arguments to the widget.
133
+ def type
134
+ @type ||= :string
135
+ end
136
+ attr_writer :type
137
+
138
+ # unrestricted options storage for the widget type. this could be a list of options for a select, or extra configuration for a calendar widget.
139
+ attr_accessor :type_options
140
+ end
141
+
142
+ def iname; :form end
143
+
144
+ delegate :request_forgery_protection_token, :allow_forgery_protection, :to => :controller
145
+ def protect_against_forgery? #:nodoc:
146
+ allow_forgery_protection && request_forgery_protection_token
147
+ end
148
+ end
149
+ end
@@ -1,160 +1,162 @@
1
- module Presentation
2
- # TODO: ability to render a hash
3
- # TODO: custom css classes for rows and/or cells
4
- # TODO: document or complain for required options -- id and fields
5
- # TODO: make fields= accept an ActiveRecord::Base.columns array for a default field set
6
- class Grid < Base
7
- # The id for this presentation. Required.
8
- attr_accessor :id
9
-
10
- # The display title for this presentation. Will default based on the id.
11
- attr_writer :title
12
- def title
13
- @title ||= self.id.titleize
14
- end
15
-
16
- # Paradigm Example:
17
- # Grid.new(:fields => [
18
- # :email,
19
- # {"Full Name" => proc{|r| [r.first_name, r.last_name].join(' ')}},
20
- # {"Roles" => {:value => :roles, :type => :collection}}
21
- # ])
22
- #
23
- # Is equivalent to:
24
- # g = Grid.new
25
- # g.fields << :email
26
- # g.fields << {"Full Name" => proc{|r| [r.first_name, r.last_name].join(' ')},
27
- # g.fields << {"Roles" => {:value => :roles, :type => :collection}}
28
- def fields=(args)
29
- args.each do |field|
30
- self.fields << field
31
- end
32
- end
33
-
34
- def fields
35
- @fields ||= Presenting::FieldSet.new(Field, :name, :value)
36
- end
37
-
38
- def colspan
39
- @colspan ||= fields.size + (record_links.empty? ? 0 : 1)
40
- end
41
-
42
- def iname; :grid end
43
-
44
- class Field < Presenting::Attribute
45
- # Defines how this field sorts. This means two things:
46
- # 1. whether it sorts
47
- # 2. what name it uses to sort
48
- #
49
- # Examples:
50
- #
51
- # # The field is sortable and assumes the sort_name of "first_name".
52
- # # This is the default.
53
- # Field.new(:sortable => true, :name => "First Name")
54
- #
55
- # # The field is sortable and assumes the sort_name of "first".
56
- # Field.new(:sortable => 'first', :name => 'First Name')
57
- #
58
- # # The field is unsortable.
59
- # Field.new(:sortable => false)
60
- def sortable=(val)
61
- @sort_name = case val
62
- when TrueClass: self.id
63
- when FalseClass, NilClass: nil
64
- else val.to_s
65
- end
66
- end
67
-
68
- # if the field is sortable at all
69
- def sortable?
70
- self.sortable = Presenting::Defaults.grid_is_sortable unless defined? @sort_name
71
- !@sort_name.blank?
72
- end
73
-
74
- attr_reader :sort_name
75
-
76
- # is this field sorted in the given request?
77
- def is_sorted?(request)
78
- @is_sorted ||= if sortable? and sorting = request.query_parameters["sort"] and sorting[sort_name]
79
- sorting[sort_name].to_s.match(/desc/i) ? 'desc' : 'asc'
80
- else
81
- false
82
- end
83
- end
84
-
85
- # for the view -- modifies the current request such that it would sort this field.
86
- def sorted_url(request)
87
- if current_direction = is_sorted?(request)
88
- next_direction = current_direction == 'desc' ? 'asc' : 'desc'
89
- else
90
- next_direction = 'desc'
91
- end
92
- request.path + '?' + request.query_parameters.merge("sort" => {sort_name => next_direction}).to_param
93
- end
94
-
95
- ##
96
- ## Planned
97
- ##
98
-
99
- # TODO: discover "type" from data class (ActiveRecord) if available
100
- # TODO: decorate a Hash object so type is specifiable there as well
101
- # PLAN: type should determine how a field renders. custom types for custom renders. this should be the second option to present().
102
- # attr_accessor :type
103
-
104
- # PLAN: a field's description would appear in the header column, perhaps only visibly in a tooltip
105
- # attr_accessor :description
106
-
107
- # PLAN: any field may be linked. this would happen after :value and :type.
108
- # attr_accessor :link
109
- end
110
-
111
- # Links are an area where I almost made the mistake of too much configuration. Presentations are configured in the view,
112
- # and all of the view helpers are available. When I looked at the (simple) configuration I was building and realized that
113
- # I could just as easily take the result of link_to, well, I felt a little silly.
114
- #
115
- # Compare:
116
- #
117
- # @grid.links = [
118
- # {:name => 'Foo', :url => foo_path, :class => 'foo'}
119
- # ]
120
- #
121
- # vs:
122
- #
123
- # @grid.links = [
124
- # link_to('Foo', foo_path, :class => 'foo')
125
- # ]
126
- #
127
- # Not only is the second example (the supported example, by the way) shorter and cleaner, it encourages the developer
128
- # to stay in touch with the Rails internals and therefore discourages a configuration-heavy mindset.
129
- def links=(set)
130
- set.compact.each do |link|
131
- raise ArgumentError, "Links must be strings, such as the output of link_to()." unless link.is_a?(String)
132
- links << link
133
- end
134
- end
135
- def links
136
- @links ||= []
137
- end
138
-
139
- # Like links, except the link will appear for each record. This means that the link must be a block that accepts the
140
- # record as its argument. For example:
141
- #
142
- # @grid.record_links = [
143
- # proc{|record| link_to("Foo", foo_path(record), :class => 'foo') }
144
- # ]
145
- #
146
- def record_links=(set)
147
- set.compact.each do |link|
148
- raise ArgumentError, "Record links must be blocks that accept the record as an argument." unless link.respond_to?(:call) and link.arity == 1
149
- record_links << link
150
- end
151
- end
152
- def record_links
153
- @record_links ||= []
154
- end
155
-
156
- def paginate?
157
- defined? WillPaginate and (presentable.is_a? WillPaginate::Collection or presentable.respond_to?(:total_entries))
158
- end
159
- end
160
- end
1
+ module Presentation
2
+ # TODO: ability to render a hash
3
+ # TODO: custom css classes for rows and/or cells
4
+ # TODO: document or complain for required options -- id and fields
5
+ # TODO: make fields= accept an ActiveRecord::Base.columns array for a default field set
6
+ class Grid < Base
7
+ # The id for this presentation. Required.
8
+ attr_accessor :id
9
+
10
+ # The display title for this presentation. Will default based on the id.
11
+ attr_writer :title
12
+ def title
13
+ @title ||= self.id.titleize
14
+ end
15
+
16
+ # Paradigm Example:
17
+ # Grid.new(:fields => [
18
+ # :email,
19
+ # {"Full Name" => proc{|r| [r.first_name, r.last_name].join(' ')}},
20
+ # {"Roles" => {:value => :roles, :type => :collection}}
21
+ # ])
22
+ #
23
+ # Is equivalent to:
24
+ # g = Grid.new
25
+ # g.fields << :email
26
+ # g.fields << {"Full Name" => proc{|r| [r.first_name, r.last_name].join(' ')},
27
+ # g.fields << {"Roles" => {:value => :roles, :type => :collection}}
28
+ def fields=(args)
29
+ args.each do |field|
30
+ self.fields << field
31
+ end
32
+ end
33
+
34
+ def fields
35
+ @fields ||= Presenting::FieldSet.new(Field, :name, :value)
36
+ end
37
+
38
+ def colspan
39
+ @colspan ||= fields.size + (record_links.empty? ? 0 : 1)
40
+ end
41
+
42
+ def iname; :grid end
43
+
44
+ class Field < Presenting::Attribute
45
+ # Defines how this field sorts. This means two things:
46
+ # 1. whether it sorts
47
+ # 2. what name it uses to sort
48
+ #
49
+ # Examples:
50
+ #
51
+ # # The field is sortable and assumes the sort_name of "first_name".
52
+ # # This is the default.
53
+ # Field.new(:sortable => true, :name => "First Name")
54
+ #
55
+ # # The field is sortable and assumes the sort_name of "first".
56
+ # Field.new(:sortable => 'first', :name => 'First Name')
57
+ #
58
+ # # The field is unsortable.
59
+ # Field.new(:sortable => false)
60
+ def sortable=(val)
61
+ @sort_name = case val
62
+ when TrueClass, FalseClass, NilClass
63
+ val
64
+ else
65
+ val.to_s
66
+ end
67
+ end
68
+
69
+ # if the field is sortable at all
70
+ def sortable?
71
+ self.sortable = Presenting::Defaults.grid_is_sortable unless defined? @sort_name
72
+ self.sortable = self.id if @sort_name == true
73
+ !@sort_name.blank?
74
+ end
75
+
76
+ attr_reader :sort_name
77
+
78
+ # is this field sorted in the given request?
79
+ def is_sorted?(request)
80
+ @is_sorted ||= if sortable? and sorting = request.query_parameters["sort"] and sorting[sort_name]
81
+ sorting[sort_name].to_s.match(/desc/i) ? 'desc' : 'asc'
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ # for the view -- modifies the current request such that it would sort this field.
88
+ def sorted_url(request)
89
+ if current_direction = is_sorted?(request)
90
+ next_direction = current_direction == 'desc' ? 'asc' : 'desc'
91
+ else
92
+ next_direction = 'desc'
93
+ end
94
+ request.path + '?' + request.query_parameters.merge("sort" => {sort_name => next_direction}).to_param
95
+ end
96
+
97
+ ##
98
+ ## Planned
99
+ ##
100
+
101
+ # TODO: discover "type" from data class (ActiveRecord) if available
102
+ # TODO: decorate a Hash object so type is specifiable there as well
103
+ # PLAN: type should determine how a field renders. custom types for custom renders. this should be the second option to present().
104
+ # attr_accessor :type
105
+
106
+ # PLAN: a field's description would appear in the header column, perhaps only visibly in a tooltip
107
+ # attr_accessor :description
108
+
109
+ # PLAN: any field may be linked. this would happen after :value and :type.
110
+ # attr_accessor :link
111
+ end
112
+
113
+ # Links are an area where I almost made the mistake of too much configuration. Presentations are configured in the view,
114
+ # and all of the view helpers are available. When I looked at the (simple) configuration I was building and realized that
115
+ # I could just as easily take the result of link_to, well, I felt a little silly.
116
+ #
117
+ # Compare:
118
+ #
119
+ # @grid.links = [
120
+ # {:name => 'Foo', :url => foo_path, :class => 'foo'}
121
+ # ]
122
+ #
123
+ # vs:
124
+ #
125
+ # @grid.links = [
126
+ # link_to('Foo', foo_path, :class => 'foo')
127
+ # ]
128
+ #
129
+ # Not only is the second example (the supported example, by the way) shorter and cleaner, it encourages the developer
130
+ # to stay in touch with the Rails internals and therefore discourages a configuration-heavy mindset.
131
+ def links=(set)
132
+ set.compact.each do |link|
133
+ raise ArgumentError, "Links must be strings, such as the output of link_to()." unless link.is_a?(String)
134
+ links << link
135
+ end
136
+ end
137
+ def links
138
+ @links ||= []
139
+ end
140
+
141
+ # Like links, except the link will appear for each record. This means that the link must be a block that accepts the
142
+ # record as its argument. For example:
143
+ #
144
+ # @grid.record_links = [
145
+ # proc{|record| link_to("Foo", foo_path(record), :class => 'foo') }
146
+ # ]
147
+ #
148
+ def record_links=(set)
149
+ set.compact.each do |link|
150
+ raise ArgumentError, "Record links must be blocks that accept the record as an argument." unless link.respond_to?(:call) and link.arity == 1
151
+ record_links << link
152
+ end
153
+ end
154
+ def record_links
155
+ @record_links ||= []
156
+ end
157
+
158
+ def paginate?
159
+ defined? WillPaginate and (presentable.is_a? WillPaginate::Collection or presentable.respond_to?(:total_entries))
160
+ end
161
+ end
162
+ end
@@ -1,9 +1,9 @@
1
- module Presentation
2
- class Search < Base
3
- def iname; :search end
4
-
5
- def url
6
- request.path + '?' + request.query_parameters.except("search").to_param
7
- end
8
- end
9
- end
1
+ module Presentation
2
+ class Search < Base
3
+ def iname; :search end
4
+
5
+ def url
6
+ request.path + '?' + request.query_parameters.except("search").to_param
7
+ end
8
+ end
9
+ end