forma 0.1.1 → 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 16aaf06c56a52180e7b4aea01508cb4bdc9fcb74
4
+ data.tar.gz: 4e1a5437ff778c502118d0cebc20fc97c03f2e8c
5
+ SHA512:
6
+ metadata.gz: 84ae57199011563b025ca909d30b6047bd3865e25a54dd8d606732b8631de098411743b58b60b6e002ec615f52c8206032320ffd27a44afdbd5b764e5b2ad18f
7
+ data.tar.gz: c07481957ed0302a27a3325491e364fabf6effb634438ba6c15e6175e307fe23ddfa2057783942062486263444234e80a0b97d9e55c4b70eaac7c650bc7006bb
data/lib/forma/action.rb CHANGED
@@ -15,31 +15,56 @@ module Forma
15
15
  @confirm = h[:confirm]
16
16
  @as = h[:as]
17
17
  @tooltip = h[:tooltip]
18
+ @select = h[:select]
19
+ @condition = h[:condition]
18
20
  end
19
21
 
20
22
  def to_html(model)
21
- children = [ (el('img', attrs: { src: @icon }) if @icon.present?), el('span', text: eval_label(model)) ]
22
- button = (@as.to_s == 'button')
23
- el(
24
- 'a',
25
- attrs: {
26
- id: @id,
27
- class: ['ff-action', ('btn' if button)],
28
- href: eval_url(model), 'data-method' => @method, 'data-confirm' => @confirm,
29
- 'data-original-title' => @tooltip
30
- },
31
- children: children
32
- )
23
+ if eval_condition(model)
24
+ if @select
25
+ el(
26
+ 'a',
27
+ attrs: {
28
+ id: @id, class: ['ff-action', 'btn', 'btn-mini', 'ff-select-action', 'btn-xs', 'btn-default'],
29
+ href: '#', 'data-original-title' => @tooltip,
30
+ 'data-value-id' => model.id, 'data-value-type' => model.class.name, 'data-value-text' => model.to_s
31
+ },
32
+ children: [ el('i', attrs: { class: 'icon icon-download fa fa-hand-o-left' }) ]
33
+ )
34
+ else
35
+ children = [ (el('img', attrs: { src: eval_icon(model) }) if @icon.present?), el('span', text: eval_label(model)) ]
36
+ button = (@as.to_s == 'button')
37
+ el(
38
+ 'a',
39
+ attrs: {
40
+ id: @id,
41
+ class: ['ff-action', ('btn btn-default' if button)],
42
+ href: eval_url(model), 'data-method' => @method, 'data-confirm' => @confirm,
43
+ 'data-original-title' => @tooltip
44
+ },
45
+ children: children
46
+ )
47
+ end
48
+ end
33
49
  end
34
50
 
35
51
  private
36
52
 
37
53
  def eval_url(model)
38
- @url.is_a?(Proc) ? @url.call(model) : @url.to_s
54
+ @url.is_a?(Proc) ? @url.call(model).to_s : @url.to_s
39
55
  end
40
56
 
41
57
  def eval_label(model)
42
- @label.is_a?(Proc) ? @label.call(model) : @label.to_s
58
+ @label.is_a?(Proc) ? @label.call(model).to_s : @label.to_s
59
+ end
60
+
61
+ def eval_icon(model)
62
+ @icon.is_a?(Proc) ? @icon.call(model).to_s : @icon.to_s
63
+ end
64
+
65
+ def eval_condition(model)
66
+ if @condition then @condition.is_a?(Proc) ? @condition.call(model) : @condition
67
+ else true end
43
68
  end
44
69
  end
45
70
  end
data/lib/forma/config.rb CHANGED
@@ -34,9 +34,13 @@ module Forma
34
34
  class NumberConfig
35
35
  attr_accessor :delimiter
36
36
  attr_accessor :separator
37
+ attr_accessor :max_digits
38
+ attr_accessor :min_digits
37
39
  def initialize
38
40
  self.delimiter = ','
39
41
  self.separator = '.'
42
+ self.max_digits = 3
43
+ self.min_digits = 2
40
44
  end
41
45
  end
42
46
 
data/lib/forma/field.rb CHANGED
@@ -5,7 +5,7 @@ module Forma
5
5
  include Forma::Utils
6
6
  include Forma::Html
7
7
 
8
- attr_reader :label, :hint, :i18n, :name, :tag
8
+ attr_reader :label, :hint, :i18n, :name, :tag, :inline_hint
9
9
  attr_reader :required, :autofocus, :readonly
10
10
  attr_reader :width, :height
11
11
  attr_reader :before, :after
@@ -17,6 +17,7 @@ module Forma
17
17
  def initialize(h = {})
18
18
  h = h.symbolize_keys
19
19
  @id = h[:id]; @label = h[:label]; @hint = h[:hint]; @i18n = h[:i18n]
20
+ @inline_hint = h[:inline_hint]
20
21
  @required = h[:required]; @autofocus = h[:autofocus]; @readonly = (not not h[:readonly])
21
22
  @width = h[:width]; @height = h[:height]
22
23
  @before = h[:before]; @after = h[:after]
@@ -26,6 +27,9 @@ module Forma
26
27
  @model_name = h[:model_name]; @child_model_name = h[:child_model_name]
27
28
  @actions = h[:actions] || []
28
29
  @tag = h[:tag]
30
+ @empty = h[:empty]
31
+ @force_nonempty = h[:force_nonempty]
32
+ @class = h[:class]
29
33
  end
30
34
 
31
35
  def action(url, h={})
@@ -40,22 +44,16 @@ module Forma
40
44
  else
41
45
  chain = [ self.model_name, self.name ]
42
46
  end
47
+ chain.map { |x| x.to_s.gsub '.', '_' }
43
48
  end
44
49
 
45
50
  def id
46
51
  if @id then @id
47
- else name_as_chain.flatten.join('_') end
52
+ else name_as_chain.select{ |x| x.present? }.join('_') end
48
53
  end
49
54
 
50
55
  def parameter_name
51
- chain = name_as_chain; length = chain.length
52
- p_name = ''
53
- chain.reverse.each_with_index do |n, i|
54
- if i == 0 then p_name = n
55
- elsif i == length - 1 then p_name = "#{n}[#{p_name}]"
56
- else p_name = "#{n}_attributes[#{p_name}]" end
57
- end
58
- p_name
56
+ parameter_name_from_chain(name_as_chain)
59
57
  end
60
58
 
61
59
  # Convert this element into HTML.
@@ -63,15 +61,15 @@ module Forma
63
61
  val = self.value
64
62
  if edit and not readonly
65
63
  edit = edit_element(val)
66
- el('div', children: [ before_element, icon_element, edit, after_element, actions_element ])
64
+ el('div', children: [ before_element, icon_element, edit, after_element, actions_element, inline_hint_element ])
67
65
  else
68
- if val.present? or val == false
66
+ if val.present? or val == false or @force_nonempty
69
67
  view = view_element(val)
70
68
  view = el('a', attrs: { href: eval_url }, children: [ view ]) if @url
71
- el('div', children: [ before_element, icon_element, view, after_element, actions_element ])
69
+ el('div', attrs: { class: (@class ? eval_with_model(@class) : nil) },
70
+ children: [ before_element, icon_element, view, after_element, actions_element, inline_hint_element ])
72
71
  else
73
- empty = empty_element
74
- el('div', children: [ empty, actions_element ])
72
+ el('div', children: [ empty_element, actions_element ])
75
73
  end
76
74
  end
77
75
  end
@@ -94,7 +92,9 @@ module Forma
94
92
  end
95
93
 
96
94
  def localized_label
97
- self.label.present? ? self.label : I18n.t(localization_key, default: self.name)
95
+ unless self.label == false
96
+ self.label.present? ? self.label : I18n.t(localization_key, default: self.name)
97
+ end
98
98
  end
99
99
 
100
100
  def localized_hint
@@ -103,35 +103,28 @@ module Forma
103
103
 
104
104
  protected
