glib-web 3.9.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 +26 -9
- data/app/helpers/glib/json_ui/view_builder/panels.rb +136 -12
- data/app/helpers/glib/json_ui/view_builder.rb +3 -0
- data/app/models/concerns/glib/enum_humanization.rb +9 -4
- 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/dynamic_group.json.jbuilder +15 -3
- 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
- data/app/views/json_ui/garage/forms/show_hide.json.jbuilder +23 -1
- data/app/views/json_ui/garage/views/images.json.jbuilder +0 -5
- 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,15 +48,21 @@ class Glib::JsonUi::ViewBuilder
|
|
48
48
|
# Override
|
49
49
|
def created
|
50
50
|
if @prop && (form = page.current_form)
|
51
|
-
form.
|
51
|
+
association = form.nested_associations.last
|
52
|
+
context = association || form
|
52
53
|
|
53
|
-
|
54
|
-
@value ||= form.field_value(@prop)
|
55
|
-
@label ||= form.field_label(@prop, @label_args || {})
|
56
|
-
@hint ||= form.hint_label(@prop, @hint_args || {})
|
57
|
-
@placeholder ||= form.placeholder_label(@prop, @placeholder_args || {})
|
54
|
+
context.field_assert_respond_to(@prop)
|
58
55
|
|
59
|
-
@
|
56
|
+
@name ||= context.field_name(@prop, @multiple || false)
|
57
|
+
@label ||= context.field_label(@prop, @label_args || {})
|
58
|
+
@hint ||= context.hint_label(@prop, @hint_args || {})
|
59
|
+
@placeholder ||= context.placeholder_label(@prop, @placeholder_args || {})
|
60
|
+
@validation ||= context.field_validation(@prop)
|
61
|
+
|
62
|
+
if form.current_dynamic_group.nil?
|
63
|
+
# This is not relevant inside a dynamic group
|
64
|
+
@value ||= context.field_value(@prop)
|
65
|
+
end
|
60
66
|
end
|
61
67
|
json.name @name
|
62
68
|
json.value @value if @value
|
@@ -196,17 +202,28 @@ class Glib::JsonUi::ViewBuilder
|
|
196
202
|
end
|
197
203
|
|
198
204
|
class DynamicGroup < AbstractField
|
205
|
+
include Panels::ModelPanel
|
206
|
+
|
199
207
|
string :titlePrefix
|
200
208
|
panels_builder :content, :template
|
201
209
|
hash :groupFieldProperties
|
202
210
|
|
203
211
|
# NOTE: Consider using sub-panel instead (e.g. groupTemplate)
|
204
212
|
# views :groupTemplateViews
|
213
|
+
|
214
|
+
def content(value)
|
215
|
+
@delegate_class = Glib::JsonUi::ViewBuilder::Panels::Form
|
216
|
+
form = page.current_form
|
217
|
+
|
218
|
+
form.current_dynamic_group = self
|
219
|
+
form.nested_associations << self
|
220
|
+
value.call(page.content_builder([:template]))
|
221
|
+
form.nested_associations.pop
|
222
|
+
form.current_dynamic_group = nil
|
223
|
+
end
|
205
224
|
end
|
206
225
|
|
207
226
|
class RadioGroup < AbstractField
|
208
|
-
# string :name
|
209
|
-
# string :value
|
210
227
|
views :childViews
|
211
228
|
bool :row
|
212
229
|
|
@@ -1,15 +1,22 @@
|
|
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
|
5
|
+
attr_accessor :current_dynamic_group
|
6
|
+
|
4
7
|
action :onSubmit
|
5
8
|
string :paramNameForFormData
|
6
9
|
bool :local
|
7
10
|
|
11
|
+
def nested_associations
|
12
|
+
@nested_associations ||= []
|
13
|
+
end
|
14
|
+
|
8
15
|
# TODO: Enable this when we know it won't break existing apps.
|
9
16
|
# Even for pure client-side apps, this is required because form.validate() requires a URL to construct form data.
|
10
17
|
# required :url
|
11
18
|
|
12
|
-
def is_array_association?(prop)
|
19
|
+
def self.is_array_association?(model, prop)
|
13
20
|
# # Not all model is ActiveRecord
|
14
21
|
# if @model.class.respond_to?(:reflect_on_association)
|
15
22
|
# return @model.class.reflect_on_association(prop).macro
|
@@ -17,42 +24,103 @@ class Glib::JsonUi::ViewBuilder
|
|
17
24
|
# false
|
18
25
|
|
19
26
|
# Not all model is ActiveRecord
|
20
|
-
|
27
|
+
model.class.try(:reflect_on_association, prop)&.macro == :has_many
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.is_single_association?(model, prop)
|
31
|
+
# Not all model is ActiveRecord
|
32
|
+
model.class.try(:reflect_on_association, prop)&.macro == :belongs_to
|
21
33
|
end
|
22
34
|
|
23
35
|
def field_assert_respond_to(prop)
|
36
|
+
self.class.field_assert_respond_to(@model, prop)
|
37
|
+
# # Identify a prop being used on a nil model or incorrect model.
|
38
|
+
# raise "Invalid property `#{prop}` on '#{@model.class}'" unless @model.respond_to?(prop)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.field_assert_respond_to(model, prop)
|
24
42
|
# Identify a prop being used on a nil model or incorrect model.
|
25
|
-
raise "Invalid property `#{prop}` on '#{
|
43
|
+
raise "Invalid property `#{prop}` on '#{model.class}'" unless model.respond_to?(prop)
|
26
44
|
end
|
27
45
|
|
28
46
|
def field_name(prop, multiple)
|
29
|
-
|
30
|
-
|
47
|
+
self.class.field_name(@model, prop, multiple, page)
|
48
|
+
end
|
49
|
+
|
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
|
+
|
73
|
+
suffix = is_array_association?(model, prop) || multiple ? '[]' : ''
|
74
|
+
"#{name}[#{prop}]#{suffix}"
|
31
75
|
end
|
32
76
|
|
33
77
|
def field_value(prop)
|
34
|
-
|
35
|
-
|
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 }
|
36
84
|
else
|
37
|
-
|
85
|
+
model.send(prop)
|
38
86
|
end
|
39
87
|
end
|
40
88
|
|
41
89
|
def field_label(prop, args)
|
42
|
-
|
90
|
+
self.class.field_label(@model, @model_name, prop, args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.field_label(model, model_name, prop, args)
|
94
|
+
name = prop.to_s
|
95
|
+
if name.ends_with?('_id') && is_single_association?(model, name.chomp('_id'))
|
96
|
+
name = name.chomp('_id') # Always uses non-ID property name in i18n files
|
97
|
+
end
|
98
|
+
I18n.t("dt_models.#{model_name}.#{name}.label", **args.merge(default: nil)) || I18n.t("activerecord.attributes.#{model_name}.#{name}", **args)
|
43
99
|
end
|
44
100
|
|
45
101
|
def hint_label(prop, args)
|
46
|
-
|
102
|
+
self.class.hint_label(@model_name, prop, args)
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.hint_label(model_name, prop, args)
|
106
|
+
I18n.t("dt_models.#{model_name}.#{prop}.hint", **args.merge(default: nil))
|
47
107
|
end
|
48
108
|
|
49
109
|
def placeholder_label(prop, args)
|
50
|
-
|
110
|
+
self.class.placeholder_label(@model_name, prop, args)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.placeholder_label(model_name, prop, args)
|
114
|
+
I18n.t("dt_models.#{model_name}.#{prop}.placeholder", **args.merge(default: nil))
|
51
115
|
end
|
52
116
|
|
53
117
|
def field_validation(prop)
|
118
|
+
self.class.field_validation(@model, prop)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.field_validation(model, prop)
|
54
122
|
validations = {}
|
55
|
-
|
123
|
+
model.class.validators_on(prop).each do |validator|
|
56
124
|
if validator.kind == :presence
|
57
125
|
validations[:required] = { message: 'Required' }
|
58
126
|
end
|
@@ -299,5 +367,61 @@ class Glib::JsonUi::ViewBuilder
|
|
299
367
|
# end
|
300
368
|
end
|
301
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
|
+
|
302
426
|
end
|
303
427
|
end
|
@@ -11,15 +11,20 @@ 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
|
-
|
22
|
+
if default_translation.nil? && Rails.env.development?
|
23
|
+
i18n_key = "activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}"
|
24
|
+
I18n.t(i18n_key, raise: I18n::MissingTranslationData)
|
25
|
+
else
|
26
|
+
I18n.t(i18n_key, default: default_translation)
|
27
|
+
end
|
23
28
|
end
|
24
29
|
end
|
25
30
|
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
|
@@ -15,7 +15,7 @@ page.form url: json_ui_garage_url(path: 'forms/generic_post'), method: 'post', p
|
|
15
15
|
[
|
16
16
|
{ name: 'question', value: 'Quality of work' },
|
17
17
|
{ name: 'type', value: 'rating' },
|
18
|
-
{ name: 'enabled', value: '1'
|
18
|
+
{ name: 'enabled', value: '1' },
|
19
19
|
],
|
20
20
|
[
|
21
21
|
{ name: 'question', value: 'Satisfied?' },
|
@@ -26,9 +26,21 @@ page.form url: json_ui_garage_url(path: 'forms/generic_post'), method: 'post', p
|
|
26
26
|
group.template padding: { left: 32 }, childViews: ->(template) do
|
27
27
|
template.spacer height: 10
|
28
28
|
template.fields_text width: 'matchParent', name: 'question', label: 'Question', placeholder: 'Question'
|
29
|
+
|
29
30
|
options = [ :rating, :yes_no ]
|
30
|
-
template.fields_select
|
31
|
-
|
31
|
+
template.fields_select \
|
32
|
+
width: 'matchParent',
|
33
|
+
name: 'type',
|
34
|
+
label: 'Answer Type',
|
35
|
+
placeholder: 'Answer Type',
|
36
|
+
options: options.map { |o| { text: o.to_s.humanize, value: o } }
|
37
|
+
|
38
|
+
template.fields_check \
|
39
|
+
width: 'matchParent',
|
40
|
+
name: 'enabled',
|
41
|
+
label: 'Enable',
|
42
|
+
checkValue: '1',
|
43
|
+
showIf: { "==": [{ "var": 'user[evaluation][{{index}}][type]' }, 'rating'] }
|
32
44
|
|
33
45
|
template.spacer height: 14
|
34
46
|
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
|
|
@@ -99,7 +99,13 @@ page.form url: json_ui_garage_url(path: 'forms/generic_post'), method: 'post', p
|
|
99
99
|
|
100
100
|
form.spacer height: 20
|
101
101
|
form.h1 text: 'Select'
|
102
|
-
options = ['', 'show', 'hide']
|
102
|
+
# options = ['', 'show', 'hide']
|
103
|
+
options = {
|
104
|
+
'' => '<EMPTY>',
|
105
|
+
'show' => 'Show',
|
106
|
+
'hide' => 'Hide',
|
107
|
+
nil => '<NULL>'
|
108
|
+
}.map { |k, v| { value: k, text: v } }
|
103
109
|
form.fields_select name: 'user[select1]', width: 'matchParent', label: 'Select "show"', options: options, value: ''
|
104
110
|
form.label text: 'Selected', showIf: {
|
105
111
|
"==": [
|
@@ -117,6 +123,22 @@ page.form url: json_ui_garage_url(path: 'forms/generic_post'), method: 'post', p
|
|
117
123
|
''
|
118
124
|
]
|
119
125
|
}
|
126
|
+
form.label text: 'Null', showIf: {
|
127
|
+
"==": [
|
128
|
+
{
|
129
|
+
"var": 'user[select1]'
|
130
|
+
},
|
131
|
+
nil
|
132
|
+
]
|
133
|
+
}
|
134
|
+
form.label text: 'Any', showIf: {
|
135
|
+
"!=": [
|
136
|
+
{
|
137
|
+
"var": 'user[select1]'
|
138
|
+
},
|
139
|
+
nil
|
140
|
+
]
|
141
|
+
}
|
120
142
|
|
121
143
|
form.spacer height: 20
|
122
144
|
form.h1 text: 'Combined conditions'
|
@@ -7,11 +7,6 @@ render "#{@path_prefix}/nav_menu", json: json, page: page
|
|
7
7
|
small_image_url = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSGQpSWjtELISLBlmugOZ6wzl1JamYXQvbFeYywpfg3E8b8DrO0Kg&s'
|
8
8
|
|
9
9
|
page.scroll padding: glib_json_padding_body, childViews: ->(scroll) do
|
10
|
-
scroll.panels_column lg: { cols: 3 }, md: { cols: 1 }, sm: { cols: 1 }, xs: { cols: 1 }
|
11
|
-
scroll.panels_column backgroundColor: '#333333', height: 'matchParent', lg: { cols: 3 }, xs: { cols: 4 }, childViews: ->(column) do
|
12
|
-
column.image width: 'matchParent', url: glib_json_image_standard_url
|
13
|
-
end
|
14
|
-
|
15
10
|
scroll.h2 text: 'Avatar'
|
16
11
|
scroll.spacer height: 6
|
17
12
|
scroll.avatar \
|