presenting 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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