forma 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +40 -0
- data/Rakefile +8 -0
- data/lib/forma/action.rb +45 -0
- data/lib/forma/config.rb +68 -0
- data/lib/forma/field.rb +451 -0
- data/lib/forma/form.rb +257 -0
- data/lib/forma/helpers.rb +139 -0
- data/lib/forma/html.rb +163 -2
- data/lib/forma/railtie.rb +15 -0
- data/lib/forma/table.rb +115 -0
- data/lib/forma/utils.rb +24 -0
- data/lib/forma/version.rb +1 -1
- data/lib/forma.rb +8 -0
- data/spec/config_spec.rb +20 -0
- data/spec/field_spec.rb +3 -0
- data/spec/form_spec.rb +33 -0
- data/spec/helpers_spec.rb +18 -0
- data/spec/html_spec.rb +26 -0
- data/spec/spec_helper.rb +0 -6
- data/test/field_test.rb +63 -0
- data/test/form_test.rb +16 -0
- data/test/test_helper.rb +3 -0
- data/vendor/assets/images/ff-icons.png +0 -0
- data/vendor/assets/javascripts/forma.js +147 -0
- data/vendor/assets/stylesheets/forma-min.css +41 -1
- data/vendor/assets/stylesheets/forma.css +326 -6
- data/vendor/less/forma.less +138 -8
- metadata +28 -9
- data/.rspec +0 -1
- data/lib/forma/html/attributes.rb +0 -104
- data/lib/forma/html/element.rb +0 -74
- data/spec/html/attributes_spec.rb +0 -54
- data/spec/html/element_spec.rb +0 -21
data/lib/forma/form.rb
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Forma
|
3
|
+
# Form.
|
4
|
+
class Form
|
5
|
+
include Forma::Html
|
6
|
+
include Forma::FieldHelper
|
7
|
+
include Forma::WithTitleElement
|
8
|
+
include Forma::Utils
|
9
|
+
attr_accessor :collapsible, :collapsed
|
10
|
+
attr_accessor :icon, :title, :title_actions
|
11
|
+
attr_accessor :model, :parent_field, :edit
|
12
|
+
attr_accessor :model_name
|
13
|
+
|
14
|
+
def initialize(h = {})
|
15
|
+
h = h.symbolize_keys
|
16
|
+
# general
|
17
|
+
@id = h[:id]
|
18
|
+
# @theme = h[:theme] || 'blue'
|
19
|
+
# title properties
|
20
|
+
@title = h[:title]
|
21
|
+
@icon = h[:icon]
|
22
|
+
@collapsible = h[:collapsible]
|
23
|
+
@collapsed = h[:collapsed]
|
24
|
+
# submit options
|
25
|
+
@url = h[:url]
|
26
|
+
@submit = h[:submit] || 'Save'
|
27
|
+
@wait_on_submit = h[:wait_on_submit].blank? ? true : (not not h[:wait_on_submit])
|
28
|
+
@method = h[:method]
|
29
|
+
@auth_token = h[:auth_token]
|
30
|
+
# tabs
|
31
|
+
@tabs = h[:tabs] || []
|
32
|
+
@selected_tab = h[:selected_tab] || 0
|
33
|
+
# model, errors and editing options
|
34
|
+
@model = h[:model]
|
35
|
+
@errors = h[:errors]
|
36
|
+
@edit = h[:edit]
|
37
|
+
@model_name = h[:model_name]
|
38
|
+
# actions
|
39
|
+
@title_actions = h[:title_actions] || []
|
40
|
+
@bottom_actions = h[:bottom_actions] || []
|
41
|
+
@multipart = (not not h[:multipart])
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_html
|
45
|
+
el(
|
46
|
+
'div',
|
47
|
+
attrs: { id: @id, class: [ 'ff-form' ] },
|
48
|
+
children: [
|
49
|
+
title_element,
|
50
|
+
body_element,
|
51
|
+
]
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def submit(txt = nil)
|
56
|
+
@submit = txt if txt.present?
|
57
|
+
@submit
|
58
|
+
end
|
59
|
+
|
60
|
+
def title_action(url, h={})
|
61
|
+
h[:url] = url
|
62
|
+
@title_actions << Action.new(h)
|
63
|
+
end
|
64
|
+
|
65
|
+
def bottom_action(url, h={})
|
66
|
+
h[:url] = url
|
67
|
+
h[:as] = 'button' unless h[:as].present?
|
68
|
+
@bottom_actions << Action.new(h)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Adds a new tab and ibject body content.
|
72
|
+
def tab(opts={})
|
73
|
+
tab = Tab.new(opts)
|
74
|
+
@tabs << tab
|
75
|
+
yield tab if block_given?
|
76
|
+
tab
|
77
|
+
end
|
78
|
+
|
79
|
+
# Adding a new field to this form.
|
80
|
+
def add_field(f)
|
81
|
+
@tabs = [ Tab.new ] if @tabs.empty?
|
82
|
+
@tabs[0].col1.add_field(f)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def body_element
|
88
|
+
el(
|
89
|
+
(@edit and parent_field.nil?) ? 'form' : 'div',
|
90
|
+
attrs: {
|
91
|
+
enctype: ('multipart/form-data' if @multipart),
|
92
|
+
class: (@wait_on_submit ? ['ff-form-body', 'ff-wait-on-submit', 'ff-collapsible-body'] : ['ff-form-body', 'ff-collapsible-body']),
|
93
|
+
action: (@url if @edit), method: (@method if @edit),
|
94
|
+
style: ({display: 'none'} if @collapsible && @collapsed)
|
95
|
+
},
|
96
|
+
children: [
|
97
|
+
(errors_element if @errors.present?),
|
98
|
+
(auth_token_element if (@edit == true and parent_field.nil?)),
|
99
|
+
tabs_element,
|
100
|
+
bottom_actions,
|
101
|
+
]
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
def errors_element
|
106
|
+
many = @errors.is_a?(Array)
|
107
|
+
children = (many ? @errors.map { |e| el('li', text: e.to_s) } : [ el('span', text: @errors.to_s) ])
|
108
|
+
el((many ? 'ul' : 'div'), attrs: { class: 'ff-form-errors' }, children: children)
|
109
|
+
end
|
110
|
+
|
111
|
+
def auth_token_element
|
112
|
+
if @auth_token.present?
|
113
|
+
el('div', attrs: { style: {padding: 0, margin: 0, height: 0, width: 0, display: 'inline'} }, children: [
|
114
|
+
el('input', attrs: { type: 'hidden', name: 'authenticity_token', value: @auth_token })
|
115
|
+
])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def field_element(fld)
|
120
|
+
def field_error_element(errors)
|
121
|
+
many = errors.length > 1
|
122
|
+
children = (many ? errors.map { |e| el('li', text: e.to_s) } : [el('div', text: errors[0].to_s)])
|
123
|
+
el('div', attrs: { class: 'ff-field-errors' }, children: children)
|
124
|
+
end
|
125
|
+
# X---- field initialization ----X
|
126
|
+
fld.model = @model
|
127
|
+
fld.parent = self.parent_field
|
128
|
+
fld.model_name = self.model_name
|
129
|
+
# X------------------------------X
|
130
|
+
has_errors = (@edit and @model.present? and fld.respond_to?(:has_errors?) and fld.has_errors?)
|
131
|
+
if fld.label != false
|
132
|
+
label_text = fld.localized_label
|
133
|
+
label_hint = fld.localized_hint
|
134
|
+
label_element = el('div', attrs: { class: (fld.required ? ['ff-label', 'ff-required'] : ['ff-label'])},
|
135
|
+
text: label_text,
|
136
|
+
children: [
|
137
|
+
(el('i', attrs: { class: 'ff-field-hint', 'data-toggle' => 'tooltip', title: label_hint }) if label_hint.present?)
|
138
|
+
]
|
139
|
+
)
|
140
|
+
end
|
141
|
+
value_element = el('div', attrs: { class: (fld.required ? ['ff-value', 'ff-required'] : ['ff-value']) }, children: [
|
142
|
+
fld.to_html(@edit), (field_error_element(fld.errors) if has_errors),
|
143
|
+
])
|
144
|
+
el(
|
145
|
+
'div', attrs: {
|
146
|
+
id: ("fld_#{fld.id}" if fld.id), class: (has_errors ? ['ff-field', 'ff-error'] : ['ff-field']) },
|
147
|
+
children: [ label_element, value_element ]
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
def tabs_element
|
152
|
+
def column_element(col, hasSecondCol)
|
153
|
+
if col
|
154
|
+
el('div', attrs: { class: ['ff-col', (hasSecondCol ? 'ff-col-50' : 'ff-col-100')]}, children: [
|
155
|
+
el('div', attrs: { class: 'ff-col-inner' }, children: col.fields.map { |fld| field_element(fld) })
|
156
|
+
])
|
157
|
+
end
|
158
|
+
end
|
159
|
+
def tab_actions(tab)
|
160
|
+
if tab.actions.any?
|
161
|
+
el('div', attrs: { class: 'ff-tab-actions' }, children: tab.actions.map { |a| a.to_html(@model) })
|
162
|
+
end
|
163
|
+
end
|
164
|
+
def tab_element(tab)
|
165
|
+
hasSecondCol = (not tab.col2.fields.empty?)
|
166
|
+
col1 = column_element(tab.col1, hasSecondCol)
|
167
|
+
col2 = column_element(tab.col2, hasSecondCol)
|
168
|
+
el('div', attrs: { class: 'ff-tab-content',style: ({ display: 'none' } if @tabs.index(tab) != @selected_tab) },
|
169
|
+
children: [
|
170
|
+
tab_actions(tab),
|
171
|
+
el('div', attrs: { class: 'ff-cols'}, children: [col1, col2])
|
172
|
+
]
|
173
|
+
)
|
174
|
+
end
|
175
|
+
def tabs_header
|
176
|
+
if @tabs.length > 1
|
177
|
+
el('ul', attrs: { class: 'ff-tabs-header' }, children: @tabs.map { |tab|
|
178
|
+
el('li', attrs: { class: ('ff-selected' if @tabs.index(tab) == @selected_tab) }, children: [
|
179
|
+
(el('img', attrs: { src: tab.icon }) if tab.icon),
|
180
|
+
(el('span', text: tab.title || 'No Title'))
|
181
|
+
])
|
182
|
+
})
|
183
|
+
end
|
184
|
+
end
|
185
|
+
el(
|
186
|
+
'div',
|
187
|
+
attrs: { class: 'ff-tabs' },
|
188
|
+
children: [
|
189
|
+
tabs_header,
|
190
|
+
el('div', attrs: { class: 'ff-tabs-body'}, children: @tabs.map { |tab| tab_element(tab) })
|
191
|
+
]
|
192
|
+
)
|
193
|
+
end
|
194
|
+
|
195
|
+
def bottom_actions
|
196
|
+
edit = (@edit and parent_field.nil?)
|
197
|
+
if edit || @bottom_actions.any?
|
198
|
+
children = @bottom_actions.map { |a| a.to_html(@model) }
|
199
|
+
save_action = el('button', attrs: { type: 'submit', class: 'btn btn-primary' }, text: @submit) if edit
|
200
|
+
el('div', attrs: { class: 'ff-bottom-actions' }, children: [save_action] + children )
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# This is a tab.
|
206
|
+
class Tab
|
207
|
+
include Forma::FieldHelper
|
208
|
+
attr_reader :title, :icon, :actions
|
209
|
+
|
210
|
+
def initialize(h = {})
|
211
|
+
h = h.symbolize_keys
|
212
|
+
@title = h[:title]
|
213
|
+
@icon = h[:icon]
|
214
|
+
@col1 = h[:col1]
|
215
|
+
@col2 = h[:col2]
|
216
|
+
@actions = h[:actions] || []
|
217
|
+
end
|
218
|
+
|
219
|
+
# Adding field to this tab.
|
220
|
+
def add_field(f)
|
221
|
+
col1.add_field(f)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Returns the first column of this tab.
|
225
|
+
def col1
|
226
|
+
@col1 = Col.new if @col1.blank?
|
227
|
+
yield @col1 if block_given?
|
228
|
+
@col1
|
229
|
+
end
|
230
|
+
|
231
|
+
# Returns the second column of this tab.
|
232
|
+
def col2
|
233
|
+
@col2 = Col.new if @col2.blank?
|
234
|
+
yield @col2 if block_given?
|
235
|
+
@col2
|
236
|
+
end
|
237
|
+
|
238
|
+
def action(url, h={})
|
239
|
+
h[:url] = url
|
240
|
+
@actions << Action.new(h)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Form may have up to two columns.
|
245
|
+
class Col
|
246
|
+
include Forma::FieldHelper
|
247
|
+
attr_reader :fields
|
248
|
+
|
249
|
+
def initialize(fields = [])
|
250
|
+
@fields = fields
|
251
|
+
end
|
252
|
+
|
253
|
+
def add_field(f)
|
254
|
+
@fields << f
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Forma
|
3
|
+
module Helpers
|
4
|
+
def forma_for(model, opts = {})
|
5
|
+
opts[:model] = model
|
6
|
+
opts[:edit] = true
|
7
|
+
opts[:auth_token] = form_authenticity_token if defined?(Rails)
|
8
|
+
opts[:method] = 'post' if opts[:method].blank?
|
9
|
+
f = Forma::Form.new(opts)
|
10
|
+
yield f if block_given?
|
11
|
+
f.to_html.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def view_for(model, opts = {})
|
15
|
+
opts[:model] = model
|
16
|
+
opts[:edit] = false
|
17
|
+
f = Forma::Form.new(opts)
|
18
|
+
yield f if block_given?
|
19
|
+
f.to_html.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def table_for(models, opts = {}, &block)
|
23
|
+
opts[:models] = models
|
24
|
+
opts[:context] = block
|
25
|
+
t = Forma::Table.new(opts)
|
26
|
+
yield t if block_given?
|
27
|
+
t.to_html.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
module_function :forma_for
|
31
|
+
module_function :view_for
|
32
|
+
end
|
33
|
+
|
34
|
+
module FieldHelper
|
35
|
+
def complex_field(opts = {})
|
36
|
+
field = Forma::ComplexField.new(opts)
|
37
|
+
yield field if block_given?
|
38
|
+
add_field(field)
|
39
|
+
end
|
40
|
+
|
41
|
+
def map_field(name, opts = {})
|
42
|
+
opts[:name] = name
|
43
|
+
field = Forma::MapField.new(opts)
|
44
|
+
yield field if block_given?
|
45
|
+
add_field(field)
|
46
|
+
end
|
47
|
+
|
48
|
+
def subform(name, opts = {})
|
49
|
+
opts[:name] = name
|
50
|
+
field = Forma::SubformField.new(opts)
|
51
|
+
yield field.form if block_given?
|
52
|
+
add_field(field)
|
53
|
+
end
|
54
|
+
|
55
|
+
def text_field(name, opts={})
|
56
|
+
opts[:name] = name
|
57
|
+
field = Forma::TextField.new(opts)
|
58
|
+
yield field if block_given?
|
59
|
+
add_field(field)
|
60
|
+
end
|
61
|
+
|
62
|
+
def password_field(name, opts={})
|
63
|
+
opts[:password] = true
|
64
|
+
text_field(name, opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
def email_field(name, opts={})
|
68
|
+
opts[:name] = name
|
69
|
+
field = Forma::EmailField.new(opts)
|
70
|
+
yield field if block_given?
|
71
|
+
add_field(field)
|
72
|
+
end
|
73
|
+
|
74
|
+
def date_field(name, opts={})
|
75
|
+
opts[:name] = name
|
76
|
+
field = Forma::DateField.new(opts)
|
77
|
+
yield field if block_given?
|
78
|
+
add_field(field)
|
79
|
+
end
|
80
|
+
|
81
|
+
def boolean_field(name, opts={})
|
82
|
+
opts[:name] = name
|
83
|
+
field = Forma::BooleanField.new(opts)
|
84
|
+
yield field if block_given?
|
85
|
+
add_field(field)
|
86
|
+
end
|
87
|
+
|
88
|
+
def image_field(name, opts={})
|
89
|
+
opts[:name] = name
|
90
|
+
field = Forma::ImageField.new(opts)
|
91
|
+
yield field if block_given?
|
92
|
+
add_field(field)
|
93
|
+
end
|
94
|
+
|
95
|
+
def number_field(name, opts = {})
|
96
|
+
opts[:name] = name
|
97
|
+
field = Forma::NumberField.new(opts)
|
98
|
+
yield field if block_given?
|
99
|
+
add_field(field)
|
100
|
+
end
|
101
|
+
|
102
|
+
def combo_field(name, opts = {})
|
103
|
+
opts[:name] = name
|
104
|
+
field = Forma::ComboField.new(opts)
|
105
|
+
yield field if block_given?
|
106
|
+
add_field(field)
|
107
|
+
end
|
108
|
+
|
109
|
+
def select_field(name, search_url, opts = {})
|
110
|
+
opts[:name] = name
|
111
|
+
opts[:search_url] = search_url
|
112
|
+
field = Forma::SelectField.new(opts)
|
113
|
+
yield field if block_given?
|
114
|
+
add_field(field)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module WithTitleElement
|
119
|
+
def title_element
|
120
|
+
def active_title
|
121
|
+
el(
|
122
|
+
'span',
|
123
|
+
attrs: { class: (self.collapsible ? ['ff-active-title', 'ff-collapsible'] : ['ff-active-title']) },
|
124
|
+
children: [
|
125
|
+
(el('i', attrs: { class: (self.collapsed ? ['ff-collapse', 'ff-collapsed'] : ['ff-collapse']) }) if self.collapsible),
|
126
|
+
(el('img', attrs: { src: self.icon }) if self.icon),
|
127
|
+
(el('span', text: self.title)),
|
128
|
+
].reject { |x| x.blank? }
|
129
|
+
)
|
130
|
+
end
|
131
|
+
if self.title.present?
|
132
|
+
title_acts = el('div', attrs: { class: 'ff-title-actions' },
|
133
|
+
children: self.title_actions.map { |a| a.to_html(@model) }
|
134
|
+
) if self.title_actions.any?
|
135
|
+
el('div', attrs: { class: 'ff-title' }, children: [ active_title, title_acts ])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/forma/html.rb
CHANGED
@@ -1,3 +1,164 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
# Utilities for html text generation.
|
4
|
+
module Forma::Html
|
5
|
+
|
6
|
+
# Attribute creation.
|
7
|
+
def attr(*params)
|
8
|
+
if params.length == 2
|
9
|
+
SimpleAttr.new(params[0].to_s, params[1].to_s)
|
10
|
+
elsif params.length == 1 and params[0].is_a?(Hash)
|
11
|
+
StyleAttr.new(params[0])
|
12
|
+
elsif params.length == 1
|
13
|
+
ClassAttr.new(params[0])
|
14
|
+
else
|
15
|
+
raise "illegal attr specification: #{params}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# You can easily create elements using this method.
|
20
|
+
#
|
21
|
+
# ```
|
22
|
+
# include Forma::Html
|
23
|
+
# element = el("div", attrs = {id: 'main', class: 'header', style: {'font-size' => '20px'}})
|
24
|
+
# html = element.to_s
|
25
|
+
# ```
|
26
|
+
def el(tag, opts = {})
|
27
|
+
opts = opts.symbolize_keys
|
28
|
+
h = { text: opts[:text], html: opts[:html] }
|
29
|
+
if opts[:attrs]
|
30
|
+
attributes = []
|
31
|
+
opts[:attrs].each do |k, v|
|
32
|
+
if k == :class || k == :style
|
33
|
+
attributes << attr(v)
|
34
|
+
else
|
35
|
+
attributes << attr(k, v)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
h[:attrs] = attributes
|
39
|
+
end
|
40
|
+
h[:children] = opts[:children]
|
41
|
+
Element.new(tag, h)
|
42
|
+
end
|
43
|
+
|
44
|
+
module_function :attr
|
45
|
+
module_function :el
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
class Attr; end
|
50
|
+
|
51
|
+
# Simple attribute.
|
52
|
+
class SimpleAttr < Attr
|
53
|
+
attr_reader :name, :value
|
54
|
+
def initialize(name, value)
|
55
|
+
@name = name
|
56
|
+
@value = value
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
if @name.present? and @value.present?
|
61
|
+
%Q{#{@name}="#{@value}"}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Class attribute.
|
67
|
+
class ClassAttr < Attr
|
68
|
+
attr_reader :values
|
69
|
+
def initialize(values)
|
70
|
+
@values = values
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s
|
74
|
+
if @values.present?
|
75
|
+
if @values.is_a?(Array)
|
76
|
+
%Q{class="#{@values.join(" ")}"}
|
77
|
+
else
|
78
|
+
%Q{class="#{@values}"}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Style attribute.
|
85
|
+
class StyleAttr < Attr
|
86
|
+
attr_reader :styles
|
87
|
+
def initialize(styles)
|
88
|
+
@styles = styles
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
if @styles.present?
|
93
|
+
%Q{style="#{@styles.map{ |k,v| "#{k}:#{v}" }.join(";")}"}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Element class.
|
99
|
+
class Element
|
100
|
+
attr_reader :tag, :id, :attrs
|
101
|
+
attr_accessor :text
|
102
|
+
|
103
|
+
def initialize(tag, h)
|
104
|
+
@tag = tag.to_s
|
105
|
+
if h[:text]; @text = h[:text]
|
106
|
+
elsif h[:html]; @text = h[:html].html_safe
|
107
|
+
end
|
108
|
+
@attrs = h[:attrs] || []
|
109
|
+
@children = []
|
110
|
+
h[:children].each { |c| @children << c } if h[:children]
|
111
|
+
ids = @attrs.select { |x| x.is_a?(SimpleAttr) and x.name == "id" }.map { |x| x.value }
|
112
|
+
@id = ids[0] if ids.length > 0
|
113
|
+
@classes = @attrs.select { |x| x.is_a?(ClassAttr) }.map{ |x| x.values }.flatten
|
114
|
+
end
|
115
|
+
|
116
|
+
def klass
|
117
|
+
@classes
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_s
|
121
|
+
generate_html.html_safe
|
122
|
+
end
|
123
|
+
|
124
|
+
def attrs_by_name(name)
|
125
|
+
if name.to_s == 'class' then attrs.select { |x| x.is_a?(ClassAttr) }
|
126
|
+
elsif name.to_s == 'style' then attrs.select { |x| x.is_a?(SimpleAttr) }
|
127
|
+
else attrs.select { |x| (x.respond_to?(:name) and x.name == name.to_s) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def generate_html
|
134
|
+
h = ''
|
135
|
+
h << '<' << @tag << generate_tag_and_attributes.to_s << '>'
|
136
|
+
h << generate_inner_html
|
137
|
+
h << '</' << @tag << '>'
|
138
|
+
h
|
139
|
+
end
|
140
|
+
|
141
|
+
def generate_tag_and_attributes
|
142
|
+
attrs = @attrs.map{|a| a.to_s}.reject{|s| s.blank? }.join(" ")
|
143
|
+
' ' << attrs unless attrs.blank?
|
144
|
+
end
|
145
|
+
|
146
|
+
def generate_inner_html
|
147
|
+
h = ''
|
148
|
+
if @text.html_safe?
|
149
|
+
h << @text
|
150
|
+
else
|
151
|
+
h << ERB::Util.html_escape(@text)
|
152
|
+
end
|
153
|
+
h << generate_children unless @children.blank?
|
154
|
+
h
|
155
|
+
end
|
156
|
+
|
157
|
+
def generate_children
|
158
|
+
h = ''
|
159
|
+
@children.each { |c| h << c.to_s }
|
160
|
+
h
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'forma/helpers'
|
3
|
+
|
4
|
+
module Forma
|
5
|
+
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
end
|
8
|
+
|
9
|
+
class Railtie < Rails::Railtie
|
10
|
+
initializer 'forma.helpers' do
|
11
|
+
ActionView::Base.send :include, Forma::Helpers
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
data/lib/forma/table.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Forma
|
3
|
+
class Table
|
4
|
+
include Forma::Html
|
5
|
+
include Forma::FieldHelper
|
6
|
+
include Forma::WithTitleElement
|
7
|
+
attr_reader :collapsible, :collapsed, :icon, :title
|
8
|
+
attr_reader :title_actions
|
9
|
+
|
10
|
+
def initialize(h = {})
|
11
|
+
h = h.symbolize_keys
|
12
|
+
@id = h[:id]
|
13
|
+
# title properties
|
14
|
+
@title = h[:title]
|
15
|
+
@icon = h[:icon]
|
16
|
+
@collapsible = h[:collapsible]
|
17
|
+
@collapsed = h[:collapsed]
|
18
|
+
# values and fields
|
19
|
+
@models = h[:models]
|
20
|
+
@fields = h[:fields] || []
|
21
|
+
@paginate = h[:paginate]
|
22
|
+
# actions
|
23
|
+
@title_actions = h[:title_actions] || []
|
24
|
+
@item_actions = h[:item_actions] || []
|
25
|
+
# context
|
26
|
+
@context = h[:context]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_html
|
30
|
+
el(
|
31
|
+
'div',
|
32
|
+
attrs: { id: @id, class: ['ff-table'] },
|
33
|
+
children: [
|
34
|
+
title_element,
|
35
|
+
body_element,
|
36
|
+
]
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def title_action(url, h={})
|
41
|
+
h[:url] = url
|
42
|
+
@title_actions << Action.new(h)
|
43
|
+
end
|
44
|
+
|
45
|
+
def item_action(url, h={})
|
46
|
+
h[:url] = url
|
47
|
+
@item_actions << Action.new(h)
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_field(f)
|
51
|
+
@fields << f
|
52
|
+
end
|
53
|
+
|
54
|
+
def paginate(h={})
|
55
|
+
@paginate = true
|
56
|
+
@paginate_options = h
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def body_element
|
62
|
+
el(
|
63
|
+
'div', attrs: {
|
64
|
+
class: ['ff-table-body', 'ff-collapsible-body'],
|
65
|
+
style: ( {display: 'none'} if @collapsible && @collapsed ),
|
66
|
+
},
|
67
|
+
children: [ table_element, pagination_element ]
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def table_element
|
72
|
+
def table_header_element
|
73
|
+
children = @fields.map { |f|
|
74
|
+
f.model = @models.first
|
75
|
+
label_text = f.localized_label
|
76
|
+
label_hint = f.localized_hint
|
77
|
+
el('th', attrs: { class: 'ff-field' }, text: label_text, children: [
|
78
|
+
(el('i', attrs: { class: 'ff-field-hint', 'data-toggle' => 'tooltip', title: label_hint }) if label_hint.present?)
|
79
|
+
])
|
80
|
+
}
|
81
|
+
children << el('th', attrs: { style: {width: '100px'} }) if @item_actions.any?
|
82
|
+
el('thead', children: [
|
83
|
+
el('tr', children: children)
|
84
|
+
])
|
85
|
+
end
|
86
|
+
def table_row(model)
|
87
|
+
children = @fields.map { |fld|
|
88
|
+
fld.model = model
|
89
|
+
el('td', children: [ fld.to_html(false) ])
|
90
|
+
}
|
91
|
+
if @item_actions.any?
|
92
|
+
children << el('td', children: @item_actions.map { |act| act.to_html(model) })
|
93
|
+
end
|
94
|
+
el('tr', children: children)
|
95
|
+
end
|
96
|
+
def table_body_element
|
97
|
+
children =
|
98
|
+
el('tbody', children: @models.map { |model| table_row(model) })
|
99
|
+
end
|
100
|
+
if @models and @models.any?
|
101
|
+
el('table', attrs: { class: 'ff-common-table' }, children: [ table_header_element, table_body_element ])
|
102
|
+
else
|
103
|
+
el('div', attrs: { class: ['ff-empty', 'ff-table-empty'] }, text: Forma.config.texts.table_empty)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def pagination_element
|
108
|
+
if @paginate and @context
|
109
|
+
self_from_block = eval("self", @context.binding)
|
110
|
+
s = self_from_block.send(:will_paginate, @models, @paginate_options)
|
111
|
+
el('div', html: s.to_s)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/forma/utils.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Forma
|
3
|
+
module Utils
|
4
|
+
def singular_name(model)
|
5
|
+
if model.respond_to?(:model_name); model.model_name.singular_route_key # Mongoid
|
6
|
+
elsif model.class.respond_to?(:table_name); model.class.table_name.singularize # ActiveModel
|
7
|
+
else; '' # Others
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def extract_value(val, name)
|
12
|
+
def simple_value(model, name)
|
13
|
+
if model.respond_to?(name); model.send(name)
|
14
|
+
elsif model.respond_to?('[]'); model[name] || model[name.to_sym]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
name.to_s.split('.').each { |n| val = simple_value(val, n) if val }
|
18
|
+
val
|
19
|
+
end
|
20
|
+
|
21
|
+
module_function :singular_name
|
22
|
+
module_function :extract_value
|
23
|
+
end
|
24
|
+
end
|