rear 0.2.0
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.
- checksums.yaml +15 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +20 -0
- data/LICENSE +19 -0
- data/README.md +101 -0
- data/Rakefile +79 -0
- data/assets/api.js +307 -0
- data/assets/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css +8 -0
- data/assets/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js +26 -0
- data/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/assets/bootstrap/css/bootstrap.min.css +9 -0
- data/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/assets/bootstrap/img/glyphicons-halflings.png +0 -0
- data/assets/bootstrap/js/bootstrap.min.js +6 -0
- data/assets/jquery.js +5 -0
- data/assets/noty/jquery.noty.js +520 -0
- data/assets/noty/layouts/top.js +34 -0
- data/assets/noty/layouts/topRight.js +43 -0
- data/assets/noty/promise.js +432 -0
- data/assets/noty/themes/default.js +156 -0
- data/assets/select2-bootstrap.css +86 -0
- data/assets/select2/select2-spinner.gif +0 -0
- data/assets/select2/select2.css +652 -0
- data/assets/select2/select2.min.js +22 -0
- data/assets/select2/select2.png +0 -0
- data/assets/select2/select2x2.png +0 -0
- data/assets/ui.css +75 -0
- data/assets/xhr.js +4 -0
- data/bin/rear +65 -0
- data/docs/Assocs.md +100 -0
- data/docs/Columns.md +404 -0
- data/docs/Deploy.md +62 -0
- data/docs/FileManager.md +75 -0
- data/docs/Filters.md +341 -0
- data/docs/Setup.md +201 -0
- data/lib/rear.rb +13 -0
- data/lib/rear/actions.rb +98 -0
- data/lib/rear/constants.rb +61 -0
- data/lib/rear/controller_setup.rb +249 -0
- data/lib/rear/helpers.rb +17 -0
- data/lib/rear/helpers/class.rb +46 -0
- data/lib/rear/helpers/columns.rb +68 -0
- data/lib/rear/helpers/filters.rb +147 -0
- data/lib/rear/helpers/generic.rb +73 -0
- data/lib/rear/helpers/order.rb +47 -0
- data/lib/rear/helpers/pager.rb +35 -0
- data/lib/rear/helpers/render.rb +37 -0
- data/lib/rear/home_controller.rb +10 -0
- data/lib/rear/input.rb +341 -0
- data/lib/rear/orm.rb +73 -0
- data/lib/rear/rear.rb +74 -0
- data/lib/rear/setup.rb +9 -0
- data/lib/rear/setup/associations.rb +33 -0
- data/lib/rear/setup/columns.rb +109 -0
- data/lib/rear/setup/filters.rb +314 -0
- data/lib/rear/setup/generic.rb +59 -0
- data/lib/rear/setup/menu.rb +39 -0
- data/lib/rear/templates/editor/ace.slim +7 -0
- data/lib/rear/templates/editor/assocs.slim +10 -0
- data/lib/rear/templates/editor/boolean.slim +5 -0
- data/lib/rear/templates/editor/bulk_edit.slim +75 -0
- data/lib/rear/templates/editor/checkbox.slim +5 -0
- data/lib/rear/templates/editor/ckeditor.slim +7 -0
- data/lib/rear/templates/editor/date.slim +9 -0
- data/lib/rear/templates/editor/datetime.slim +9 -0
- data/lib/rear/templates/editor/layout.slim +103 -0
- data/lib/rear/templates/editor/password.slim +1 -0
- data/lib/rear/templates/editor/radio.slim +5 -0
- data/lib/rear/templates/editor/select.slim +3 -0
- data/lib/rear/templates/editor/string.slim +1 -0
- data/lib/rear/templates/editor/text.slim +1 -0
- data/lib/rear/templates/editor/time.slim +9 -0
- data/lib/rear/templates/error.slim +36 -0
- data/lib/rear/templates/filters/boolean.slim +10 -0
- data/lib/rear/templates/filters/checkbox.slim +15 -0
- data/lib/rear/templates/filters/date.slim +10 -0
- data/lib/rear/templates/filters/datetime.slim +10 -0
- data/lib/rear/templates/filters/layout.slim +26 -0
- data/lib/rear/templates/filters/radio.slim +14 -0
- data/lib/rear/templates/filters/select.slim +9 -0
- data/lib/rear/templates/filters/string.slim +3 -0
- data/lib/rear/templates/filters/text.slim +3 -0
- data/lib/rear/templates/filters/time.slim +10 -0
- data/lib/rear/templates/home.slim +0 -0
- data/lib/rear/templates/layout.slim +78 -0
- data/lib/rear/templates/pager.slim +22 -0
- data/lib/rear/templates/pane/ace.slim +2 -0
- data/lib/rear/templates/pane/assocs.slim +62 -0
- data/lib/rear/templates/pane/boolean.slim +2 -0
- data/lib/rear/templates/pane/checkbox.slim +5 -0
- data/lib/rear/templates/pane/ckeditor.slim +2 -0
- data/lib/rear/templates/pane/date.slim +2 -0
- data/lib/rear/templates/pane/datetime.slim +2 -0
- data/lib/rear/templates/pane/layout.slim +111 -0
- data/lib/rear/templates/pane/password.slim +2 -0
- data/lib/rear/templates/pane/quickview.slim +21 -0
- data/lib/rear/templates/pane/radio.slim +5 -0
- data/lib/rear/templates/pane/select.slim +5 -0
- data/lib/rear/templates/pane/string.slim +2 -0
- data/lib/rear/templates/pane/text.slim +2 -0
- data/lib/rear/templates/pane/time.slim +2 -0
- data/lib/rear/utils.rb +288 -0
- data/rear.gemspec +27 -0
- data/test/helpers.rb +33 -0
- data/test/models/ar.rb +52 -0
- data/test/models/dm.rb +53 -0
- data/test/models/sq.rb +58 -0
- data/test/setup.rb +4 -0
- data/test/templates/adhoc/book/editor/name.slim +1 -0
- data/test/templates/adhoc/book/editor/string.slim +1 -0
- data/test/templates/adhoc/book/pane/name.slim +1 -0
- data/test/templates/adhoc/book/pane/string.slim +1 -0
- data/test/templates/shared/shared-templates/editor/string.slim +1 -0
- data/test/templates/shared/shared-templates/pane/string.slim +1 -0
- data/test/test__assocs.rb +249 -0
- data/test/test__columns.rb +269 -0
- data/test/test__crud.rb +81 -0
- data/test/test__custom_templates.rb +147 -0
- data/test/test__filters.rb +228 -0
- metadata +220 -0
data/lib/rear/setup.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module RearSetup
|
|
2
|
+
|
|
3
|
+
# make some assocs readonly.
|
|
4
|
+
# this is a cosmetic measure - frontend just wont let user modify them
|
|
5
|
+
# but the API for their manipulation will still work
|
|
6
|
+
def readonly_assocs *assocs
|
|
7
|
+
(@__rear__readonly_assocs ||= []).concat(assocs) if assocs.any?
|
|
8
|
+
(@__rear__readonly_assocs || [])
|
|
9
|
+
end
|
|
10
|
+
alias readonly_assoc readonly_assocs
|
|
11
|
+
|
|
12
|
+
# ignore some assocs.
|
|
13
|
+
# this is a cosmetic measure - assocs just wont be displayed on frontend
|
|
14
|
+
# but the API for their manipulation will still work
|
|
15
|
+
def ignored_assocs *assocs
|
|
16
|
+
(@__rear__ignored_assocs ||= []).concat(assocs) if assocs.any?
|
|
17
|
+
(@__rear__ignored_assocs || [])
|
|
18
|
+
end
|
|
19
|
+
alias ignored_assoc ignored_assocs
|
|
20
|
+
alias ignore_assocs ignored_assocs
|
|
21
|
+
alias ignore_assoc ignored_assocs
|
|
22
|
+
|
|
23
|
+
# when rendering some model in a "remote" association pane,
|
|
24
|
+
# all columns of current model will be displayed.
|
|
25
|
+
#
|
|
26
|
+
# `assoc_columns` allow to set a list of "remotely" displayed columns.
|
|
27
|
+
#
|
|
28
|
+
def assoc_columns *columns
|
|
29
|
+
@__rear__assoc_columns = columns if columns.any?
|
|
30
|
+
@__rear__assoc_columns
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module RearSetup
|
|
2
|
+
|
|
3
|
+
# add new column or override automatically added one
|
|
4
|
+
#
|
|
5
|
+
# @param [Symbol] name
|
|
6
|
+
# @param [Symbol] type one of [:string, :text, :date, :time, :datetime, :boolean]
|
|
7
|
+
# default: :string
|
|
8
|
+
# @param [Hash] opts_and_or_html_attrs
|
|
9
|
+
# @option opts_and_or_html_attrs :pane
|
|
10
|
+
# when set to false the column wont be displayed on pane pages
|
|
11
|
+
# @option opts_and_or_html_attrs :editor
|
|
12
|
+
# when set to false the column wont be displayed on editor pages
|
|
13
|
+
# @option opts_and_or_html_attrs :label
|
|
14
|
+
# @option opts_and_or_html_attrs :readonly
|
|
15
|
+
# @option opts_and_or_html_attrs :disabled
|
|
16
|
+
# @option opts_and_or_html_attrs :multiple
|
|
17
|
+
# @option opts_and_or_html_attrs any attributes to be added to HTML tag
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# input :name
|
|
21
|
+
# # => <input type="text" value="...">
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# input :name, :style => "width: 100%;"
|
|
25
|
+
# # => <input style="width: 100%;" type="text" ...>
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# input :name, :text, :cols => 40
|
|
29
|
+
# # => <textarea cols="40" ...>...</textarea>
|
|
30
|
+
#
|
|
31
|
+
# @example display author name only on pane pages
|
|
32
|
+
# input(:author_id) { disable :editor }
|
|
33
|
+
#
|
|
34
|
+
# @example Ace Editor
|
|
35
|
+
# input :content, :ace
|
|
36
|
+
#
|
|
37
|
+
# @example CKEditor
|
|
38
|
+
# input :content, :ckeditor
|
|
39
|
+
#
|
|
40
|
+
def input name, type = nil, opts_and_or_html_attrs = {}, &proc
|
|
41
|
+
|
|
42
|
+
type.is_a?(Hash) && (opts_and_or_html_attrs = type) && (type = nil)
|
|
43
|
+
opts_and_or_html_attrs[:row] = opts_and_or_html_attrs[:row] ?
|
|
44
|
+
opts_and_or_html_attrs[:row].to_s : @__rear__row
|
|
45
|
+
|
|
46
|
+
existing_column = nil
|
|
47
|
+
columns.each_with_index {|c,i| c && c.first == name && existing_column = [c,i]}
|
|
48
|
+
column = existing_column ? Array.new(existing_column.first) : []
|
|
49
|
+
|
|
50
|
+
column[0] = name
|
|
51
|
+
column[1] = type ? type.to_s.downcase.to_sym : column[1] || COLUMNS__DEFAULT_TYPE
|
|
52
|
+
column[2] = (column[2]||{}).merge(opts_and_or_html_attrs).freeze
|
|
53
|
+
column[3] = proc
|
|
54
|
+
column.freeze
|
|
55
|
+
|
|
56
|
+
existing_column ?
|
|
57
|
+
columns[existing_column.last] = column :
|
|
58
|
+
columns << column
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# reset any automatically(or manually) added columns
|
|
62
|
+
def reset_columns!
|
|
63
|
+
@__rear__columns = {}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# display multiple columns in a row(on editor)
|
|
67
|
+
#
|
|
68
|
+
# @example using a block
|
|
69
|
+
#
|
|
70
|
+
# row :Location do
|
|
71
|
+
# column :country
|
|
72
|
+
# column :state
|
|
73
|
+
# column :city
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# @example without a block
|
|
77
|
+
#
|
|
78
|
+
# column :country, :row => :Location
|
|
79
|
+
# column :state, :row => :Location
|
|
80
|
+
# column :city, :row => :Location
|
|
81
|
+
#
|
|
82
|
+
def row label = nil, &proc
|
|
83
|
+
# explicit labels will be strings and implicit ones will be numbers
|
|
84
|
+
# as a way to distinguish them when rendering templates
|
|
85
|
+
@__rear__row = label ? label.to_s : (Time.now.to_f + rand)
|
|
86
|
+
self.instance_exec(&proc) if proc
|
|
87
|
+
@__rear__row = nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# set HTML attributes to be used on all columns on both pane and editor pages
|
|
91
|
+
def html_attrs attrs = {}
|
|
92
|
+
@__rear__html_attrs = attrs if attrs.any? && @__rear__html_attrs.nil?
|
|
93
|
+
@__rear__html_attrs || {}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# set HTML attributes to be used on all columns only on pane pages.
|
|
97
|
+
# @note will override any attrs set via `html_attrs`
|
|
98
|
+
def pane_attrs attrs = {}
|
|
99
|
+
@__rear__pane_attrs = attrs if attrs.any? && @__rear__pane_attrs.nil?
|
|
100
|
+
@__rear__pane_attrs || html_attrs
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# set HTML attributes to be used on all columns only on editor pages.
|
|
104
|
+
# @note will override any attrs set via `html_attrs`
|
|
105
|
+
def editor_attrs attrs = {}
|
|
106
|
+
@__rear__editor_attrs = attrs if attrs.any? && @__rear__editor_attrs.nil?
|
|
107
|
+
@__rear__editor_attrs || html_attrs
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
module RearSetup
|
|
2
|
+
|
|
3
|
+
# add a filter.
|
|
4
|
+
#
|
|
5
|
+
# by default a text filter will be rendered.
|
|
6
|
+
# to define filters of another type, pass desired type as a Symbol via second argument.
|
|
7
|
+
#
|
|
8
|
+
# acceptable types:
|
|
9
|
+
# - :string/:text
|
|
10
|
+
# - :select
|
|
11
|
+
# - :radio
|
|
12
|
+
# - :checkbox
|
|
13
|
+
# - :date
|
|
14
|
+
# - :datetime
|
|
15
|
+
# - :time
|
|
16
|
+
# - :boolean
|
|
17
|
+
#
|
|
18
|
+
# @note comparison function
|
|
19
|
+
# :text/:string filters will use :like comparison function by default:
|
|
20
|
+
# ... WHERE name LIKE '%VALUE%' ...
|
|
21
|
+
#
|
|
22
|
+
# :checkbox filters will use :in comparison function by default:
|
|
23
|
+
# ... WHERE column IN ('VALUE1', 'VALUE2') ...
|
|
24
|
+
# if you use a custom cmp function with a :checkbox filter,
|
|
25
|
+
# filter's column will be compared to each selected value:
|
|
26
|
+
# ... WHERE (column LIKE '%VALUE1%' OR column LIKE '%VALUE2%') ...
|
|
27
|
+
#
|
|
28
|
+
# any other types will use :eql comparison function by default:
|
|
29
|
+
# "... WHERE created_at = 'VALUE' ...
|
|
30
|
+
#
|
|
31
|
+
# to use a non-default comparison function, set it via :cmp option:
|
|
32
|
+
# `filter :name, :cmp => :eql`
|
|
33
|
+
#
|
|
34
|
+
# available comparison functions:
|
|
35
|
+
# - :eql # equal
|
|
36
|
+
# - :not # not equal
|
|
37
|
+
# - :gt # greater than
|
|
38
|
+
# - :gte # greater than or equal
|
|
39
|
+
# - :lt # less than
|
|
40
|
+
# - :lte # less than or equal
|
|
41
|
+
# - :like # - column LIKE '%VALUE%'
|
|
42
|
+
# - :unlike # - column NOT LIKE '%VALUE%'
|
|
43
|
+
# - :_like # match beginning of line - column LIKE 'VALUE%'
|
|
44
|
+
# - :_unlike # - column NOT LIKE 'VALUE%'
|
|
45
|
+
# - :like_ # match end of line - column LIKE '%VALUE'
|
|
46
|
+
# - :unlike_ # - column NOT LIKE '%VALUE'
|
|
47
|
+
# - :_like_ # exact match - column LIKE 'VALUE'
|
|
48
|
+
# - :_unlike_ # - column NOT LIKE 'VALUE'
|
|
49
|
+
#
|
|
50
|
+
# @note if type not given,
|
|
51
|
+
# Rear will use the type of the column with same name, if any.
|
|
52
|
+
# if no column found, it will use :text
|
|
53
|
+
#
|
|
54
|
+
# @note :radio, :checkbox and :select filters requires a block to run.
|
|
55
|
+
# block should return an Array or a Hash.
|
|
56
|
+
# use an Array when stored keys are the same as displayed values.
|
|
57
|
+
# use a Hash when stored keys are different.
|
|
58
|
+
# Important! if no block given, Rear will search for a column
|
|
59
|
+
# with same name and type and inherit options from there.
|
|
60
|
+
# so if you have say a :checkbox column named :colors with defined options,
|
|
61
|
+
# you only need to do `filter :colors`, without specifying type and options.
|
|
62
|
+
# type and options will be inherited from earlier defined column.
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
#
|
|
66
|
+
# class Page < ActiveRecord::Base
|
|
67
|
+
# # ...
|
|
68
|
+
# include Rear
|
|
69
|
+
# rear do
|
|
70
|
+
#
|
|
71
|
+
# # text filter using :like comparison function
|
|
72
|
+
# filter :name
|
|
73
|
+
#
|
|
74
|
+
# # text filter using :eql comparison function
|
|
75
|
+
# filter :name, :cmp => :eql
|
|
76
|
+
#
|
|
77
|
+
# # date filter using :eql comparison function
|
|
78
|
+
# filter :created_at, :date
|
|
79
|
+
#
|
|
80
|
+
# # date filter using :gte comparison function
|
|
81
|
+
# filter :created_at, :date, :cmp => :gte
|
|
82
|
+
#
|
|
83
|
+
# # dropdown filter using :eql comparison function
|
|
84
|
+
# filter :color, :select do
|
|
85
|
+
# ['Red', 'Green', 'Blue']
|
|
86
|
+
# end
|
|
87
|
+
#
|
|
88
|
+
# # dropdown filter using :like comparison function
|
|
89
|
+
# filter :color, :select, :cmp => :like do
|
|
90
|
+
# ['Red', 'Green', 'Blue']
|
|
91
|
+
# end
|
|
92
|
+
# end
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# @example :radio filter using Hash
|
|
96
|
+
#
|
|
97
|
+
# rear do
|
|
98
|
+
# filter :color, :radio do
|
|
99
|
+
# {'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'}
|
|
100
|
+
# end
|
|
101
|
+
# end
|
|
102
|
+
#
|
|
103
|
+
# @example inheriting type and options from a earlier defined column
|
|
104
|
+
#
|
|
105
|
+
# rear do
|
|
106
|
+
# column :colors, :checkbox do
|
|
107
|
+
# options 'Red', 'Green', 'Blue'
|
|
108
|
+
# end
|
|
109
|
+
#
|
|
110
|
+
# filter :colors # type and options inherited from :colors column
|
|
111
|
+
# end
|
|
112
|
+
#
|
|
113
|
+
# @param [Symbol] column
|
|
114
|
+
# @param [Symbol] type
|
|
115
|
+
# @param [Hash] opts_and_or_html_attrs
|
|
116
|
+
# @options opts_and_or_html_attrs :cmp comparison function
|
|
117
|
+
# @options opts_and_or_html_attrs :label
|
|
118
|
+
# @param [Proc] options block used on :select, :radio and :checkbox filters
|
|
119
|
+
# should return Array or Hash.
|
|
120
|
+
#
|
|
121
|
+
def filter column, type = nil, opts_and_or_html_attrs = {}, &proc
|
|
122
|
+
|
|
123
|
+
opts = (opts_and_or_html_attrs||{}).dup
|
|
124
|
+
type.is_a?(Hash) && (opts = type.dup) && (type = nil) && (opts_and_or_html_attrs = nil)
|
|
125
|
+
matching_column = columns.find {|c| c && c.first == column}
|
|
126
|
+
|
|
127
|
+
# if no type given, inheriting it from a column with same name, if any.
|
|
128
|
+
type ||= (matching_column||[])[1]
|
|
129
|
+
type = FILTERS__DEFAULT_TYPE unless FILTERS__HANDLED_TYPES.include?(type)
|
|
130
|
+
|
|
131
|
+
# if filter is of :select, :radio or :checkbox type and no options block given,
|
|
132
|
+
# inheriting it from a column with same name, if any.
|
|
133
|
+
if proc.nil? && matching_column && matching_column[1] == type
|
|
134
|
+
mci = RearInput.new(matching_column[0], type, &matching_column[3])
|
|
135
|
+
mci.optioned? && proc = lambda { mci.options }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# using defaults if no comparison function given
|
|
139
|
+
unless cmp = opts.delete(:cmp)
|
|
140
|
+
cmp = case type
|
|
141
|
+
when :text, :string
|
|
142
|
+
:like
|
|
143
|
+
when :checkbox
|
|
144
|
+
:in
|
|
145
|
+
else
|
|
146
|
+
:eql
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
unless label = opts.delete(:label)
|
|
151
|
+
label = column.to_s
|
|
152
|
+
label << '?' if type == :boolean
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
(filters[column.to_sym] ||= {})[cmp] = {
|
|
156
|
+
template: 'filters/%s.slim' % type,
|
|
157
|
+
type: type,
|
|
158
|
+
label: label.freeze,
|
|
159
|
+
decorative?: opts.delete(:decorative?),
|
|
160
|
+
attrs: opts.freeze,
|
|
161
|
+
proc: proc
|
|
162
|
+
}.freeze
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# sometimes you need to filter by some value that has too much options.
|
|
166
|
+
# For ex. you want to filter pages by author and there are about 1000 authors in db.
|
|
167
|
+
# displaying all authors within a single dropdown filter is kinda cumbersome.
|
|
168
|
+
# we need to somehow narrow them down.
|
|
169
|
+
# decorative filters allow to do this with easy.
|
|
170
|
+
# in our case, we do not display the authors until a letter selected.
|
|
171
|
+
#
|
|
172
|
+
# @example
|
|
173
|
+
# class Pages < E
|
|
174
|
+
# include Rear
|
|
175
|
+
# model PageModel
|
|
176
|
+
#
|
|
177
|
+
# decorative_filter :letter, :select do
|
|
178
|
+
# ('A'..'Z').to_a
|
|
179
|
+
# end
|
|
180
|
+
#
|
|
181
|
+
# filter :author_id, :select do
|
|
182
|
+
# if letter = filter?(:letter) # use here the name of decorative filter
|
|
183
|
+
# authors = {}
|
|
184
|
+
# AuthorModel.all(:name.like => "%#{letter}%").each |a|
|
|
185
|
+
# authors[a.id] = a.name
|
|
186
|
+
# end
|
|
187
|
+
# authors
|
|
188
|
+
# else
|
|
189
|
+
# {"" => "Select a letter please"}
|
|
190
|
+
# end
|
|
191
|
+
# end
|
|
192
|
+
# end
|
|
193
|
+
#
|
|
194
|
+
# @note
|
|
195
|
+
# decorative filters will not actually query the db, so you can name them as you want.
|
|
196
|
+
#
|
|
197
|
+
# @note
|
|
198
|
+
# decorative filters does not support custom comparison functions
|
|
199
|
+
#
|
|
200
|
+
def decorative_filter *args, &proc
|
|
201
|
+
html_attrs = args.last.is_a?(Hash) ? Hash[args.pop] : {}
|
|
202
|
+
setup = {decorative?: true, cmp: FILTERS__DECORATIVE_CMP}
|
|
203
|
+
filter *args << html_attrs.merge(setup), &proc
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# @example Array with default comparison function
|
|
207
|
+
# quick_filter :color, 'Red', 'Green', 'Blue'
|
|
208
|
+
# ... WHERE color = '[Red|Green|Blue]'
|
|
209
|
+
#
|
|
210
|
+
# @example Array with custom comparison function
|
|
211
|
+
# quick_filter :color, 'Red', 'Green', 'Blue', :cmp => :like
|
|
212
|
+
# ... WHERE color LIKE '[Red|Green|Blue]'
|
|
213
|
+
#
|
|
214
|
+
# @example Hash with default comparison function
|
|
215
|
+
# quick_filter :color, 'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'
|
|
216
|
+
# ... WHERE color = '[r|g|b]'
|
|
217
|
+
#
|
|
218
|
+
# @example Hash with custom comparison function
|
|
219
|
+
# quick_filter :color, :cmp => :like, 'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'
|
|
220
|
+
# ... WHERE color LIKE '%[r|g|b]%'
|
|
221
|
+
#
|
|
222
|
+
# @example Hash with comparison function defined per filter
|
|
223
|
+
# quick_filter :color, [:like, 'r'] => 'Red', 'g' => 'Green', 'b' => 'Blue'
|
|
224
|
+
# on Red
|
|
225
|
+
# ... WHERE color LIKE '%r%'
|
|
226
|
+
# on Green or Blue
|
|
227
|
+
# ... WHERE color = '[g|b]'
|
|
228
|
+
#
|
|
229
|
+
def quick_filter column, *args
|
|
230
|
+
|
|
231
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
232
|
+
cmp = options.delete(:cmp) || :eql
|
|
233
|
+
query_formats = FILTERS__QUERY_MAP.call(orm)
|
|
234
|
+
if query_format = query_formats[cmp]
|
|
235
|
+
options = Hash[options.map do |k,v|
|
|
236
|
+
[
|
|
237
|
+
v.to_s,
|
|
238
|
+
k.is_a?(Array) ? [query_formats[k.first], k.last] : [query_format, k]
|
|
239
|
+
]
|
|
240
|
+
end]
|
|
241
|
+
|
|
242
|
+
# if options provided as arguments, adding them to options Hash
|
|
243
|
+
args.each {|a| options[a.to_s] = [query_format, a.to_s] }
|
|
244
|
+
|
|
245
|
+
# if no options given,
|
|
246
|
+
# inheriting them from a column with same name, if any.
|
|
247
|
+
if options.empty? && mc = columns.find {|c| c && c.first == column}
|
|
248
|
+
mci = RearInput.new(mc[0], mc[1], &mc[3])
|
|
249
|
+
mci.optioned? && mci.options.each_pair do |k,v|
|
|
250
|
+
options[v.to_s] = [query_format, k]
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
quick_filters[column.to_sym] = options
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# Used when you need fine-tuned control over displayed items.
|
|
260
|
+
# Internal filters wont render any inputs, they will work under the hood.
|
|
261
|
+
# `internal_filter` requires a block that should return a list of matching items.
|
|
262
|
+
|
|
263
|
+
# @example Display only articles newer than 2010
|
|
264
|
+
|
|
265
|
+
# class Article
|
|
266
|
+
# include DataMapper::Resource
|
|
267
|
+
|
|
268
|
+
# property :id, Serial
|
|
269
|
+
# # ...
|
|
270
|
+
# property :created_at, Date, index: true
|
|
271
|
+
# end
|
|
272
|
+
|
|
273
|
+
# Rear.register Article do
|
|
274
|
+
# # ...
|
|
275
|
+
|
|
276
|
+
# internal_filter do
|
|
277
|
+
# Article.all(:created_at.gt => Date.new(2010))
|
|
278
|
+
# end
|
|
279
|
+
# end
|
|
280
|
+
|
|
281
|
+
# @example Filter articles by category
|
|
282
|
+
|
|
283
|
+
# class Article < ActiveRecord::Base
|
|
284
|
+
# belongs_to :category
|
|
285
|
+
# end
|
|
286
|
+
|
|
287
|
+
# Rear.register Article do
|
|
288
|
+
|
|
289
|
+
# # firstly lets render a decorative filter
|
|
290
|
+
# # that will render a list of categories to choose from
|
|
291
|
+
# decorative_filter :Category do
|
|
292
|
+
# Hash[ Category.all.map {|c| [c.id, c.name]} ]
|
|
293
|
+
# end
|
|
294
|
+
|
|
295
|
+
# # then we using internal_filter
|
|
296
|
+
# # to yield selected category and filter articles
|
|
297
|
+
# internal_filter do
|
|
298
|
+
# if category_id = filter?(:Category)
|
|
299
|
+
# Article.all(category_id: category_id.to_i)
|
|
300
|
+
# end
|
|
301
|
+
# end
|
|
302
|
+
# end
|
|
303
|
+
#
|
|
304
|
+
def internal_filter &proc
|
|
305
|
+
# instance_exec at runtime is expensive enough,
|
|
306
|
+
# so compiling procs into methods at load time.
|
|
307
|
+
chunks = [self.to_s, proc.__id__]
|
|
308
|
+
name = ('__rear__%s__' % chunks.join('_').gsub(/\W/, '_')).to_sym
|
|
309
|
+
define_method name, &proc
|
|
310
|
+
private name
|
|
311
|
+
internal_filters.push(name)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
end
|