forma 0.0.0 → 0.1.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.
- 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
|