glib-web 3.10.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|