105
105
 
106
- def icon_element
107
- el('img', attrs: { src: eval_icon, style: { 'margin-right' => '4px' } }) if @icon.present?
108
- end
109
-
110
- def before_element
111
- el('span', text: before, attrs: { class: 'ff-field-before' }) if before.present?
112
- end
113
-
114
- def after_element
115
- el('span', text: after, attrs: { class: 'ff-field-after' }) if after.present?
116
- end
117
-
118
- def empty_element
119
- el('span', attrs: { class: 'ff-empty' }, text: Forma.config.texts.empty)
120
- end
121
-
122
- def actions_element
123
- if @actions.any?
124
- el('div', attrs: { class: 'ff-field-actions' }, children: @actions.map { |action| action.to_html(@model) })
106
+ def parameter_name_from_chain(chain)
107
+ length = chain.length
108
+ p_name = ''
109
+ chain.select{ |x| x.present? }.reverse.each_with_index do |n, i|
110
+ if i == 0 then p_name = n
111
+ elsif i == length - 1 then p_name = "#{n}[#{p_name}]"
112
+ else p_name = "#{n}_attributes[#{p_name}]" end
125
113
  end
114
+ p_name
126
115
  end
127
116
 
128
- def eval_url
129
- @url.is_a?(Proc) ? @url.call(self.model) : @url.to_s
130
- end
117
+ def icon_element; el('img', attrs: { src: eval_icon, style: { 'margin-right' => '4px' } }) if @icon.present? end
118
+ def before_element; el('span', text: eval_with_model(before), attrs: { class: 'ff-field-before' }) if before.present? end
119
+ def after_element; el('span', text: eval_with_model(after), attrs: { class: 'ff-field-after' }) if after.present? end
120
+ def empty_element; el('span', attrs: { class: 'ff-empty' }, text: Forma.config.texts.empty) unless @empty == false end
121
+ def actions_element; el('div', attrs: { class: 'ff-field-actions' }, children: @actions.map { |action| action.to_html(@model) }) if @actions.any? end
122
+ def inline_hint_element; el('div', attrs: { class: 'ff-inline-hint' }, text: @inline_hint) if @inline_hint.present? end
123
+ def eval_url; eval_with_model(@url) end
124
+ def eval_icon; eval_with_model(@icon) end
131
125
 
132
- def eval_icon
133
- @icon.is_a?(Proc) ? @icon.call(self.model) : @icon.to_s
134
- end
126
+ private
127
+ def eval_with_model(val, h={}); val.is_a?(Proc) ? val.call(h[:model] || self.model) : val.to_s end
135
128
  end
136
129
 
137
130
  # Complex field.
@@ -150,24 +143,17 @@ module Forma
150
143
  end
151
144
 
152
145
  def value
153
- val = super
154
- if val then val
155
- else
156
- @fields.each { |f| f.model = self.model }
157
- @fields.map { |f| f.value }
158
- end
146
+ @model
159
147
  end
160
148
 
161
149
  def edit_element(val)
