glib-web 3.10.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/helpers/glib/enum_helper.rb +12 -5
- data/app/helpers/glib/forms_helper.rb +4 -3
- data/app/helpers/glib/json_ui/styling_helper.rb +1 -1
- data/app/helpers/glib/json_ui/view_builder/fields.rb +11 -34
- data/app/helpers/glib/json_ui/view_builder/panels.rb +98 -7
- data/app/models/concerns/glib/enum_humanization.rb +5 -5
- data/app/models/concerns/glib/soft_deletable.rb +103 -36
- data/app/models/glib/application_record.rb +10 -8
- data/app/views/json_ui/garage/forms/file_upload.json.jbuilder +2 -2
- data/app/views/json_ui/garage/forms/index.json.jbuilder +3 -6
- data/app/views/json_ui/garage/forms/selects.json.jbuilder +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29bf404f48d26b824a2e10162a8ec01be7a13754027b4bd3e6b625c1255dbb80
|
4
|
+
data.tar.gz: 5ed5e65ac4e3cbd23180de8fd03f2a98746b4a5df787d8d69f5f25c9d0f66bbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c42ecfe127600686502e3721e37a05bf62ce0b194801f42c0c1d49c5df09d12f86cdd8435e682686de4d2c2cf7d288ca6c205cf3da48a452d1e2aadb1f685e1
|
7
|
+
data.tar.gz: '082866223cfe93db662d532a2030bc60428d82ae1d62c2b98c9910d2e37c3054ca5120d851cb021210eaca9cf60828483a3f91acc70061110d3581dcf17d015a'
|
@@ -1,10 +1,17 @@
|
|
1
1
|
module Glib
|
2
2
|
module EnumHelper
|
3
|
-
def glib_enum_options(clazz, enum_field, keys
|
4
|
-
|
5
|
-
|
6
|
-
keys
|
7
|
-
|
3
|
+
def glib_enum_options(clazz, enum_field, keys: nil, include_hints: false)
|
4
|
+
enum_name = enum_field.to_s
|
5
|
+
keys ||= clazz.defined_enums[enum_name].keys
|
6
|
+
keys.map do |i|
|
7
|
+
text = clazz.glib_enum_humanize(enum_field, i)
|
8
|
+
if include_hints
|
9
|
+
i18n_key = clazz.model_name.i18n_key
|
10
|
+
hint = I18n.t("dt_models.#{i18n_key}.#{enum_name.pluralize}.#{i}.hint")
|
11
|
+
text += " #{hint}"
|
12
|
+
end
|
13
|
+
{ text: text, value: i }
|
14
|
+
end
|
8
15
|
end
|
9
16
|
|
10
17
|
# See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones and description
|
@@ -1,15 +1,16 @@
|
|
1
1
|
module Glib
|
2
2
|
module FormsHelper
|
3
3
|
def glib_form_field_label(model_name, prop, args = {})
|
4
|
-
I18n.t("dt_models.#{model_name}.#{prop}.label", args.merge(default: nil)) ||
|
4
|
+
I18n.t("dt_models.#{model_name}.#{prop}.label", **args.merge(default: nil)) ||
|
5
|
+
I18n.t("activerecord.attributes.#{model_name}.#{prop}", **args)
|
5
6
|
end
|
6
7
|
|
7
8
|
def glib_form_hint_label(model_name, prop, args = {})
|
8
|
-
I18n.t("dt_models.#{model_name}.#{prop}.hint", args.merge(default: nil))
|
9
|
+
I18n.t("dt_models.#{model_name}.#{prop}.hint", **args.merge(default: nil))
|
9
10
|
end
|
10
11
|
|
11
12
|
def glib_form_placeholder_label(model_name, prop, args = {})
|
12
|
-
I18n.t("dt_models.#{model_name}.#{prop}.placeholder", args.merge(default: nil))
|
13
|
+
I18n.t("dt_models.#{model_name}.#{prop}.placeholder", **args.merge(default: nil))
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
@@ -48,8 +48,8 @@ class Glib::JsonUi::ViewBuilder
|
|
48
48
|
# Override
|
49
49
|
def created
|
50
50
|
if @prop && (form = page.current_form)
|
51
|
-
|
52
|
-
context =
|
51
|
+
association = form.nested_associations.last
|
52
|
+
context = association || form
|
53
53
|
|
54
54
|
context.field_assert_respond_to(@prop)
|
55
55
|
|
@@ -57,10 +57,11 @@ class Glib::JsonUi::ViewBuilder
|
|
57
57
|
@label ||= context.field_label(@prop, @label_args || {})
|
58
58
|
@hint ||= context.hint_label(@prop, @hint_args || {})
|
59
59
|
@placeholder ||= context.placeholder_label(@prop, @placeholder_args || {})
|
60
|
+
@validation ||= context.field_validation(@prop)
|
60
61
|
|
61
|
-
if
|
62
|
-
|
63
|
-
@
|
62
|
+
if form.current_dynamic_group.nil?
|
63
|
+
# This is not relevant inside a dynamic group
|
64
|
+
@value ||= context.field_value(@prop)
|
64
65
|
end
|
65
66
|
end
|
66
67
|
json.name @name
|
@@ -201,47 +202,23 @@ class Glib::JsonUi::ViewBuilder
|
|
201
202
|
end
|
202
203
|
|
203
204
|
class DynamicGroup < AbstractField
|
205
|
+
include Panels::ModelPanel
|
206
|
+
|
204
207
|
string :titlePrefix
|
205
|
-
|
208
|
+
panels_builder :content, :template
|
206
209
|
hash :groupFieldProperties
|
207
210
|
|
208
211
|
# NOTE: Consider using sub-panel instead (e.g. groupTemplate)
|
209
212
|
# views :groupTemplateViews
|
210
213
|
|
211
|
-
attr_writer :model
|
212
|
-
|
213
|
-
def model(model)
|
214
|
-
@model = model
|
215
|
-
@model_name = @model.class.model_name.singular
|
216
|
-
end
|
217
|
-
|
218
|
-
def field_name(prop, multiple)
|
219
|
-
prop
|
220
|
-
end
|
221
|
-
|
222
|
-
def field_label(prop, args)
|
223
|
-
@delegate_class.field_label(@model, @model_name, prop, args)
|
224
|
-
end
|
225
|
-
|
226
|
-
def hint_label(prop, args)
|
227
|
-
@delegate_class.hint_label(@model_name, prop, args)
|
228
|
-
end
|
229
|
-
|
230
|
-
def placeholder_label(prop, args)
|
231
|
-
@delegate_class.placeholder_label(@model_name, prop, args)
|
232
|
-
end
|
233
|
-
|
234
|
-
def field_assert_respond_to(prop)
|
235
|
-
raise "Please specify a model for #{self.class.component_name} before using its property" unless @model
|
236
|
-
@delegate_class.field_assert_respond_to(@model, prop)
|
237
|
-
end
|
238
|
-
|
239
214
|
def content(value)
|
240
215
|
@delegate_class = Glib::JsonUi::ViewBuilder::Panels::Form
|
241
216
|
form = page.current_form
|
242
217
|
|
243
218
|
form.current_dynamic_group = self
|
219
|
+
form.nested_associations << self
|
244
220
|
value.call(page.content_builder([:template]))
|
221
|
+
form.nested_associations.pop
|
245
222
|
form.current_dynamic_group = nil
|
246
223
|
end
|
247
224
|
end
|
@@ -1,12 +1,17 @@
|
|
1
1
|
class Glib::JsonUi::ViewBuilder
|
2
2
|
module Panels
|
3
3
|
class Form < View
|
4
|
+
attr_reader :model_name # See Panels::Form.field_name
|
4
5
|
attr_accessor :current_dynamic_group
|
5
6
|
|
6
7
|
action :onSubmit
|
7
8
|
string :paramNameForFormData
|
8
9
|
bool :local
|
9
10
|
|
11
|
+
def nested_associations
|
12
|
+
@nested_associations ||= []
|
13
|
+
end
|
14
|
+
|
10
15
|
# TODO: Enable this when we know it won't break existing apps.
|
11
16
|
# Even for pure client-side apps, this is required because form.validate() requires a URL to construct form data.
|
12
17
|
# required :url
|
@@ -39,19 +44,45 @@ class Glib::JsonUi::ViewBuilder
|
|
39
44
|
end
|
40
45
|
|
41
46
|
def field_name(prop, multiple)
|
42
|
-
self.class.field_name(@model,
|
47
|
+
self.class.field_name(@model, prop, multiple, page)
|
43
48
|
end
|
44
49
|
|
45
|
-
def self.field_name(model,
|
50
|
+
def self.field_name(model, prop, multiple, page)
|
51
|
+
form = page.current_form
|
52
|
+
include_form_model = true
|
53
|
+
reversed_model_panels = []
|
54
|
+
form.nested_associations.reverse.each do |panel|
|
55
|
+
if panel.is_a?(Fields::DynamicGroup)
|
56
|
+
include_form_model = false
|
57
|
+
break # Remove anything before Fields::DynamicGroup
|
58
|
+
end
|
59
|
+
reversed_model_panels << panel
|
60
|
+
end
|
61
|
+
|
62
|
+
name = include_form_model ? form.model_name : ''
|
63
|
+
reversed_model_panels.reverse.each do |panel|
|
64
|
+
index_exists = !panel.assoc_order_index.nil?
|
65
|
+
field_name = index_exists ? panel.model_name.pluralize : panel.model_name
|
66
|
+
name += "[#{field_name}_attributes]"
|
67
|
+
if index_exists
|
68
|
+
index = panel.assoc_order_index == :auto ? '' : panel.assoc_order_index
|
69
|
+
name += "[#{index}]"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
46
73
|
suffix = is_array_association?(model, prop) || multiple ? '[]' : ''
|
47
|
-
"#{
|
74
|
+
"#{name}[#{prop}]#{suffix}"
|
48
75
|
end
|
49
76
|
|
50
77
|
def field_value(prop)
|
51
|
-
|
52
|
-
|
78
|
+
self.class.field_value(@model, prop)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.field_value(model, prop)
|
82
|
+
if is_array_association?(model, prop)
|
83
|
+
model.send(prop)&.map { |record| record.id }
|
53
84
|
else
|
54
|
-
|
85
|
+
model.send(prop)
|
55
86
|
end
|
56
87
|
end
|
57
88
|
|
@@ -84,8 +115,12 @@ class Glib::JsonUi::ViewBuilder
|
|
84
115
|
end
|
85
116
|
|
86
117
|
def field_validation(prop)
|
118
|
+
self.class.field_validation(@model, prop)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.field_validation(model, prop)
|
87
122
|
validations = {}
|
88
|
-
|
123
|
+
model.class.validators_on(prop).each do |validator|
|
89
124
|
if validator.kind == :presence
|
90
125
|
validations[:required] = { message: 'Required' }
|
91
126
|
end
|
@@ -332,5 +367,61 @@ class Glib::JsonUi::ViewBuilder
|
|
332
367
|
# end
|
333
368
|
end
|
334
369
|
|
370
|
+
module ModelPanel
|
371
|
+
attr_reader :model_name # See Panels::Form.field_name
|
372
|
+
attr_reader :assoc_order_index
|
373
|
+
|
374
|
+
def model(model)
|
375
|
+
@model = model
|
376
|
+
@model_name = @model.class.model_name.singular
|
377
|
+
end
|
378
|
+
|
379
|
+
def order_index(index)
|
380
|
+
@assoc_order_index = index
|
381
|
+
end
|
382
|
+
|
383
|
+
def field_name(prop, multiple)
|
384
|
+
@delegate_class.field_name(@model, prop, multiple, page)
|
385
|
+
end
|
386
|
+
|
387
|
+
def field_value(prop)
|
388
|
+
@delegate_class.field_value(@model, prop)
|
389
|
+
end
|
390
|
+
|
391
|
+
def field_label(prop, args)
|
392
|
+
@delegate_class.field_label(@model, @model_name, prop, args)
|
393
|
+
end
|
394
|
+
|
395
|
+
def hint_label(prop, args)
|
396
|
+
@delegate_class.hint_label(@model_name, prop, args)
|
397
|
+
end
|
398
|
+
|
399
|
+
def placeholder_label(prop, args)
|
400
|
+
@delegate_class.placeholder_label(@model_name, prop, args)
|
401
|
+
end
|
402
|
+
|
403
|
+
def field_validation(prop)
|
404
|
+
@delegate_class.field_validation(@model, prop)
|
405
|
+
end
|
406
|
+
|
407
|
+
def field_assert_respond_to(prop)
|
408
|
+
raise "Please specify a model for #{self.class.component_name} before using its property" unless @model
|
409
|
+
@delegate_class.field_assert_respond_to(@model, prop)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
class Association < View
|
414
|
+
include ModelPanel
|
415
|
+
|
416
|
+
def childViews(value)
|
417
|
+
@delegate_class = Glib::JsonUi::ViewBuilder::Panels::Form
|
418
|
+
form = page.current_form
|
419
|
+
|
420
|
+
form.nested_associations << self
|
421
|
+
json.set!(:childViews) { value.call(page.view_builder) }
|
422
|
+
form.nested_associations.pop
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
335
426
|
end
|
336
427
|
end
|
@@ -11,19 +11,19 @@ module Glib
|
|
11
11
|
extend ClassMethods
|
12
12
|
end
|
13
13
|
|
14
|
-
def glib_enum_humanize(enum_name,
|
15
|
-
self.class.glib_enum_humanize(enum_name, send(enum_name)
|
14
|
+
def glib_enum_humanize(enum_name, default_enum_value = nil)
|
15
|
+
self.class.glib_enum_humanize(enum_name, send(enum_name) || default_enum_value)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
module ClassMethods
|
20
|
-
def glib_enum_humanize(enum_name, enum_value,
|
20
|
+
def glib_enum_humanize(enum_name, enum_value, default_translation = nil)
|
21
21
|
if enum_value
|
22
|
-
if
|
22
|
+
if default_translation.nil? && Rails.env.development?
|
23
23
|
i18n_key = "activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}"
|
24
24
|
I18n.t(i18n_key, raise: I18n::MissingTranslationData)
|
25
25
|
else
|
26
|
-
I18n.t(i18n_key, default:
|
26
|
+
I18n.t(i18n_key, default: default_translation)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -1,40 +1,72 @@
|
|
1
1
|
# To use this, simply:
|
2
2
|
# - Include the module
|
3
3
|
# - Add this migration: `add_column :model, :deleted_at, :datetime, null: true`
|
4
|
-
#
|
5
|
-
# After that, any call to `destroy` will automatically be a soft-deletion.
|
6
4
|
module Glib
|
7
5
|
module SoftDeletable
|
8
6
|
extend ActiveSupport::Concern
|
9
7
|
|
8
|
+
module ClassMethods
|
9
|
+
def auto_hide_soft_deleted_records
|
10
|
+
column_name = :deleted_at
|
11
|
+
default_scope { where(column_name => nil) }
|
12
|
+
|
13
|
+
if Rails.env.development? || Rails.env.test?
|
14
|
+
raise ActiveRecord::ConfigurationError, "#{table_name}.#{column_name} need to be indexed" unless connection.index_exists?(table_name, column_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# `behaviour` can be either `:soft_destroy` or `:hard_destroy``
|
19
|
+
def on_mark_for_destruction(behaviour)
|
20
|
+
@__glib_mark_for_destruction_behaviour = behaviour
|
21
|
+
end
|
22
|
+
|
23
|
+
def __mark_for_destruction_behaviour
|
24
|
+
@__glib_mark_for_destruction_behaviour ||= :disallowed
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
10
28
|
included do
|
11
|
-
|
29
|
+
extend ClassMethods
|
30
|
+
|
12
31
|
scope :with_deleted, -> { unscope(where: :deleted_at) }
|
13
32
|
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
33
|
+
# Override
|
34
|
+
def destroy
|
35
|
+
if marked_for_destruction?
|
36
|
+
case (behaviour = self.class.__mark_for_destruction_behaviour)
|
37
|
+
when :soft_destroy
|
38
|
+
soft_destroy
|
39
|
+
when :hard_destroy
|
40
|
+
# Make sure that the associated records are handled the same way as this record (i.e. the parent record).
|
41
|
+
mark_applicable_associations_for_destruction
|
42
|
+
|
43
|
+
hard_destroy
|
44
|
+
when :disallowed
|
45
|
+
raise_hard_delete_disallowed
|
46
|
+
else
|
47
|
+
raise "Unsupported on_mark_for_destruction behaviour: #{behaviour}"
|
48
|
+
end
|
49
|
+
|
50
|
+
return true # Tell Rails that destroy has succeeded
|
51
|
+
end
|
21
52
|
|
22
|
-
|
53
|
+
raise_hard_delete_disallowed
|
23
54
|
end
|
24
55
|
|
25
|
-
|
26
|
-
def
|
27
|
-
|
56
|
+
|
57
|
+
def hard_destroy
|
58
|
+
method(:destroy).super_method.call
|
28
59
|
end
|
29
60
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
61
|
+
def soft_destroy
|
62
|
+
ActiveRecord::Base.transaction do
|
63
|
+
_soft_destroy
|
64
|
+
end
|
65
|
+
end
|
34
66
|
|
67
|
+
def undo_soft_destroy
|
35
68
|
ActiveRecord::Base.transaction do
|
36
|
-
|
37
|
-
revive_associated_records
|
69
|
+
_undo_soft_destroy
|
38
70
|
end
|
39
71
|
end
|
40
72
|
|
@@ -43,34 +75,69 @@ module Glib
|
|
43
75
|
deleted_at.present?
|
44
76
|
end
|
45
77
|
|
78
|
+
# Bypass validation because it is impossible to ensure records that have circular dependencies are valid at all time because
|
79
|
+
# records are updated/validated one at a time.
|
80
|
+
# Besides, it's probably not a good idea to prevent soft-deletion of objects that are already not valid.
|
81
|
+
def _soft_destroy
|
82
|
+
update_columns(deleted_at: DateTime.current)
|
83
|
+
soft_destroy_associated_records
|
84
|
+
end
|
85
|
+
|
86
|
+
def _undo_soft_destroy
|
87
|
+
undo_soft_destroy_associated_records
|
88
|
+
update_columns(deleted_at: nil)
|
89
|
+
end
|
90
|
+
|
46
91
|
private
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
92
|
+
def mark_applicable_associations_for_destruction
|
93
|
+
self.class.reflect_on_all_associations.each do |assoc|
|
94
|
+
next unless assoc.options[:dependent] == :destroy
|
95
|
+
|
96
|
+
process_soft_deletable_relationship(assoc.name) do |record|
|
97
|
+
record.mark_for_destruction
|
98
|
+
end
|
51
99
|
end
|
52
100
|
end
|
53
101
|
|
54
|
-
def
|
55
|
-
ActiveRecord::
|
56
|
-
|
57
|
-
|
102
|
+
def raise_hard_delete_disallowed
|
103
|
+
raise ActiveRecord::ConfigurationError, "Hard deletion is not allowed for #{self.class.name}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def process_soft_deletable_relationship(relationship, &block)
|
107
|
+
association(relationship) # Raises an exception if relationship doesn't exist. This is a security safe guard.
|
108
|
+
|
109
|
+
# Use `send()` instead of `association(relationship).scope` because the latter returns new record objects as opposed
|
110
|
+
# to existing objects which could cause a side effect.
|
111
|
+
relation = send(relationship)
|
112
|
+
|
113
|
+
if relation.respond_to?(:find_each)
|
114
|
+
relation.find_each do |record|
|
115
|
+
block.call(record)
|
116
|
+
end
|
117
|
+
else
|
118
|
+
block.call(relation) unless relation.nil? # Process a single record
|
58
119
|
end
|
59
120
|
end
|
60
121
|
|
61
|
-
def
|
62
|
-
|
122
|
+
def soft_destroy_associated_records
|
123
|
+
soft_deletable_associations.each do |relationship|
|
124
|
+
process_soft_deletable_relationship(relationship) { |record| record._soft_destroy }
|
125
|
+
end
|
63
126
|
end
|
64
127
|
|
65
|
-
def
|
66
|
-
|
128
|
+
def undo_soft_destroy_associated_records
|
129
|
+
soft_deletable_associations.each do |relationship|
|
130
|
+
process_soft_deletable_relationship(relationship) { |record| record._undo_soft_destroy }
|
131
|
+
end
|
67
132
|
end
|
68
133
|
|
69
134
|
# This should return an array of all associated records of an object that
|
70
|
-
# should be soft deleted with it
|
71
|
-
#
|
72
|
-
#
|
73
|
-
|
135
|
+
# should be soft deleted with it. This provides a non-intrusive way that can
|
136
|
+
# co-exist with hard deletion (e.g. using `dependent: :destroy`), which means
|
137
|
+
# that the model can use both for different purposes.
|
138
|
+
# For example, a model may use soft-deletion for archiving and hard-deletion
|
139
|
+
# for editing (e.g. using `accepts_nested_attributes_for``)
|
140
|
+
def soft_deletable_associations
|
74
141
|
[]
|
75
142
|
end
|
76
143
|
end
|
@@ -7,14 +7,16 @@ module Glib
|
|
7
7
|
scope :created_asc, -> { order(created_at: :asc) }
|
8
8
|
scope :created_desc, -> { order(created_at: :desc) }
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
# end
|
10
|
+
def self.glib_has_one_attached(field_name)
|
11
|
+
has_one_attached field_name
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
define_method("#{field_name}=") do |value|
|
14
|
+
if value == ''
|
15
|
+
send(field_name).detach
|
16
|
+
else
|
17
|
+
super(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
19
21
|
end
|
20
22
|
end
|
@@ -25,8 +25,8 @@ page.form options.merge(childViews: ->(form) do
|
|
25
25
|
form.fields_file name: 'user[pdf1][]', width: 'matchParent', label: 'PDF Document', accepts: rules, directUploadUrl: rails_direct_uploads_url,
|
26
26
|
placeholderView: { type: 'image', width: 100, height: 100, url: 'data:image/webp;base64,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' }
|
27
27
|
|
28
|
-
rules = { fileType: 'pdf', maxFileSize: 5000 }
|
29
|
-
form.fields_file name: 'user[pdf2][]', width: 'matchParent', label: 'PDF
|
28
|
+
rules = { fileType: ['pdf', 'image'], maxFileSize: 5000 }
|
29
|
+
form.fields_file name: 'user[pdf2][]', width: 'matchParent', label: 'PDF/Image File', accepts: rules, directUploadUrl: rails_direct_uploads_url
|
30
30
|
|
31
31
|
rules = { fileType: 'zip', maxFileSize: 5000 }
|
32
32
|
form.fields_file name: 'user[zip][]', width: 'matchParent', label: 'ZIP Document', accepts: rules, directUploadUrl: rails_direct_uploads_url
|
@@ -50,8 +50,9 @@ page.list sections: [
|
|
50
50
|
template.thumbnail title: 'Submit on Change', onClick: ->(action) do
|
51
51
|
action.windows_open url: json_ui_garage_url(path: 'forms/submit_on_change')
|
52
52
|
end
|
53
|
-
|
54
|
-
|
53
|
+
template.thumbnail title: 'Dynamic Group', onClick: ->(action) do
|
54
|
+
action.windows_open url: json_ui_garage_url(path: 'forms/dynamic_group')
|
55
|
+
end
|
55
56
|
end
|
56
57
|
end, ->(section) do
|
57
58
|
section.header padding: glib_json_padding_list, childViews: ->(header) do
|
@@ -105,7 +106,6 @@ page.list sections: [
|
|
105
106
|
section.header padding: glib_json_padding_list, childViews: ->(header) do
|
106
107
|
header.h2 text: 'Web Only'
|
107
108
|
end
|
108
|
-
|
109
109
|
section.rows builder: ->(template) do
|
110
110
|
template.thumbnail title: 'Basic Rich Text Editor', onClick: ->(action) do
|
111
111
|
action.windows_open url: json_ui_garage_url(path: 'forms/rich_text')
|
@@ -124,9 +124,6 @@ page.list sections: [
|
|
124
124
|
end
|
125
125
|
|
126
126
|
section.rows builder: ->(template) do
|
127
|
-
template.thumbnail title: 'Dynamic Group', onClick: ->(action) do
|
128
|
-
action.windows_open url: json_ui_garage_url(path: 'forms/dynamic_group')
|
129
|
-
end
|
130
127
|
template.thumbnail title: 'Dynamic Select', onClick: ->(action) do
|
131
128
|
action.windows_open url: json_ui_garage_url(path: 'forms/dynamic_select')
|
132
129
|
end
|
@@ -56,7 +56,7 @@ page.form url: json_ui_garage_url(path: 'forms/generic_post'), method: 'post', p
|
|
56
56
|
options = []
|
57
57
|
languages.each do |group, sub|
|
58
58
|
options << { type: 'label', text: group }
|
59
|
-
options.concat(sub.map { |k, v| { value: k, text: v } })
|
59
|
+
options.concat(sub.map { |k, v| { value: k, text: v, subtitle: 'Country' } })
|
60
60
|
options << { type: 'divider' }
|
61
61
|
end
|
62
62
|
|