162
150
  el(
163
151
  'div',
164
152
  attrs: { class: 'ff-complex-field' },
165
- children: @fields.zip(val).map { |fv|
166
- el(
167
- 'div',
168
- attrs: { class: 'ff-field' },
169
- children: [ fv[0].edit_element(fv[1]) ]
170
- )
153
+ children: @fields.map { |f|
154
+ f.model = self.model
155
+ f.model_name = self.model_name
156
+ el('div', attrs: { class: 'ff-complex-part' }, children: [ f.to_html(true) ])
171
157
  }
172
158
  )
173
159
  end
@@ -176,15 +162,20 @@ module Forma
176
162
  el(
177
163
  'div',
178
164
  attrs: { class: 'ff-complex-field' },
179
- children: @fields.zip(val).map { |fv|
180
- el(
181
- 'div',
182
- attrs: { class: 'ff-complex-part' },
183
- children: [ fv[0].view_element(fv[1]) ]
184
- )
165
+ children: @fields.map { |f|
166
+ f.model = self.model
167
+ el('div', attrs: { class: 'ff-complex-part' }, children: [ f.to_html(false) ])
185
168
  }
186
169
  )
187
170
  end
171
+
172
+ def errors
173
+ @fields.map { |f| f.model = @model; f.errors }.flatten
174
+ end
175
+
176
+ def has_errors?
177
+ errors.any?
178
+ end
188
179
  end
189
180
 
190
181
  # Map field.
@@ -336,21 +327,36 @@ module Forma
336
327
  def initialize(h = {})
337
328
  h = h.symbolize_keys
338
329
  @formatter = h[:formatter]
330
+ @@date_counter ||= 0
331
+ @@date_counter += 1
332
+ @date_counter = @@date_counter
339
333
  super(h)
340
334
  end
341
335
 
342
336
  def view_element(val)
343
- el('span', text: val.localtime.strftime(formatter || Forma.config.date.formatter))
337
+ val = val.respond_to?(:localtime) ? val.localtime : val
338
+ el('span', text: val.strftime(formatter || Forma.config.date.formatter))
344
339
  end
345
340
 
346
341
  def edit_element(val)
347
- el('input', attrs: {
348
- name: parameter_name,
349
- type: 'text',
350
- value: val.to_s,
351
- autofocus: @autofocus,
352
- style: { width: ("#{width}px" if width.present?) }
353
- })
342
+ input_id = "ff-date-#{@date_counter}"
343
+ val = Date.strptime(val) if (val.present? and val.is_a?(String)) rescue nil
344
+ el('div', children: [
345
+ el('input', attrs: {
346
+ id: input_id,
347
+ name: parameter_name,
348
+ value: val.to_s,
349
+ type: 'hidden'
350
+ }),
351
+ el('input', attrs: {
352
+ class: 'ff-date',
353
+ type: 'text',
354
+ value: (val.strftime('%d-%b-%Y') if val.present?),
355
+ autofocus: @autofocus,
356
+ style: { width: "#{width || 100}px" },
357
+ 'data-altfield' => input_id,
358
+ })
359
+ ])
354
360
  end
355
361
  end
356
362
 
@@ -369,22 +375,59 @@ module Forma
369
375
 
370
376
  # Image upload field.
371
377
  class ImageField < SimpleField
378
+ def initialize(h = {})
379
+ h = h.symbolize_keys
380
+ @popover = h[:popover]
381
+ @height = h[:height] || 200
382
+ @width = h[:width] || 200
383
+ super(h)
384
+ end
385
+
372
386
  def view_element(val)
373
- el('img', attrs: { src: val.url } )
387
+ popover_data = {}
388
+ image_url = val.respond_to?(:url) ? val.url : val.to_s
389
+ if @popover
390
+ popover_data['data-original-title'] = eval_with_model(@popover[:title])
391
+ url = eval_with_model(@popover[:url])
392
+ popover_data['data-content'] = %Q{<div style="height: #{@height}px; width: #{@width}px;"><img src="#{url}"></img></div>}
393
+ popover_data['data-html'] = 'true'
394
+ popover_data['data-placement'] = @popover[:placement]
395
+ el('img', attrs: { src: image_url, class: 'ff-popover' }.merge(popover_data))
396
+ else
397
+ el('img', attrs: { src: image_url })
398
+ end
374
399
  end
375
400
 
376
401
  def edit_element(val)
377
- el('input', attrs: {
378
- name: parameter_name,
379
- type: 'file',
380
- })
402
+ el('input', attrs: { name: parameter_name, type: 'file' })
403
+ end
404
+ end
405
+
406
+ # Image upload field.
407
+ class FileField < SimpleField
408
+ def view_element(val)
409
+ el('div', text: 'NO IMPLEMENTATION')
410
+ end
411
+
412
+ def edit_element(val)
413
+ el('input', attrs: { name: parameter_name, type: 'file' })
381
414
  end
382
415
  end
383
416
 
384
417
  # Number field.
385
418
  class NumberField < TextField
419
+ include Forma::Utils
420
+ def initialize(h = {})
421
+ h = h.symbolize_keys
422
+ @min_digits = h[:min_digits] || Forma.config.num.min_digits
423
+ @max_digits = h[:max_digits] || Forma.config.num.max_digits
424
+ @separator = h[:separator] || Forma.config.num.separator
425
+ @delimiter = h[:delimiter] || Forma.config.num.delimiter
426
+ super(h)
427
+ end
428
+
386
429
  def view_element(val)
387
- el('code', text: "#{val}")
430
+ el('code', text: "#{number_format(val.to_f, max_digits: @max_digits, min_digits: @min_digits)}")
388
431
  end
389
432
  end
390
433
 
@@ -408,8 +451,8 @@ module Forma
408
451
  data = normalize_data(@collection, @empty)
409
452
  selection = val.present? ? val : @default
410
453
  el('select', attrs: { name: parameter_name }, children: data.map { |text, value|
411
- if value.nil? then el('option', attrs: { selected: selection.blank? }, text: text)
412
- else el('option', attrs: { selected: (true if selection == value), value: value }, text: text)
454
+ if value.nil? then el('option', attrs: { selected: selection.blank? , value: ""}, text: text)
455
+ else el('option', attrs: { selected: (true if selection.to_s == value.to_s), value: value }, text: text)
413
456
  end
414
457
  })
415
458
  end
@@ -419,11 +462,64 @@ module Forma
419
462
  def normalize_data(collection, empty)
420
463
  if collection.is_a?(Hash) then data = collection.to_a
421
464
  else data = collection.map { |x| [x.to_s, x.id] } end
422
- if empty != false then data.insert[empty.to_s, nil] end
465
+ if empty != false then data = ([[empty.to_s, nil]] + data) end
423
466
  Hash[data]
424
467
  end
425
468
  end
426
469
 
470
+ # Array field.
471
+ class ArrayField < SimpleField
472
+ def initialize(h={})
473
+ h = h.symbolize_keys
474
+ @item_actions = h[:item_actions] || []
475
+ @item_url = h[:item_url]
476
+ super(h)
477
+ end
478
+
479
+ def view_element(val)
480
+ el('div', attrs: { class: ['ff-array-field'] }, children: val.map { |x|
481
+ el('div', attrs: { class: 'ff-array-part' }, children: [
482
+ if @item_url then el('a', attrs: { href: eval_with_model(@item_url, model: x) }, text: x.to_s) else el('span', text: x.to_s) end,
483
+ (el('span', attrs: { class: 'ff-actions' }, children: @item_actions.map { |a| a.to_html(x) } ) if @item_actions.any?)
484
+ ])
485
+ })
486
+ end
487
+
488
+ def edit_element(val)
489
+ el('div', text: 'NO IMPLEMENTATION')
490
+ end
491
+
492
+ def item_action(url, h={})
493
+ h[:url] = url
494
+ @item_actions << Action.new(h)
495
+ end
496
+ end
497
+
498
+ # Table field.
499
+ class TableField < SimpleField
500
+ def initialize(h={})
501
+ h = h.symbolize_keys
502
+ h[:label] = false
503
+ h[:force_nonempty] = true
504
+ @table = Forma::Table.new(h[:table] || {})
505
+ super(h)
506
+ end
507
+
508
+ def view_element(val)
509
+ @table.models = val
510
+ @table.to_html
511
+ end
512
+
513
+ def edit_element(val)
514
+ el('div', text: 'NO IMPLEMENTATION')
515
+ end
516
+
517
+ def table
518
+ yield @table if block_given?
519
+ @table
520
+ end
521
+ end
522
+
427
523
  # Selection field.
428
524
  class SelectField < SimpleField
429
525
  def initialize(h={})
@@ -431,21 +527,46 @@ module Forma
431
527
  @search_url = h[:search_url]
432
528
  @search_width = h[:search_width] || 500
433
529
  @search_height = h[:search_height] || 600
530
+ @polymorphic = h[:polymorphic]
434
531
  super(h)
435
532
  end
436
533
 
437
- def view_element(val)
438
- el(@tag || 'span', text: val.to_s)
534
+ def id_field_name
535
+ chain = name_as_chain
536
+ chain[chain.length - 1] = "#{chain.last}_id"
537
+ parameter_name_from_chain(chain)
538
+ end
539
+
540
+ def type_field_name
541
+ chain = name_as_chain
542
+ chain[chain.length - 1] = "#{chain.last}_type"
543
+ parameter_name_from_chain(chain)
439
544
  end
440
545
 
546
+ def view_element(val); el(@tag || 'span', text: val.to_s) end
547
+
441
548
  def edit_element(val)
442
- el('div', attrs: { id: self.id, class: 'ff-select-field' }, children: [
443
- el('input', attrs: { id: "#{self.id}_value", type: 'text', value: "#{val and val.id}" }),
444
- el('span', attrs: { id: "#{self.id}_text" }, text: val.to_s),
445
- el('a', attrs: { class: 'ff-select-link btn btn-mini', 'data-id' => self.id, 'data-url' => @search_url, 'data-width' => @search_width, 'data-height' => @search_height }, children: [
446
- el('i', attrs: { class: 'icon icon-search' })
549
+ inputs = [ el('input', attrs: { id: "#{self.id}_id_value", type: 'hidden', value: "#{val and val.id}", name: id_field_name }) ]
550
+ if @polymorphic
551
+ inputs << el('input',
552
+ attrs: { id: "#{self.id}_type_value", type: 'hidden', value: "#{val and val.class.to_s}", name: type_field_name }
553
+ )
554
+ end
555
+ text_element = el(
556
+ 'span',
557
+ attrs: { id: "#{self.id}_text", class: ['ff-select-label', ('ff-empty' if val.blank?)] },
558
+ text: (val.present? ? val.to_s : Forma.config.texts.empty)
559
+ )
560
+ buttons = el('div', attrs: { class: 'btn-group' }, children: [
561
+ el('a', attrs: { class: 'ff-select-link btn btn-mini btn-default btn-xs', 'data-id' => self.id, 'data-url' => @search_url, 'data-width' => @search_width, 'data-height' => @search_height }, children: [
562
+ el('i', attrs: { class: 'icon icon-search fa fa-search' })
563
+ ]),
564
+ el('a', attrs: { class: 'ff-clear-selection-action btn btn-mini btn-default btn-xs', 'data-id' => self.id }, children: [
565
+ el('i', attrs: { class: 'icon icon-trash fa fa-trash-o' })
447
566
  ])
448
567
  ])
568
+ children = inputs + [ text_element, buttons ]
569
+ el('div', attrs: { id: self.id, class: 'ff-select-field' }, children: children)
449
570
  end
450
571
  end
451
572
  end
data/lib/forma/form.rb CHANGED
@@ -109,7 +109,7 @@ module Forma
109
109
  end
110
110
 
111
111
  def auth_token_element
112
- if @auth_token.present?
112
+ if @auth_token.present? and @method != 'get'
113
113
  el('div', attrs: { style: {padding: 0, margin: 0, height: 0, width: 0, display: 'inline'} }, children: [
114
114
  el('input', attrs: { type: 'hidden', name: 'authenticity_token', value: @auth_token })
115
115
  ])
@@ -138,7 +138,10 @@ module Forma
138
138
  ]
139
139
  )
140
140
  end
141
- value_element = el('div', attrs: { class: (fld.required ? ['ff-value', 'ff-required'] : ['ff-value']) }, children: [
141
+ value_class = ['ff-value']
142
+ value_class << 'ff-required' if fld.required
143
+ value_class << 'ff-value-no-label' if fld.label == false
144
+ value_element = el('div', attrs: { class: value_class }, children: [
142
145
  fld.to_html(@edit), (field_error_element(fld.errors) if has_errors),
143
146
  ])
144
147
  el(
data/lib/forma/helpers.rb CHANGED
@@ -92,6 +92,13 @@ module Forma
92
92
  add_field(field)
93
93
  end
94
94
 
95
+ def file_field(name, opts={})
96
+ opts[:name] = name
97
+ field = Forma::FileField.new(opts)
98
+ yield field if block_given?
99
+ add_field(field)
100
+ end
101
+
95
102
  def number_field(name, opts = {})
96
103
  opts[:name] = name
97
104
  field = Forma::NumberField.new(opts)
@@ -113,6 +120,20 @@ module Forma
113
120
  yield field if block_given?
114
121
  add_field(field)
115
122
  end
123
+
124
+ def array_field(name, opts = {})
125
+ opts[:name] = name
126
+ field = Forma::ArrayField.new(opts)
127
+ yield field if block_given?
128
+ add_field(field)
129
+ end
130
+
131
+ def table_field(name, opts = {})
132
+ opts[:name] = name
133
+ field = Forma::TableField.new(opts)
134
+ yield field if block_given?
135
+ add_field(field)
136
+ end
116
137
  end
117
138
 
118
139
  module WithTitleElement
data/lib/forma/html.rb CHANGED
@@ -44,7 +44,7 @@ module Forma::Html
44
44
  module_function :attr
45
45
  module_function :el
46
46
 
47
- private
47
+ # private
48
48
 
49
49
  class Attr; end
50
50
 
@@ -57,8 +57,10 @@ module Forma::Html
57
57
  end
58
58
 
59
59
  def to_s
60
- if @name.present? and @value.present?
61
- %Q{#{@name}="#{@value}"}
60
+ if @name == 'value'
61
+ %Q{#{@name}="#{ERB::Util.html_escape(@value)}"}
62
+ elsif @name.present? and @value.present?
63
+ %Q{#{@name}="#{ERB::Util.html_escape(@value)}"}
62
64
  end
63
65
  end
64
66
  end
data/lib/forma/table.rb CHANGED
@@ -6,6 +6,7 @@ module Forma
6
6
  include Forma::WithTitleElement
7
7
  attr_reader :collapsible, :collapsed, :icon, :title
8
8
  attr_reader :title_actions
9
+ attr_accessor :models
9
10
 
10
11
  def initialize(h = {})
11
12
  h = h.symbolize_keys
@@ -108,7 +109,14 @@ module Forma
108
109
  if @paginate and @context
109
110
  self_from_block = eval("self", @context.binding)
110
111
  s = self_from_block.send(:will_paginate, @models, @paginate_options)
111
- el('div', html: s.to_s)
112
+ paginate = el('div', html: s.to_s)
113
+ el('div', attrs: { class: 'ff-paginate' }, children: [
114
+ paginate,
115
+ (el('div', attrs: { class: 'ff-totals' }, children: [
116
+ el('code', text: "#{@models.total_entries}"),
117
+ el('span', text: @paginate_options[:records] || 'records')
118
+ ]) if @models.total_entries > 0)
119
+ ])
112
120
  end
113
121
  end
114
122
  end
data/lib/forma/utils.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ require 'action_controller'
3
+
2
4
  module Forma
3
5
  module Utils
6
+ include ActionView::Helpers::NumberHelper
7
+
4
8
  def singular_name(model)
5
9
  if model.respond_to?(:model_name); model.model_name.singular_route_key # Mongoid
6
10
  elsif model.class.respond_to?(:table_name); model.class.table_name.singularize # ActiveModel
@@ -18,7 +22,25 @@ module Forma
18
22
  val
19
23
  end
20
24
 
25
+ def number_format(num, h = {})
26
+ max_digits = h[:max_digits] || 2
27
+ max_digits = 0 if max_digits < 0
28
+ min_digits = h[:min_digits] || 0
29
+ min_digits = max_digits if min_digits > max_digits
30
+ separator = h[:separator] || '.'
31
+ delimiter = h[:delimiter] || ','
32
+ formatted = number_with_precision(num, precision: max_digits, separator: separator, delimiter: delimiter, strip_insignificant_zeros: true)
33
+ nums = formatted.split(separator)
34
+ length_after_separator = (nums[1] || '').length
35
+ if length_after_separator >= min_digits
36
+ formatted
37
+ else
38
+ "#{nums[0]}#{separator}#{(nums[1] || '').ljust(min_digits, '0')}"
39
+ end
40
+ end
41
+
21
42
  module_function :singular_name
22
43
  module_function :extract_value
44
+ module_function :number_format
23
45
  end
24
46
  end
data/lib/forma/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module Forma
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.2"
4
4
  end
@@ -0,0 +1,15 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class UtilsTest < Test::Unit::TestCase
5
+ include Forma::Utils
6
+
7
+ def test_number_format
8
+ assert_equal '123,456', number_format(123_456)
9
+ assert_equal '123,456.00', number_format(123_456, min_digits: 2)
10
+ assert_equal '123,456.79', number_format(123_456.789)
11
+ assert_equal '123,456.789', number_format(123_456.789, max_digits: 3)
12
+ assert_equal '123,456.789', number_format(123_456.789, max_digits: 4)
13
+ assert_equal '123,456.7890', number_format(123_456.789, max_digits: 4, min_digits: 4)
14
+ end
15
+ end
@@ -69,6 +69,35 @@
69
69
  });
70
70
  };
71
71
 
72
+ var initializeSelectActions = function() {
73
+ $('.ff-select-action').click(function() {
74
+ var valueId = window.name + '_id_value';
75
+ var typeId = window.name + '_type_value';
76
+ var labelId = window.name + '_text';
77
+ var valueElement = window.opener.$('#' + valueId);
78
+ var labelElement = window.opener.$('#' + labelId);
79
+ var typeElement = window.opener.$('#' + typeId);
80
+ valueElement.val($(this).attr('data-value-id'));
81
+ if (typeElement) { typeElement.val($(this).attr('data-value-type')); }
82
+ labelElement.html($(this).attr('data-value-text'));
83
+ labelElement.removeClass('ff-empty');
84
+ window.close();
85
+ return false;
86
+ });
87
+ $('.ff-clear-selection-action').click(function() {
88
+ var valueId = $(this).attr('data-id') + '_id_value';
89
+ var labelId = $(this).attr('data-id') + '_text';
90
+ var typeId = $(this).attr('data-id') + '_type_value';
91
+ var valueElement = $('#' + valueId);
92
+ var labelElement = $('#' + labelId);
93
+ var typeElement = $('#' + typeId);
94
+ valueElement.val(null);
95
+ if (typeElement) { typeElement.val(null); }
96
+ labelElement.text('(empty)');
97
+ labelElement.addClass('ff-empty');
98
+ });
99
+ };
100
+
72
101
  // google map initialization
73
102
 
74
103
  var mapsData = {};
@@ -121,6 +150,24 @@
121
150
  }
122
151
  };
123
152
 
153
+ var initPopovers = function() {
154
+ $('.ff-popover').popover({ trigger: 'click' });
155
+ };
156
+
157
+ var initDatePicker = function() {
158
+ var pickers = $('.ff-date');
159
+ for (var i = 0, l = pickers.length; i < l; i++) {
160
+ var picker = $(pickers[i]);
161
+ picker.datepicker({
162
+ dateFormat: 'dd-M-yy',
163
+ altField: '#' + picker.attr('data-altfield'), altFormat: 'yy-mm-dd',
164
+ onClose: function(dateText, inst) {
165
+ if(dateText == '') { $(inst.settings["altField"]).val(dateText); }
166
+ },
167
+ });
168
+ }
169
+ };
170
+
124
171
  // prepare function
125
172
 
126
173
  var ready = function() {
@@ -129,7 +176,11 @@
129
176
  initializeFormSubmit();
130
177
  initializeTooltips();
131
178
  initGoogleMaps();
179
+ initPopovers();
180
+ initDatePicker();
181
+ // selectors
132
182
  initializeSelectFields();
183
+ initializeSelectActions();
133
184
  };
134
185
 
135
186
  // turbolink initilization!
@@ -1,41 +1,47 @@
1
- .user-select-none{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;}
2
- .ff-form,.ff-table{margin-bottom:16px;}.ff-form .ff-action,.ff-table .ff-action{margin-left:4px;}.ff-form .ff-action:first-child,.ff-table .ff-action:first-child{margin-left:0;}
3
- .ff-form .ff-action img,.ff-table .ff-action img{padding-right:4px;height:16px;width:16px;}
4
- .ff-form .ff-title,.ff-table .ff-title{position:relative;}.ff-form .ff-title .ff-active-title,.ff-table .ff-title .ff-active-title{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:pointer;display:inline-block;padding:4px 8px;font-weight:bold;font-size:14px;}.ff-form .ff-title .ff-active-title .ff-collapse,.ff-table .ff-title .ff-active-title .ff-collapse{display:inline-block;height:14px;width:14px;margin-right:4px;margin-top:2px;}
5
- .ff-form .ff-title .ff-active-title img,.ff-table .ff-title .ff-active-title img{height:16px;width:16px;overflow:hidden;margin-right:4px;}
6
- .ff-form .ff-title .ff-title-actions,.ff-table .ff-title .ff-title-actions{position:absolute;right:0;top:4px;padding:0 8px;}
7
- .ff-form .ff-collapse,.ff-table .ff-collapse{background:url(/assets/glyphicons-halflings.png) -313px -119px;}
8
- .ff-form .ff-collapsed.ff-collapse,.ff-table .ff-collapsed.ff-collapse{background:url(/assets/glyphicons-halflings.png) -456px -72px;}
9
- .ff-form .ff-field-hint,.ff-table .ff-field-hint{background:url(/assets/glyphicons-halflings.png) -456px -72px;}
10
- .ff-form .ff-tabs,.ff-table .ff-tabs{padding:4px 0;}.ff-form .ff-tabs ul.ff-tabs-header,.ff-table .ff-tabs ul.ff-tabs-header{list-style-type:none;margin:0;position:relative;border-bottom:1px solid #b1b1b1;}.ff-form .ff-tabs ul.ff-tabs-header li,.ff-table .ff-tabs ul.ff-tabs-header li{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:pointer;display:inline-block;padding:4px 8px;margin-left:8px;margin-bottom:-1px;border:1px solid transparent;}.ff-form .ff-tabs ul.ff-tabs-header li img,.ff-table .ff-tabs ul.ff-tabs-header li img{margin:0 4px;height:16px;width:16px;}
11
- .ff-form .ff-tabs ul.ff-tabs-header li.ff-selected,.ff-table .ff-tabs ul.ff-tabs-header li.ff-selected{background:#ffffcc;border:1px solid #b1b1b1;border-bottom:1px solid #ffffcc;}
12
- .ff-form .ff-tabs .ff-tab-actions,.ff-table .ff-tabs .ff-tab-actions{padding:4px 8px;background:#ffffcc;border:1px solid #b1b1b1;margin-top:-1px;}
13
- .ff-form .ff-tabs .ff-cols,.ff-table .ff-tabs .ff-cols{margin:4px 0 0 0;position:relative;font-size:13px;}.ff-form .ff-tabs .ff-cols .ff-col-100,.ff-table .ff-tabs .ff-cols .ff-col-100{width:100%;}
14
- .ff-form .ff-tabs .ff-cols .ff-col-50,.ff-table .ff-tabs .ff-cols .ff-col-50{width:50%;float:left;}
15
- .ff-form .ff-tabs .ff-cols .ff-col:first-child .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col:first-child .ff-col-inner{padding-right:4px;}
16
- .ff-form .ff-tabs .ff-cols .ff-col:last-child .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col:last-child .ff-col-inner{padding-left:4px;}
17
- .ff-form .ff-tabs .ff-cols .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col-inner{padding:0;position:relative;}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field{position:relative;border-bottom:1px solid #b1b1b1;}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field:first-child,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field:first-child{border-top:1px solid #b1b1b1;}
18
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label{display:table-cell;vertical-align:top;position:relative;width:150px;padding:4px 4px 4px 8px;background-color:#c8ffb0;border-right:3px solid transparent;}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label.ff-required,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label.ff-required{border-right:3px solid #cf0000;}
19
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label .ff-field-hint,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label .ff-field-hint{display:block;height:16px;width:16px;position:absolute;right:0;top:8px;background-position:-96px -96px;}
20
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value{display:table-cell;padding:4px;vertical-align:top;}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value .ff-field-actions,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value .ff-field-actions{display:inline-block;border-left:1px solid #b1b1b1;padding-left:8px;margin-left:8px;}
21
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-before,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-before{padding-right:8px;}
22
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-after,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-after{padding-left:8px;}
23
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-complex-part,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-complex-part{display:inline-block;padding-right:8px;}
24
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-field,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-field{display:inline-block;border:none;padding-right:8px;}
25
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-link,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-link{margin-left:8px;}
26
- .ff-form .ff-tabs .ff-cols:after,.ff-table .ff-tabs .ff-cols:after{content:".";display:block;clear:both;width:0;height:0;overflow:hidden;}
27
- .ff-form .ff-tabs input,.ff-table .ff-tabs input{padding:4px;margin:0;font-size:13px;}
28
- .ff-form .ff-tabs select,.ff-table .ff-tabs select{padding:4px;margin:0;font-size:13px;width:auto;}
29
- .ff-form .ff-tabs .ff-error input,.ff-table .ff-tabs .ff-error input{border:1px solid red;color:red;background:#fff8f8;}
30
- .ff-form .ff-form-errors,.ff-table .ff-form-errors{margin:8px;padding:8px;border:1px solid red;background:#fee;color:red;}
31
- .ff-form ul.ff-form-errors li,.ff-table ul.ff-form-errors li{margin-left:16px;}
32
- .ff-form .ff-field-errors,.ff-table .ff-field-errors{color:red;padding:4px 0;}
33
- .ff-form .ff-bottom-actions,.ff-table .ff-bottom-actions{padding:8px;border-top:1px solid #b1b1b1;background:#e0e0e0;}
34
- .ff-form table.ff-common-table,.ff-table table.ff-common-table{margin-top:8px;width:100%;border-collapse:collapse;}.ff-form table.ff-common-table thead,.ff-table table.ff-common-table thead{text-align:left;border-top:1px solid #3316ff;border-bottom:1px solid #3316ff;background:#c8ffb0;}.ff-form table.ff-common-table thead th,.ff-table table.ff-common-table thead th{font-weight:normal;padding:4px 8px;}
35
- .ff-form table.ff-common-table thead .ff-field-hint,.ff-table table.ff-common-table thead .ff-field-hint{display:inline-block;height:16px;width:16px;background-position:-96px -96px;}
36
- .ff-form table.ff-common-table tbody td,.ff-table table.ff-common-table tbody td{padding:4px 8px;border-bottom:1px solid #3316ff;}
37
- .ff-empty{color:#a0a0a0;}
38
- .ff-table-empty{padding:32px;text-align:center;background-color:#f6f6f6;border:1px solid #a0a0a0;border-top:none;}
39
- .ff-map{width:100%;height:100%;overflow:hidden;position:absolute;top:0;left:0;right:0;bottom:0;}.ff-map img{max-width:none;}
40
- .ff-form .ff-title,.ff-table .ff-title{background-color:#cfc8ff;border-bottom:2px solid #3316ff;border-top:2px solid #3316ff;}
41
- .tooltip-inner{white-space:pre;max-width:none;}
1
+ .user-select-none{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}
2
+ .ff-form,.ff-table{margin-bottom:16px}.ff-form .ff-action,.ff-table .ff-action{margin-left:4px;font-size:13px}.ff-form .ff-action:first-child,.ff-table .ff-action:first-child{margin-left:0}
3
+ .ff-form .ff-action img,.ff-table .ff-action img{padding-right:4px;height:16px;width:16px}
4
+ .ff-form .ff-title,.ff-table .ff-title{position:relative}.ff-form .ff-title .ff-active-title,.ff-table .ff-title .ff-active-title{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:pointer;display:inline-block;padding:4px 8px;font-weight:bold;font-size:14px}.ff-form .ff-title .ff-active-title .ff-collapse,.ff-table .ff-title .ff-active-title .ff-collapse{display:inline-block;height:14px;width:14px;margin-right:4px;margin-top:2px}
5
+ .ff-form .ff-title .ff-active-title img,.ff-table .ff-title .ff-active-title img{height:16px;width:16px;overflow:hidden;margin-right:4px}
6
+ .ff-form .ff-title .ff-title-actions,.ff-table .ff-title .ff-title-actions{position:absolute;right:0;top:4px;padding:0 8px}
7
+ .ff-form .ff-collapse,.ff-table .ff-collapse{background:url(/glyphicons-halflings.png) -313px -119px}
8
+ .ff-form .ff-collapsed.ff-collapse,.ff-table .ff-collapsed.ff-collapse{background:url(/glyphicons-halflings.png) -456px -72px}
9
+ .ff-form .ff-field-hint,.ff-table .ff-field-hint{background:url(/glyphicons-halflings.png) -456px -72px}
10
+ .ff-form .ff-tabs,.ff-table .ff-tabs{padding:4px 0}.ff-form .ff-tabs ul.ff-tabs-header,.ff-table .ff-tabs ul.ff-tabs-header{font-size:13px;list-style-type:none;margin:0;padding:0;position:relative;border-bottom:1px solid #b1b1b1}.ff-form .ff-tabs ul.ff-tabs-header li,.ff-table .ff-tabs ul.ff-tabs-header li{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;cursor:pointer;display:inline-block;padding:4px 8px;margin-left:8px;margin-bottom:-1px;border:1px solid transparent}.ff-form .ff-tabs ul.ff-tabs-header li img,.ff-table .ff-tabs ul.ff-tabs-header li img{margin:0 4px;height:16px;width:16px}
11
+ .ff-form .ff-tabs ul.ff-tabs-header li.ff-selected,.ff-table .ff-tabs ul.ff-tabs-header li.ff-selected{background:#ffc;border:1px solid #b1b1b1;border-bottom:1px solid #ffc}
12
+ .ff-form .ff-tabs .ff-tab-actions,.ff-table .ff-tabs .ff-tab-actions{padding:4px 8px;background:#ffc;border:1px solid #b1b1b1;margin-top:-1px}
13
+ .ff-form .ff-tabs .ff-cols,.ff-table .ff-tabs .ff-cols{margin:4px 0 0 0;position:relative;font-size:13px}.ff-form .ff-tabs .ff-cols .ff-col-100,.ff-table .ff-tabs .ff-cols .ff-col-100{width:100%}
14
+ .ff-form .ff-tabs .ff-cols .ff-col-50,.ff-table .ff-tabs .ff-cols .ff-col-50{width:50%;float:left}
15
+ .ff-form .ff-tabs .ff-cols .ff-col:first-child .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col:first-child .ff-col-inner{padding-right:4px}
16
+ .ff-form .ff-tabs .ff-cols .ff-col:last-child .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col:last-child .ff-col-inner{padding-left:4px}
17
+ .ff-form .ff-tabs .ff-cols .ff-col-inner,.ff-table .ff-tabs .ff-cols .ff-col-inner{padding:0;position:relative}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field{position:relative;border-bottom:1px solid #b1b1b1}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field:first-child,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field:first-child{border-top:1px solid #b1b1b1}
18
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label{display:table-cell;vertical-align:top;position:relative;width:150px;padding:4px 4px 4px 8px;background-color:#c8ffb0;border-right:3px solid transparent}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label.ff-required,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label.ff-required{border-right:3px solid #cf0000}
19
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label .ff-field-hint,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-label .ff-field-hint{display:block;height:16px;width:16px;position:absolute;right:0;top:8px;background-position:-96px -96px}
20
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value{display:table-cell;padding:4px;vertical-align:top}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value .ff-field-actions,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value .ff-field-actions{display:inline-block;border-left:1px solid #b1b1b1;padding-left:8px;margin-left:8px}
21
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value.ff-value-no-label,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value.ff-value-no-label{display:block}
22
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field{display:inline-block}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-link,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-link{margin-left:4px}
23
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-label,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-label{display:inline-block;padding:0 4px}
24
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field{display:inline}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field .ff-array-part,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field .ff-array-part{-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px;border-radius:5px;display:inline-block;margin:0 4px 4px 0;padding:2px 8px;border:1px solid #ddd;background:#fafafa}.ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field .ff-array-part .ff-action,.ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field .ff-array-part .ff-action{margin-left:4px}
25
+ .ff-form .ff-tabs .ff-cols:after,.ff-table .ff-tabs .ff-cols:after{content:".";display:block;clear:both;width:0;height:0;overflow:hidden}
26
+ .ff-form .ff-tabs input,.ff-table .ff-tabs input{padding:4px;margin:0;font-size:13px}
27
+ .ff-form .ff-tabs select,.ff-table .ff-tabs select{padding:4px;margin:0;font-size:13px;width:auto}
28
+ .ff-form .ff-tabs .ff-error input,.ff-table .ff-tabs .ff-error input{border:1px solid red;color:red;background:#fff8f8}
29
+ .ff-form .ff-tabs .ff-inline-hint,.ff-table .ff-tabs .ff-inline-hint{padding:4px 2px;color:#88f;font-size:12px}
30
+ .ff-form .ff-form-errors,.ff-table .ff-form-errors{margin:8px;padding:8px;border:1px solid red;background:#fee;color:red}
31
+ .ff-form ul.ff-form-errors li,.ff-table ul.ff-form-errors li{margin-left:16px}
32
+ .ff-form .ff-field-errors,.ff-table .ff-field-errors{color:red;padding:4px 0}
33
+ .ff-form .ff-bottom-actions,.ff-table .ff-bottom-actions{padding:8px;border-top:1px solid #b1b1b1;background:#e0e0e0}
34
+ .ff-form table.ff-common-table,.ff-table table.ff-common-table{margin-top:8px;width:100%;border-collapse:collapse}.ff-form table.ff-common-table thead,.ff-table table.ff-common-table thead{text-align:left;border-top:1px solid #3316ff;border-bottom:1px solid #3316ff;background:#c8ffb0}.ff-form table.ff-common-table thead th,.ff-table table.ff-common-table thead th{font-size:13px;font-weight:normal;padding:4px 8px}
35
+ .ff-form table.ff-common-table thead .ff-field-hint,.ff-table table.ff-common-table thead .ff-field-hint{display:inline-block;height:16px;width:16px;background-position:-96px -96px}
36
+ .ff-form table.ff-common-table tbody td,.ff-table table.ff-common-table tbody td{font-size:13px;padding:4px 8px;border-bottom:1px solid #3316ff;background:white}
37
+ .ff-form .ff-paginate .ff-totals,.ff-table .ff-paginate .ff-totals{padding:4px}.ff-form .ff-paginate .ff-totals span,.ff-table .ff-paginate .ff-totals span{padding-left:4px}
38
+ .ff-form .ff-field-before,.ff-table .ff-field-before{padding-right:8px}
39
+ .ff-form .ff-field-after,.ff-table .ff-field-after{padding-left:8px}
40
+ .ff-form .ff-complex-field .ff-complex-part,.ff-table .ff-complex-field .ff-complex-part{display:inline-block;padding-right:8px}
41
+ .ff-form .ff-complex-field .ff-field,.ff-table .ff-complex-field .ff-field{display:inline-block;border:none;padding-right:8px}
42
+ .ff-form .ff-select-field,.ff-table .ff-select-field{-webkit-border-radius:5px;-moz-border-radius:5px;-ms-border-radius:5px;-o-border-radius:5px;border-radius:5px;border:1px solid #ddd;padding:3px 3px 2px 3px;background:#ffc}.ff-form .ff-select-field .ff-select-label,.ff-table .ff-select-field .ff-select-label{min-width:100px}
43
+ .ff-empty{color:#a0a0a0}
44
+ .ff-table-empty{padding:32px;text-align:center;background-color:#f6f6f6;border:1px solid #a0a0a0;border-top:none}
45
+ .ff-map{width:100%;height:100%;overflow:hidden;position:absolute;top:0;left:0;right:0;bottom:0}.ff-map img{max-width:none}
46
+ .ff-form .ff-title,.ff-table .ff-title{background-color:#cfc8ff;border-bottom:2px solid #3316ff;border-top:2px solid #3316ff}
47
+ .tooltip-inner{white-space:pre;max-width:none}
@@ -11,6 +11,7 @@
11
11
  .ff-form .ff-action,
12
12
  .ff-table .ff-action {
13
13
  margin-left: 4px;
14
+ font-size: 13px;
14
15
  }
15
16
  .ff-form .ff-action:first-child,
16
17
  .ff-table .ff-action:first-child {
@@ -62,15 +63,15 @@
62
63
  }
63
64
  .ff-form .ff-collapse,
64
65
  .ff-table .ff-collapse {
65
- background: url(/assets/glyphicons-halflings.png) -313px -119px;
66
+ background: url(/glyphicons-halflings.png) -313px -119px;
66
67
  }
67
68
  .ff-form .ff-collapsed.ff-collapse,
68
69
  .ff-table .ff-collapsed.ff-collapse {
69
- background: url(/assets/glyphicons-halflings.png) -456px -72px;
70
+ background: url(/glyphicons-halflings.png) -456px -72px;
70
71
  }
71
72
  .ff-form .ff-field-hint,
72
73
  .ff-table .ff-field-hint {
73
- background: url(/assets/glyphicons-halflings.png) -456px -72px;
74
+ background: url(/glyphicons-halflings.png) -456px -72px;
74
75
  }
75
76
  .ff-form .ff-tabs,
76
77
  .ff-table .ff-tabs {
@@ -78,8 +79,10 @@
78
79
  }
79
80
  .ff-form .ff-tabs ul.ff-tabs-header,
80
81
  .ff-table .ff-tabs ul.ff-tabs-header {
82
+ font-size: 13px;
81
83
  list-style-type: none;
82
84
  margin: 0;
85
+ padding: 0;
83
86
  position: relative;
84
87
  border-bottom: 1px solid #b1b1b1;
85
88
  }
@@ -189,28 +192,43 @@
189
192
  padding-left: 8px;
190
193
  margin-left: 8px;
191
194
  }
192
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-before,
193
- .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-before {
194
- padding-right: 8px;
195
- }
196
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-after,
197
- .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-field-after {
198
- padding-left: 8px;
199
- }
200
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-complex-part,
201
- .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-complex-part {
202
- display: inline-block;
203
- padding-right: 8px;
195
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value.ff-value-no-label,
196
+ .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-value.ff-value-no-label {
197
+ display: block;
204
198
  }
205
- .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-field,
206
- .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-complex-field .ff-field {
199
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field,
200
+ .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field {
207
201
  display: inline-block;
208
- border: none;
209
- padding-right: 8px;
210
202
  }
211
203
  .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-link,
212
204
  .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-link {
213
- margin-left: 8px;
205
+ margin-left: 4px;
206
+ }
207
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-label,
208
+ .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-select-field .ff-select-label {
209
+ display: inline-block;
210
+ padding: 0 4px;
211
+ }
212
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field,
213
+ .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field {
214
+ display: inline;
215
+ }
216
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field .ff-array-part,
217
+ .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field .ff-array-part {
218
+ -webkit-border-radius: 5px;
219
+ -moz-border-radius: 5px;
220
+ -ms-border-radius: 5px;
221
+ -o-border-radius: 5px;
222
+ border-radius: 5px;
223
+ display: inline-block;
224
+ margin: 0 4px 4px 0;
225
+ padding: 2px 8px;
226
+ border: 1px solid #ddd;
227
+ background: #fafafa;
228
+ }
229
+ .ff-form .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field .ff-array-part .ff-action,
230
+ .ff-table .ff-tabs .ff-cols .ff-col-inner .ff-field .ff-array-field .ff-array-part .ff-action {
231
+ margin-left: 4px;
214
232
  }
215
233
  .ff-form .ff-tabs .ff-cols:after,
216
234
  .ff-table .ff-tabs .ff-cols:after {
@@ -240,6 +258,12 @@
240
258
  color: red;
241
259
  background: #fff8f8;
242
260
  }
261
+ .ff-form .ff-tabs .ff-inline-hint,
262
+ .ff-table .ff-tabs .ff-inline-hint {
263
+ padding: 4px 2px;
264
+ color: #88f;
265
+ font-size: 12px;
266
+ }
243
267
  .ff-form .ff-form-errors,
244
268
  .ff-table .ff-form-errors {
245
269
  margin: 8px;
@@ -278,6 +302,7 @@
278
302
  }
279
303
  .ff-form table.ff-common-table thead th,
280
304
  .ff-table table.ff-common-table thead th {
305
+ font-size: 13px;
281
306
  font-weight: normal;
282
307
  padding: 4px 8px;
283
308
  }
@@ -290,8 +315,52 @@
290
315
  }
291
316
  .ff-form table.ff-common-table tbody td,
292
317
  .ff-table table.ff-common-table tbody td {
318
+ font-size: 13px;
293
319
  padding: 4px 8px;
294
320
  border-bottom: 1px solid #3316ff;
321
+ background: white;
322
+ }
323
+ .ff-form .ff-paginate .ff-totals,
324
+ .ff-table .ff-paginate .ff-totals {
325
+ padding: 4px;
326
+ }
327
+ .ff-form .ff-paginate .ff-totals span,
328
+ .ff-table .ff-paginate .ff-totals span {
329
+ padding-left: 4px;
330
+ }
331
+ .ff-form .ff-field-before,
332
+ .ff-table .ff-field-before {
333
+ padding-right: 8px;
334
+ }
335
+ .ff-form .ff-field-after,
336
+ .ff-table .ff-field-after {
337
+ padding-left: 8px;
338
+ }
339
+ .ff-form .ff-complex-field .ff-complex-part,
340
+ .ff-table .ff-complex-field .ff-complex-part {
341
+ display: inline-block;
342
+ padding-right: 8px;
343
+ }
344
+ .ff-form .ff-complex-field .ff-field,
345
+ .ff-table .ff-complex-field .ff-field {
346
+ display: inline-block;
347
+ border: none;
348
+ padding-right: 8px;
349
+ }
350
+ .ff-form .ff-select-field,
351
+ .ff-table .ff-select-field {
352
+ -webkit-border-radius: 5px;
353
+ -moz-border-radius: 5px;
354
+ -ms-border-radius: 5px;
355
+ -o-border-radius: 5px;
356
+ border-radius: 5px;
357
+ border: 1px solid #ddd;
358
+ padding: 3px 3px 2px 3px;
359
+ background: #ffffcc;
360
+ }
361
+ .ff-form .ff-select-field .ff-select-label,
362
+ .ff-table .ff-select-field .ff-select-label {
363
+ min-width: 100px;
295
364
  }
296
365
  .ff-empty {
297
366
  color: #a0a0a0;
@@ -1,8 +1,8 @@
1
1
  .user-select-none { user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;}
2
2
  .rounded-corners(@radius: 5px) { -webkit-border-radius: @radius; -moz-border-radius: @radius; -ms-border-radius: @radius; -o-border-radius: @radius; border-radius: @radius; }
3
3
 
4
- @halflings: url(/assets/glyphicons-halflings.png);
5
- @halflings-white: url(/assets/glyphicons-halflings-white.png);
4
+ @halflings: url(/glyphicons-halflings.png);
5
+ @halflings-white: url(/glyphicons-halflings-white.png);
6
6
  @border-color: #b1b1b1;
7
7
  @selected-tab-color: #ffffcc;
8
8
  @base-font-size: 13px;
@@ -15,6 +15,7 @@
15
15
  margin-bottom: 16px;
16
16
  .ff-action {
17
17
  margin-left: 4px;
18
+ font-size: 13px;
18
19
  &:first-child { margin-left: 0; };
19
20
  img { padding-right: 4px; height: 16px; width: 16px; }
20
21
  }
@@ -33,7 +34,8 @@
33
34
  .ff-tabs {
34
35
  padding: 4px 0;
35
36
  ul.ff-tabs-header {
36
- list-style-type: none; margin: 0; position: relative;
37
+ font-size: 13px;
38
+ list-style-type: none; margin: 0; padding: 0; position: relative;
37
39
  border-bottom: 1px solid @border-color;
38
40
  li { .user-select-none; cursor: pointer; display: inline-block;
39
41
  padding: 4px 8px; margin-left: 8px; margin-bottom: -1px;
@@ -74,15 +76,21 @@
74
76
  .ff-value {
75
77
  display: table-cell; padding: 4px; vertical-align: top;
76
78
  .ff-field-actions { display: inline-block; border-left: 1px solid @border-color; padding-left: 8px; margin-left: 8px; }
77
- }
78
- .ff-field-before { padding-right: 8px; }
79
- .ff-field-after { padding-left: 8px; }
80
- .ff-complex-field {
81
- .ff-complex-part { display: inline-block; padding-right: 8px; }
82
- .ff-field { display: inline-block; border: none; padding-right: 8px; }
79
+ &.ff-value-no-label { display: block; }
83
80
  }
84
81
  .ff-select-field {
85
- .ff-select-link { margin-left: 8px; }
82
+ display: inline-block;
83
+ .ff-select-link { margin-left: 4px; }
84
+ .ff-select-label { display: inline-block; padding: 0 4px; }
85
+ }
86
+ .ff-array-field {
87
+ display: inline;
88
+ .ff-array-part {
89
+ .rounded-corners; display: inline-block; margin: 0 4px 4px 0;
90
+ padding: 2px 8px; border: 1px solid #ddd;
91
+ background: #fafafa;
92
+ .ff-action { margin-left: 4px; }
93
+ }
86
94
  }
87
95
  }
88
96
  }
@@ -91,6 +99,7 @@
91
99
  input { padding: 4px; margin: 0; font-size: @base-font-size; }
92
100
  select { padding: 4px; margin: 0; font-size: @base-font-size; width: auto; }
93
101
  .ff-error input { border: 1px solid red; color: red; background: #fff8f8; }
102
+ .ff-inline-hint { padding: 4px 2px; color: #88f; font-size: 12px; }
94
103
  }
95
104
  .ff-form-errors { margin: 8px; padding: 8px; border: 1px solid red; background: #fee; color: red;}
96
105
  ul.ff-form-errors { li { margin-left: 16px; } }
@@ -102,16 +111,40 @@
102
111
  border-top: 1px solid @blue-theme-border;
103
112
  border-bottom: 1px solid @blue-theme-border;
104
113
  background: @label-background;
105
- th { font-weight: normal; padding: 4px 8px; }
114
+ th { font-size: 13px; font-weight: normal; padding: 4px 8px; }
106
115
  .ff-field-hint { display: inline-block; height: 16px; width: 16px; background-position: -96px -96px; }
107
116
  }
108
117
  tbody {
109
- td { padding: 4px 8px; border-bottom: 1px solid @blue-theme-border; }
118
+ td {
119
+ font-size: 13px;
120
+ padding: 4px 8px; border-bottom: 1px solid @blue-theme-border;
121
+ background: white;
122
+ }
110
123
  }
111
124
  }
125
+ .ff-paginate {
126
+ .ff-totals {
127
+ padding: 4px;
128
+ span { padding-left: 4px; }
129
+ }
130
+ }
131
+ .ff-field-before { padding-right: 8px; }
132
+ .ff-field-after { padding-left: 8px; }
133
+ .ff-complex-field {
134
+ .ff-complex-part { display: inline-block; padding-right: 8px; }
135
+ .ff-field { display: inline-block; border: none; padding-right: 8px; }
136
+ }
137
+ .ff-select-field {
138
+ .rounded-corners;
139
+ border: 1px solid #ddd;
140
+ padding: 3px 3px 2px 3px;
141
+ background: @selected-tab-color;
142
+ .ff-select-label { min-width: 100px; }
143
+ }
112
144
  }
113
145
  .ff-empty { color: #a0a0a0; }
114
146
  .ff-table-empty { padding: 32px; text-align: center; background-color: #f6f6f6; border: 1px solid #a0a0a0; border-top: none; }
147
+ //.ff-table-body { background: white; }
115
148
 
116
149
  .ff-map {
117
150
  width:100%; height:100%; overflow:hidden; position: absolute;
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forma
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
5
- prerelease:
4
+ version: 0.1.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Dimitri Kurashvili
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-05-23 00:00:00.000000000 Z
11
+ date: 2014-01-04 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: bundler
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
@@ -30,23 +27,20 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: rake
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - '>='
36
32
  - !ruby/object:Gem::Version
37
33
  version: '0'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - '>='
44
39
  - !ruby/object:Gem::Version
45
40
  version: '0'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: rspec
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ~>
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ~>
60
53
  - !ruby/object:Gem::Version
@@ -62,7 +55,6 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: therubyracer
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - ~>
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - ~>
76
67
  - !ruby/object:Gem::Version
@@ -78,7 +69,6 @@ dependencies:
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: less
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
73
  - - ~>
84
74
  - !ruby/object:Gem::Version
@@ -86,7 +76,6 @@ dependencies:
86
76
  type: :development
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
80
  - - ~>
92
81
  - !ruby/object:Gem::Version
@@ -94,33 +83,29 @@ dependencies:
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: railties
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - '>='
100
88
  - !ruby/object:Gem::Version
101
89
  version: '3.1'
102
90
  type: :runtime
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ! '>='
94
+ - - '>='
108
95
  - !ruby/object:Gem::Version
109
96
  version: '3.1'
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: activesupport
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
- - - ! '>='
101
+ - - '>='
116
102
  - !ruby/object:Gem::Version
117
103
  version: '0'
118
104
  type: :runtime
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
- - - ! '>='
108
+ - - '>='
124
109
  - !ruby/object:Gem::Version
125
110
  version: '0'
126
111
  description: rich forms for ruby
@@ -157,6 +142,7 @@ files:
157
142
  - test/field_test.rb
158
143
  - test/form_test.rb
159
144
  - test/test_helper.rb
145
+ - test/utils_test.rb
160
146
  - vendor/assets/images/ff-icons.png
161
147
  - vendor/assets/javascripts/forma.js
162
148
  - vendor/assets/stylesheets/forma-min.css
@@ -165,27 +151,26 @@ files:
165
151
  homepage: http://github.com/dimakura/forma
166
152
  licenses:
167
153
  - MIT
154
+ metadata: {}
168
155
  post_install_message:
169
156
  rdoc_options: []
170
157
  require_paths:
171
158
  - lib
172
159
  required_ruby_version: !ruby/object:Gem::Requirement
173
- none: false
174
160
  requirements:
175
- - - ! '>='
161
+ - - '>='
176
162
  - !ruby/object:Gem::Version
177
163
  version: '0'
178
164
  required_rubygems_version: !ruby/object:Gem::Requirement
179
- none: false
180
165
  requirements:
181
- - - ! '>='
166
+ - - '>='
182
167
  - !ruby/object:Gem::Version
183
168
  version: '0'
184
169
  requirements: []
185
170
  rubyforge_project:
186
- rubygems_version: 1.8.24
171
+ rubygems_version: 2.0.3
187
172
  signing_key:
188
- specification_version: 3
173
+ specification_version: 4
189
174
  summary: rich forms for ruby
190
175
  test_files:
191
176
  - spec/config_spec.rb
@@ -197,3 +182,4 @@ test_files:
197
182
  - test/field_test.rb
198
183
  - test/form_test.rb
199
184
  - test/test_helper.rb
185
+ - test/utils_test.rb