flexi_admin 0.0.5 → 0.0.6
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/README.md +254 -18
- data/lib/flexi_admin/components/helpers/action_helper.rb +21 -0
- data/lib/flexi_admin/components/helpers/frontend_helper.rb +23 -0
- data/lib/flexi_admin/components/helpers/link_helper.rb +35 -2
- data/lib/flexi_admin/components/helpers/resource_helper.rb +79 -14
- data/lib/flexi_admin/components/helpers/url_helper.rb +15 -0
- data/lib/flexi_admin/components/nav/breadcrumbs_component.html.slim +13 -0
- data/lib/flexi_admin/components/nav/breadcrumbs_component.rb +86 -0
- data/lib/flexi_admin/components/resource/autocomplete_component.html.slim +19 -7
- data/lib/flexi_admin/components/resource/autocomplete_component.rb +41 -11
- data/lib/flexi_admin/components/resource/button_select_component.html.slim +2 -2
- data/lib/flexi_admin/components/resource/button_select_component.rb +8 -1
- data/lib/flexi_admin/components/resource/filter_component.html.slim +47 -0
- data/lib/flexi_admin/components/resource/filter_component.rb +51 -0
- data/lib/flexi_admin/components/resource/form_component.rb +16 -1
- data/lib/flexi_admin/components/resource/form_element_component.rb +1 -1
- data/lib/flexi_admin/components/resource/form_mixin.rb +186 -42
- data/lib/flexi_admin/components/resource/link_action_component.html.slim +2 -2
- data/lib/flexi_admin/components/resource/link_action_component.rb +3 -2
- data/lib/flexi_admin/components/resource/view_component.html.slim +14 -4
- data/lib/flexi_admin/components/resource/view_component.rb +11 -2
- data/lib/flexi_admin/components/resources/bulk_action/button_component.rb +8 -1
- data/lib/flexi_admin/components/resources/bulk_action/modal_component.html.slim +21 -15
- data/lib/flexi_admin/components/resources/bulk_action/modal_component.rb +16 -8
- data/lib/flexi_admin/components/resources/bulk_action/row_action_button_component.html.slim +16 -0
- data/lib/flexi_admin/components/resources/bulk_action/row_action_button_component.rb +78 -0
- data/lib/flexi_admin/components/resources/bulk_action/row_actions_dropdown_component.html.slim +15 -0
- data/lib/flexi_admin/components/resources/bulk_action/row_actions_dropdown_component.rb +33 -0
- data/lib/flexi_admin/components/resources/grid_view/card_component.html.slim +4 -1
- data/lib/flexi_admin/components/resources/grid_view/card_component.rb +4 -1
- data/lib/flexi_admin/components/resources/grid_view/grid_component.html.slim +3 -1
- data/lib/flexi_admin/components/resources/grid_view/grid_component.rb +4 -2
- data/lib/flexi_admin/components/resources/grid_view_component.rb +7 -2
- data/lib/flexi_admin/components/resources/index_page_component.html.slim +5 -4
- data/lib/flexi_admin/components/resources/index_page_component.rb +1 -0
- data/lib/flexi_admin/components/resources/list_view/table_component.html.slim +1 -1
- data/lib/flexi_admin/components/resources/list_view_component.rb +2 -1
- data/lib/flexi_admin/components/resources/pagination_component.html.slim +51 -39
- data/lib/flexi_admin/components/resources/pagination_component.rb +52 -47
- data/lib/flexi_admin/components/resources/switch_view_component.html.slim +2 -2
- data/lib/flexi_admin/components/resources/view_component.html.slim +15 -6
- data/lib/flexi_admin/components/shared/medium_component.html.slim +1 -1
- data/lib/flexi_admin/components/shared/table/header_item_component.html.slim +1 -1
- data/lib/flexi_admin/components/shared/table/header_item_component.rb +59 -61
- data/lib/flexi_admin/components/shared/tabs_component.html.slim +26 -0
- data/lib/flexi_admin/components/shared/tabs_component.rb +68 -0
- data/lib/flexi_admin/components.rb +11 -3
- data/lib/flexi_admin/config.rb +21 -3
- data/lib/flexi_admin/controllers/modals_controller.rb +4 -2
- data/lib/flexi_admin/controllers/resources_controller.rb +246 -51
- data/lib/flexi_admin/engine.rb +4 -0
- data/lib/flexi_admin/helpers/application_helper.rb +3 -0
- data/lib/flexi_admin/javascript/controllers/add_row_controller.js +118 -0
- data/lib/flexi_admin/javascript/controllers/autocomplete_controller.js +17 -1
- data/lib/flexi_admin/javascript/controllers/bulk_action_controller.js +192 -33
- data/lib/flexi_admin/javascript/controllers/copy_to_clipboard_controller.js +16 -0
- data/lib/flexi_admin/javascript/controllers/datalist_controller.js +1 -1
- data/lib/flexi_admin/javascript/controllers/delete_controller.js +40 -0
- data/lib/flexi_admin/javascript/controllers/expandable_field_controller.js +15 -0
- data/lib/flexi_admin/javascript/controllers/filter_auto_submit_controller.js +93 -0
- data/lib/flexi_admin/javascript/controllers/index.js +18 -0
- data/lib/flexi_admin/javascript/controllers/pagination_controller.js +8 -0
- data/lib/flexi_admin/javascript/controllers/tabs_controller.js +24 -0
- data/lib/flexi_admin/models/concerns/application_resource.rb +6 -0
- data/lib/flexi_admin/models/concerns/parentable.rb +3 -0
- data/lib/flexi_admin/models/context_params.rb +35 -10
- data/lib/flexi_admin/models/resources/context.rb +4 -0
- data/lib/flexi_admin/routes.rb +7 -1
- data/lib/flexi_admin/services/create_resource.rb +6 -6
- data/lib/flexi_admin/version.rb +1 -1
- data/lib/flexi_admin/views/shared/_reload_frame.slim +19 -0
- data/lib/flexi_admin.rb +0 -3
- metadata +171 -10
|
@@ -2,20 +2,32 @@
|
|
|
2
2
|
- if disabled && data_list?
|
|
3
3
|
= value
|
|
4
4
|
- elsif disabled && !data_list?
|
|
5
|
-
|
|
5
|
+
- if resource.present?
|
|
6
|
+
a href="#{resource_path(resource)}" data-turbo-frame="_top" = resource&.try(:ac_title) || resource&.try(:title)
|
|
6
7
|
/= helpers.link_to resource&.ac_title, resource_path(resource), data: { turbo_frame: '_top' }
|
|
8
|
+
- else
|
|
9
|
+
span.text-muted = disabled_empty_custom_message
|
|
7
10
|
- else
|
|
8
11
|
.input-group style="width: #{width || '100%'}"
|
|
9
12
|
.input-group-text id="button-addon1"
|
|
10
13
|
i.bi class="#{icon_class}"
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
- if data_list?
|
|
16
|
+
= text_area_tag name, value,
|
|
17
|
+
class: 'form-control expandable-field',
|
|
18
|
+
placeholder:,
|
|
15
19
|
'aria-describedby': 'button-addon1',
|
|
16
|
-
value: ,
|
|
17
20
|
required:,
|
|
18
|
-
|
|
21
|
+
rows: 1,
|
|
22
|
+
**expandable_autocomplete_options
|
|
23
|
+
- else
|
|
24
|
+
= text_field_tag name, resource&.ac_title,
|
|
25
|
+
class: 'form-control',
|
|
26
|
+
placeholder:,
|
|
27
|
+
'aria-describedby': 'button-addon1',
|
|
28
|
+
value: ,
|
|
29
|
+
required:,
|
|
30
|
+
**autocomplete_options
|
|
19
31
|
.invalid-feedback.d-none
|
|
20
32
|
- if select?
|
|
21
33
|
input [type="hidden"
|
|
@@ -27,4 +39,4 @@
|
|
|
27
39
|
.loading-icon.d-none data-autocomplete-target="loadingIcon"
|
|
28
40
|
.icon
|
|
29
41
|
|
|
30
|
-
ul.collapse.show data-autocomplete-target="list"
|
|
42
|
+
ul.collapse.show data-autocomplete-target="list"
|
|
@@ -5,41 +5,71 @@ module FlexiAdmin::Components::Resource
|
|
|
5
5
|
class AutocompleteComponent < FlexiAdmin::Components::BaseComponent
|
|
6
6
|
include FlexiAdmin::Components::Helpers::ResourceHelper
|
|
7
7
|
|
|
8
|
+
ALLOWED_ACTIONS = %i[select show input].freeze
|
|
9
|
+
|
|
8
10
|
attr_reader :resource, :disabled, :action, :parent, :fields, :required,
|
|
9
|
-
:name, :html_options, :path, :width, :value
|
|
11
|
+
:name, :html_options, :path, :width, :value,
|
|
12
|
+
:disabled_empty_custom_message, :placeholder, :priority_value
|
|
10
13
|
|
|
11
14
|
def initialize(resource = nil, scope:, fields: [:title],
|
|
12
15
|
action: :select, parent: nil, path: nil,
|
|
13
|
-
value: nil,
|
|
16
|
+
value: nil, disabled_empty_custom_message: nil,
|
|
17
|
+
target_controller: nil, placeholder: nil,
|
|
18
|
+
custom_scope: nil, priority_value: nil, **html_options)
|
|
14
19
|
@resource = resource
|
|
15
20
|
@scope = scope
|
|
21
|
+
@target_controller = target_controller
|
|
16
22
|
@parent = parent
|
|
17
23
|
@fields = fields
|
|
18
24
|
@path = path
|
|
19
25
|
@action = action
|
|
20
26
|
@value = value
|
|
27
|
+
@custom_scope = custom_scope&.to_s
|
|
28
|
+
@priority_value = priority_value
|
|
21
29
|
|
|
22
30
|
@html_options = html_options
|
|
23
31
|
@width = html_options.delete(:width)
|
|
24
32
|
@required = html_options[:required]
|
|
25
33
|
@style = html_options.delete(:style)
|
|
26
34
|
@disabled = html_options.key?(:disabled) ? html_options[:disabled] : false
|
|
35
|
+
|
|
27
36
|
@name = html_options[:name] || resource_input_name
|
|
28
37
|
|
|
38
|
+
@disabled_empty_custom_message = disabled_empty_custom_message || 'žádný zdroj'
|
|
39
|
+
@placeholder = placeholder || 'hledat'
|
|
40
|
+
|
|
29
41
|
validate_action!
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
def autocomplete_options
|
|
45
|
+
base_data = { autocomplete_target: 'input',
|
|
46
|
+
action: 'focus->autocomplete#focus keyup->autocomplete#keyup ' \
|
|
47
|
+
'focusout->autocomplete#onFocusOut',
|
|
48
|
+
autocomplete_search_path: get_path,
|
|
49
|
+
autocomplete_is_disabled: disabled,
|
|
50
|
+
field_type: kind }.merge(html_options)
|
|
51
|
+
|
|
52
|
+
# An app can mark one record as "the one the user is working on" so the
|
|
53
|
+
# backend can float it to the top; the value is set client-side because
|
|
54
|
+
# only the app knows what "current" means (see `ac_priority`).
|
|
55
|
+
base_data[:autocomplete_priority_value] = priority_value if priority_value.present?
|
|
56
|
+
|
|
33
57
|
{
|
|
34
58
|
style: 'border-top-right-radius: 0.4rem; border-bottom-right-radius: 0.4rem;',
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
autocomplete_search_path: get_path,
|
|
38
|
-
autocomplete_is_disabled: disabled,
|
|
39
|
-
field_type: kind }.merge(html_options)
|
|
59
|
+
autocomplete: 'off',
|
|
60
|
+
data: base_data
|
|
40
61
|
}
|
|
41
62
|
end
|
|
42
63
|
|
|
64
|
+
def expandable_autocomplete_options
|
|
65
|
+
opts = autocomplete_options
|
|
66
|
+
data = opts[:data].merge(
|
|
67
|
+
controller: 'expandable-field',
|
|
68
|
+
action: "input->expandable-field#resize #{opts[:data][:action]}"
|
|
69
|
+
)
|
|
70
|
+
{ data: data, **opts.except(:data) }
|
|
71
|
+
end
|
|
72
|
+
|
|
43
73
|
private
|
|
44
74
|
|
|
45
75
|
def icon_class
|
|
@@ -68,16 +98,16 @@ module FlexiAdmin::Components::Resource
|
|
|
68
98
|
|
|
69
99
|
case action
|
|
70
100
|
when :input
|
|
71
|
-
datalist_path(action: :input, parent
|
|
101
|
+
datalist_path(action: :input, parent: parent, fields: fields)
|
|
72
102
|
else
|
|
73
|
-
autocomplete_path(action
|
|
103
|
+
autocomplete_path(action: action, parent: parent, fields: fields, custom_scope: @custom_scope)
|
|
74
104
|
end
|
|
75
105
|
end
|
|
76
106
|
|
|
77
107
|
def validate_action!
|
|
78
|
-
return if
|
|
108
|
+
return if ALLOWED_ACTIONS.include?(@action)
|
|
79
109
|
|
|
80
110
|
raise "Invalid action: #{@action}"
|
|
81
111
|
end
|
|
82
112
|
end
|
|
83
|
-
end
|
|
113
|
+
end
|
|
@@ -2,11 +2,11 @@ div.button-select data-controller="button-select" data-button-select-disabled-va
|
|
|
2
2
|
.btn-group
|
|
3
3
|
- if disabled
|
|
4
4
|
button.btn.btn-outline-secondary.text-nowrap data-value=value data-button-select-target="button" disabled=true
|
|
5
|
-
= value
|
|
5
|
+
= value ? label_for(value) : '-'
|
|
6
6
|
- else
|
|
7
7
|
- options.each do |option|
|
|
8
8
|
button.btn.btn-outline-secondary.text-nowrap data-action="click->button-select#select" data-value=option data-button-select-target="button"
|
|
9
|
-
= option
|
|
9
|
+
= label_for(option)
|
|
10
10
|
= hidden_field_tag html_options[:name] || attr_name, value, data: { 'button-select-target': 'input' }
|
|
11
11
|
- if form
|
|
12
12
|
.invalid-feedback data-button-select-target="invalid-feedback"
|
|
@@ -5,7 +5,7 @@ module FlexiAdmin::Components::Resource
|
|
|
5
5
|
class ButtonSelectComponent < FlexiAdmin::Components::BaseComponent
|
|
6
6
|
attr_reader :resource, :attr_name, :options, :form, :label, :value, :html_options, :disabled
|
|
7
7
|
|
|
8
|
-
def initialize(resource, attr_name, options, form:, label: nil, value: nil, disabled: false, **html_options)
|
|
8
|
+
def initialize(resource, attr_name, options, form:, label: nil, value: nil, disabled: false, labels: nil, **html_options)
|
|
9
9
|
@resource = resource
|
|
10
10
|
@attr_name = attr_name
|
|
11
11
|
@options = options
|
|
@@ -14,6 +14,13 @@ module FlexiAdmin::Components::Resource
|
|
|
14
14
|
@value = value
|
|
15
15
|
@html_options = html_options
|
|
16
16
|
@disabled = disabled
|
|
17
|
+
@labels = labels || {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def label_for(option)
|
|
21
|
+
return option if @labels.empty?
|
|
22
|
+
|
|
23
|
+
@labels[option] || @labels[option.to_s] || @labels[option.to_s.to_sym] || option
|
|
17
24
|
end
|
|
18
25
|
end
|
|
19
26
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.filters.w-100
|
|
2
|
+
- active_filters = @params.to_h.except('controller', 'action', 'subdomain').select { |k, v| v.present? }
|
|
3
|
+
- filter_label_lookup = {}
|
|
4
|
+
- filter_configs.each do |filter|
|
|
5
|
+
- filter_label_lookup[filter[:name].to_s] = filter[:options].index_by { |o| o[:value].to_s }
|
|
6
|
+
- if active_filters.any?
|
|
7
|
+
.mb-2.w-100
|
|
8
|
+
| Aktivní filtry:
|
|
9
|
+
- active_filters.each do |k, v|
|
|
10
|
+
- values = v.is_a?(Array) ? v : [v]
|
|
11
|
+
- values.each do |val|
|
|
12
|
+
- label = filter_label_lookup[k]&.dig(val.to_s, :label) || val
|
|
13
|
+
- new_params = @params.deep_dup
|
|
14
|
+
- if new_params[k].is_a?(Array)
|
|
15
|
+
- new_params[k] = new_params[k] - [val]
|
|
16
|
+
- new_params.delete(k) if new_params[k].empty?
|
|
17
|
+
- else
|
|
18
|
+
- new_params.delete(k)
|
|
19
|
+
span.badge.bg-primary.text-white.mx-1
|
|
20
|
+
= "#{field_labels[k] || k.humanize}: #{label}"
|
|
21
|
+
= link_to helpers.url_for(params: new_params), class: 'text-white ms-1', title: "Odebrat filtr" do
|
|
22
|
+
i.bi.bi-x
|
|
23
|
+
.form-bar.w-100
|
|
24
|
+
= form_with url: '', method: :get, local: true, html: { class: 'd-flex align-items-center flex-row flex-wrap gap-2 w-100 filter-bar', style: '', data: { controller: 'filter-auto-submit' } } do
|
|
25
|
+
- if search_field
|
|
26
|
+
div(style="flex: 1 1 auto; min-width: 250px; max-width: 500px;" data-action="focusout->filter-auto-submit#submitOnBlur")
|
|
27
|
+
= search_field
|
|
28
|
+
- filter_configs.each do |filter|
|
|
29
|
+
- custom_label = field_labels[filter[:name].to_s] || filter[:label]
|
|
30
|
+
- if filter[:multiple]
|
|
31
|
+
.flex-shrink-0(style="min-width: 180px;")
|
|
32
|
+
.dropdown
|
|
33
|
+
button.btn.btn-outline-secondary.form-control.dropdown-toggle type="button" id="#{filter[:dropdown_id]}" data-bs-toggle="dropdown" aria-expanded="false"
|
|
34
|
+
= custom_label
|
|
35
|
+
.dropdown-menu.p-2 aria-labelledby="#{filter[:dropdown_id]}" style="max-height: 300px; overflow-y: auto; min-width: 220px;" data-action="click->filter-auto-submit#handleDropdownChange:stop"
|
|
36
|
+
- filter[:options].each do |opt|
|
|
37
|
+
label.form-check-label.dropdown-item
|
|
38
|
+
= check_box_tag "#{filter[:name]}[]", opt[:value], Array(filter[:current_value]).include?(opt[:value].to_s), class: 'form-check-input me-2'
|
|
39
|
+
= opt[:label]
|
|
40
|
+
- else
|
|
41
|
+
.flex-shrink-0(style="min-width: 180px;")
|
|
42
|
+
- select_classes = ['form-select']
|
|
43
|
+
- select_classes << 'is-active' if filter[:current_value].present?
|
|
44
|
+
label.form-label.mb-0 = custom_label
|
|
45
|
+
= select_tag filter[:name], options_for_select(filter[:options].map { |o| [o[:label], o[:value]] }, filter[:current_value]), class: select_classes.join(' '), include_blank: true, onchange: 'this.form.submit();'
|
|
46
|
+
.d-flex.gap-2
|
|
47
|
+
= button_tag t('flexi_admin.filters.cancel', default: 'Zrušit'), type: 'button', class: 'btn btn-outline-secondary', onclick: "window.location = window.location.pathname;"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FlexiAdmin::Components::Resource
|
|
4
|
+
class FilterComponent < FlexiAdmin::Components::BaseComponent
|
|
5
|
+
include FlexiAdmin::Components::Helpers::ResourceHelper
|
|
6
|
+
|
|
7
|
+
attr_reader :filter_options, :filters, :params, :search_field, :field_labels
|
|
8
|
+
|
|
9
|
+
def initialize(filter_options:, filters: true, params: {}, search_field: nil, field_labels: {})
|
|
10
|
+
@filter_options = filter_options
|
|
11
|
+
@filters = filters
|
|
12
|
+
@search_field = search_field
|
|
13
|
+
@field_labels = field_labels.with_indifferent_access
|
|
14
|
+
# tech-debt: handle params conversion
|
|
15
|
+
if params.respond_to?(:to_unsafe_h)
|
|
16
|
+
@params = params.to_unsafe_h.with_indifferent_access
|
|
17
|
+
elsif params.respond_to?(:to_h)
|
|
18
|
+
@params = params.to_h.with_indifferent_access
|
|
19
|
+
else
|
|
20
|
+
@params = params.with_indifferent_access
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def filter_configs
|
|
25
|
+
return [] unless filters && filter_options.present?
|
|
26
|
+
|
|
27
|
+
filter_options.map do |filter_name, options|
|
|
28
|
+
opts, multiple = if options.is_a?(Hash)
|
|
29
|
+
[options[:options], options[:multiple]]
|
|
30
|
+
else
|
|
31
|
+
[options, false]
|
|
32
|
+
end
|
|
33
|
+
opts = opts.map do |opt|
|
|
34
|
+
if opt.is_a?(Array)
|
|
35
|
+
{ label: opt[1], value: opt[0] }
|
|
36
|
+
else
|
|
37
|
+
{ label: opt.to_s.humanize, value: opt }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
{
|
|
41
|
+
name: filter_name,
|
|
42
|
+
label: filter_name.to_s.humanize,
|
|
43
|
+
options: opts,
|
|
44
|
+
multiple: multiple,
|
|
45
|
+
current_value: params[filter_name],
|
|
46
|
+
dropdown_id: "dropdown-#{filter_name}"
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -4,6 +4,8 @@ module FlexiAdmin::Components::Resource
|
|
|
4
4
|
class FormComponent < FlexiAdmin::Components::BaseComponent
|
|
5
5
|
include FlexiAdmin::Components::Resource::FormMixin
|
|
6
6
|
|
|
7
|
+
ResourceOrScopeNotDefinedError = Class.new(StandardError)
|
|
8
|
+
|
|
7
9
|
attr_reader :resource
|
|
8
10
|
|
|
9
11
|
def initialize(resource, disabled: true)
|
|
@@ -12,7 +14,20 @@ module FlexiAdmin::Components::Resource
|
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def disabled
|
|
15
|
-
|
|
17
|
+
if defined?(CanCan)
|
|
18
|
+
raise ResourceOrScopeNotDefinedError, "Resource or scope is not defined" if resource.blank? && scope.blank?
|
|
19
|
+
|
|
20
|
+
resource_to_check = resource.presence || self.class.class_name || scope.classify.constantize
|
|
21
|
+
|
|
22
|
+
!helpers.current_ability&.can?(:update, resource_to_check) || @disabled
|
|
23
|
+
else
|
|
24
|
+
@disabled
|
|
25
|
+
end
|
|
26
|
+
rescue ResourceOrScopeNotDefinedError => e
|
|
27
|
+
BugTracker.notify(e, scope: scope)
|
|
28
|
+
|
|
29
|
+
# Let it pass, we need to fix missing scope or resource
|
|
30
|
+
false
|
|
16
31
|
end
|
|
17
32
|
end
|
|
18
33
|
end
|
|
@@ -24,7 +24,7 @@ module FlexiAdmin::Components::Resource
|
|
|
24
24
|
|
|
25
25
|
def merge_data_keys(data)
|
|
26
26
|
controller = data[:controller]
|
|
27
|
-
controllers = controller.present? ? [controller, "form-validation"] : ["form-validation"]
|
|
27
|
+
controllers = controller.present? ? [controller, "form-validation", "add-row"] : ["form-validation", "add-row"]
|
|
28
28
|
data.merge(controller: controllers.join(" "))
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -28,48 +28,64 @@ module FlexiAdmin::Components::Resource
|
|
|
28
28
|
attr_reader :resource, :disabled, :inline, :parent_resource
|
|
29
29
|
|
|
30
30
|
# Form does not render; fields do.
|
|
31
|
-
|
|
31
|
+
# TODO: allow setting label_col/field_col for non-modal forms (e.g. via FormComponent initialize)
|
|
32
|
+
def form(url: resource_path(resource), css_class: 'myForm section', method: :patch, label_col: 'col-md-3', field_col: 'col-md-9', **html_options, &block)
|
|
33
|
+
previous_label_col = @label_col
|
|
34
|
+
previous_field_col = @field_col
|
|
35
|
+
@label_col = label_col
|
|
36
|
+
@field_col = field_col
|
|
37
|
+
|
|
32
38
|
render FlexiAdmin::Components::Resource::FormElementComponent.new(resource, url:, css_class:, method:, **html_options) do |component|
|
|
33
39
|
component.with_fields do
|
|
34
40
|
capture(&block)
|
|
35
41
|
end
|
|
36
42
|
end
|
|
43
|
+
ensure
|
|
44
|
+
@label_col = previous_label_col
|
|
45
|
+
@field_col = previous_field_col
|
|
37
46
|
end
|
|
38
47
|
|
|
39
48
|
def select_field(attr_name, options:, label: nil, value: nil, **html_options)
|
|
40
49
|
field = render_select(attr_name, value:, options:, **html_options)
|
|
41
50
|
field_wrapper = render_field_wrapper(field, attr_name)
|
|
42
51
|
|
|
43
|
-
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:)
|
|
52
|
+
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:, required: html_options[:required])
|
|
44
53
|
end
|
|
45
54
|
|
|
46
55
|
def button_select_field(attr_name, options:, label: nil, value: nil, **html_options)
|
|
47
56
|
field = render_button_select(attr_name, options:, value:, disabled:, **html_options)
|
|
48
57
|
field_wrapper = render_field_wrapper(field, attr_name)
|
|
49
58
|
|
|
50
|
-
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:)
|
|
59
|
+
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:, required: html_options[:required])
|
|
51
60
|
end
|
|
52
61
|
|
|
53
|
-
def text_field(attr_name, label: nil, value: nil, **html_options)
|
|
54
|
-
field =
|
|
62
|
+
def text_field(attr_name, label: nil, value: nil, expandable: false, **html_options)
|
|
63
|
+
field = if expandable
|
|
64
|
+
render_expandable_field(attr_name, value, html_options)
|
|
65
|
+
else
|
|
66
|
+
text_field_tag(attr_name, value:, **html_options)
|
|
67
|
+
end
|
|
55
68
|
field_wrapper = render_field_wrapper(field, attr_name)
|
|
56
69
|
|
|
57
|
-
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:)
|
|
70
|
+
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:, required: html_options[:required])
|
|
58
71
|
end
|
|
59
72
|
|
|
60
73
|
def text_field_tag(attr_name, value: nil, **html_options)
|
|
61
74
|
render_standard_field(:text, attr_name, value, html_options)
|
|
62
75
|
end
|
|
63
76
|
|
|
64
|
-
def number_field(attr_name, label: nil, value: nil, **html_options)
|
|
65
|
-
render_standard_field(:number, attr_name, value, html_options)
|
|
77
|
+
def number_field(attr_name, label: nil, value: nil, step: 0.01, **html_options)
|
|
78
|
+
field = render_standard_field(:number, attr_name, value, html_options.merge(step: step.to_s))
|
|
79
|
+
field_wrapper = render_field_wrapper(field, attr_name)
|
|
80
|
+
|
|
81
|
+
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:, required: html_options[:required])
|
|
66
82
|
end
|
|
67
83
|
|
|
68
84
|
def checkbox_field(attr_name, label: nil, checked: nil, **html_options)
|
|
69
85
|
checkbox = checkbox_field_tag(attr_name, checked:, **html_options)
|
|
70
86
|
field_wrapper = render_field_wrapper(checkbox, attr_name)
|
|
71
87
|
|
|
72
|
-
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:)
|
|
88
|
+
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:, required: html_options[:required])
|
|
73
89
|
end
|
|
74
90
|
|
|
75
91
|
def checkbox_field_tag(attr_name, checked: nil, **html_options)
|
|
@@ -99,14 +115,25 @@ module FlexiAdmin::Components::Resource
|
|
|
99
115
|
field = render_standard_field(:date, attr_name, value, html_options.merge(style: 'max-width: 180px;'))
|
|
100
116
|
field_wrapper = render_field_wrapper(field, attr_name)
|
|
101
117
|
|
|
102
|
-
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:)
|
|
118
|
+
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:, required: html_options[:required])
|
|
103
119
|
end
|
|
104
120
|
|
|
105
121
|
def datetime_field(attr_name, label: nil, value: nil, **html_options)
|
|
106
122
|
field = render_standard_field(:datetime, attr_name, value, html_options.merge(style: 'max-width: 180px;'))
|
|
107
123
|
field_wrapper = render_field_wrapper(field, attr_name)
|
|
108
124
|
|
|
109
|
-
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:)
|
|
125
|
+
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:, required: html_options[:required])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def text_area_field(attr_name, label: nil, value: nil, **html_options)
|
|
129
|
+
field = text_area_field_tag(attr_name, value:, **html_options)
|
|
130
|
+
field_wrapper = render_field_wrapper(field, attr_name)
|
|
131
|
+
|
|
132
|
+
inline ? field_wrapper : render_form_row(attr_name, field_wrapper, label:, required: html_options[:required])
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def text_area_field_tag(attr_name, value: nil, **html_options)
|
|
136
|
+
content_tag(:textarea, value, html_options.merge(name: attr_name, class: 'form-control'))
|
|
110
137
|
end
|
|
111
138
|
|
|
112
139
|
# Trix field
|
|
@@ -158,6 +185,73 @@ module FlexiAdmin::Components::Resource
|
|
|
158
185
|
end
|
|
159
186
|
end
|
|
160
187
|
|
|
188
|
+
def add_row_button(container:, template_selector: '.dynamic-rows > .form-row:has(input, select, textarea)', label: I18n.t('flexi_admin.form.add_row_button'), icon: 'plus', classes: '')
|
|
189
|
+
button = if icon.present?
|
|
190
|
+
content_tag(:button, class: 'btn btn-outline-secondary ' + classes, type: 'button',
|
|
191
|
+
data: { action: 'click->add-row#add',
|
|
192
|
+
'add-row-container-value': container,
|
|
193
|
+
'add-row-template-selector-value': template_selector }) do
|
|
194
|
+
content = []
|
|
195
|
+
content << content_tag(:i, '', class: "bi bi-#{icon} me-2")
|
|
196
|
+
content << label
|
|
197
|
+
content.join.html_safe
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
content_tag(:button, label, class: 'btn btn-outline-secondary ' + classes, type: 'button',
|
|
201
|
+
data: { action: 'click->add-row#add',
|
|
202
|
+
'add-row-container-value': container,
|
|
203
|
+
'add-row-template-selector-value': template_selector })
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
content_tag(:div, class: 'form-row') do
|
|
207
|
+
content_tag(:div, class: 'col-12') { button }
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def remove_row_button(icon: 'trash', classes: '')
|
|
212
|
+
content_tag(:button, class: 'btn btn-outline-danger btn-sm ' + classes, type: 'button',
|
|
213
|
+
data: { action: 'click->add-row#remove',
|
|
214
|
+
'add-row-template-selector-value': '.form-row' }) do
|
|
215
|
+
content = []
|
|
216
|
+
content << content_tag(:i, '', class: "bi bi-#{icon}")
|
|
217
|
+
content.join.html_safe
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def columns(**html_options, &block)
|
|
222
|
+
previous_column_row_counts = @column_row_counts
|
|
223
|
+
@column_row_counts = []
|
|
224
|
+
content = capture(&block)
|
|
225
|
+
max_rows = @column_row_counts.max || 1
|
|
226
|
+
col_count = @column_row_counts.size
|
|
227
|
+
|
|
228
|
+
css_classes = ['form-columns']
|
|
229
|
+
css_classes << html_options.delete(:class) if html_options[:class]
|
|
230
|
+
|
|
231
|
+
style = "grid-template-rows: repeat(#{max_rows}, auto); grid-template-columns: repeat(#{col_count}, 1fr);"
|
|
232
|
+
existing_style = html_options[:style]
|
|
233
|
+
style = "#{existing_style} #{style}" if existing_style
|
|
234
|
+
|
|
235
|
+
content_tag(:div, content, html_options.merge(class: css_classes.join(' '), style:))
|
|
236
|
+
ensure
|
|
237
|
+
@column_row_counts = previous_column_row_counts
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def column(**html_options, &block)
|
|
241
|
+
previous_inside_column = @inside_column
|
|
242
|
+
@inside_column = true
|
|
243
|
+
@current_column_row_count = 0
|
|
244
|
+
content = capture(&block)
|
|
245
|
+
@column_row_counts << @current_column_row_count
|
|
246
|
+
|
|
247
|
+
css_classes = ['form-column']
|
|
248
|
+
css_classes << html_options.delete(:class) if html_options[:class]
|
|
249
|
+
|
|
250
|
+
content_tag(:div, content, html_options.merge(class: css_classes.join(' ')))
|
|
251
|
+
ensure
|
|
252
|
+
@inside_column = previous_inside_column
|
|
253
|
+
end
|
|
254
|
+
|
|
161
255
|
def with_resource(resource)
|
|
162
256
|
previous_resource = @resource
|
|
163
257
|
@resource = resource
|
|
@@ -168,35 +262,57 @@ module FlexiAdmin::Components::Resource
|
|
|
168
262
|
|
|
169
263
|
private
|
|
170
264
|
|
|
265
|
+
def render_expandable_field(attr_name, value, html_options)
|
|
266
|
+
val, input_classes, resolved_disabled = resolve_field_attrs(attr_name, value, html_options, extra_class: 'expandable-field')
|
|
267
|
+
data = {
|
|
268
|
+
controller: 'expandable-field',
|
|
269
|
+
action: 'input->expandable-field#resize blur->form-validation#handleInvalid keyup->form-validation#handleInvalid'
|
|
270
|
+
}
|
|
271
|
+
merge_model_error!(data, attr_name)
|
|
272
|
+
|
|
273
|
+
content_tag(:textarea, val, {
|
|
274
|
+
name: attr_name, class: input_classes, rows: 1, data:, disabled: resolved_disabled
|
|
275
|
+
}.merge(html_options.except(:disabled)))
|
|
276
|
+
end
|
|
277
|
+
|
|
171
278
|
def render_standard_field(type, attr_name, value, html_options)
|
|
172
|
-
val =
|
|
279
|
+
val, input_classes, resolved_disabled = resolve_field_attrs(attr_name, value, html_options)
|
|
173
280
|
data = {
|
|
174
281
|
action: 'blur->form-validation#handleInvalid keyup->form-validation#handleInvalid'
|
|
175
282
|
}
|
|
176
283
|
merge_model_error!(data, attr_name) unless type == :submit
|
|
177
284
|
|
|
178
|
-
|
|
179
|
-
input_classes = 'form-control'
|
|
180
|
-
input_classes += ' is-invalid' if resource.present? && attr_name.present? && resource.errors[attr_name].present?
|
|
181
|
-
if type != :submit
|
|
182
|
-
content << content_tag(:input, nil,
|
|
183
|
-
{
|
|
184
|
-
type:,
|
|
185
|
-
name: attr_name,
|
|
186
|
-
value: val,
|
|
187
|
-
class: input_classes,
|
|
188
|
-
data:
|
|
189
|
-
}.merge(html_options)
|
|
190
|
-
.merge(disabled:))
|
|
191
|
-
end
|
|
285
|
+
return ''.html_safe if type == :submit
|
|
192
286
|
|
|
193
|
-
|
|
287
|
+
content_tag(:input, nil, {
|
|
288
|
+
type:, name: attr_name, value: val, class: input_classes, data:, disabled: resolved_disabled
|
|
289
|
+
}.merge(html_options.except(:disabled)))
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def resolve_field_attrs(attr_name, value, html_options, extra_class: nil)
|
|
293
|
+
val = value.is_a?(Proc) ? value.call : resource.try(attr_name) || value
|
|
294
|
+
input_classes = ['form-control', extra_class].compact.join(' ')
|
|
295
|
+
input_classes += ' is-invalid' if resource.present? && attr_name.present? && resource.errors[attr_name].present?
|
|
296
|
+
resolved_disabled = html_options[:disabled] || disabled
|
|
297
|
+
[val, input_classes, resolved_disabled]
|
|
194
298
|
end
|
|
195
299
|
|
|
196
300
|
def render_field_wrapper(field, attr_name)
|
|
197
301
|
content_tag(:div, class: inline ? 'inline-field' : 'field-wrapper') do
|
|
198
302
|
content = []
|
|
199
|
-
|
|
303
|
+
if disabled && attr_name.present?
|
|
304
|
+
content << content_tag(:div, class: 'copy-field-wrapper', data: { controller: 'copy-to-clipboard' }) do
|
|
305
|
+
field_content = []
|
|
306
|
+
field_content << field
|
|
307
|
+
field_content << content_tag(:button, type: 'button', class: 'copy-field-btn', tabindex: -1,
|
|
308
|
+
data: { action: 'click->copy-to-clipboard#copy' }) do
|
|
309
|
+
content_tag(:i, '', class: 'bi bi-clipboard')
|
|
310
|
+
end
|
|
311
|
+
field_content.join.html_safe
|
|
312
|
+
end
|
|
313
|
+
else
|
|
314
|
+
content << field
|
|
315
|
+
end
|
|
200
316
|
if resource.present? && attr_name.present?
|
|
201
317
|
content << content_tag(:div, class: 'invalid-feedback') do
|
|
202
318
|
resource.errors[attr_name].join(' ')
|
|
@@ -255,25 +371,53 @@ module FlexiAdmin::Components::Resource
|
|
|
255
371
|
**html_options))
|
|
256
372
|
end
|
|
257
373
|
|
|
258
|
-
def render_form_row(attr_name, field_html, label:, **html_options)
|
|
374
|
+
def render_form_row(attr_name, field_html, label:, required: false, **html_options)
|
|
259
375
|
label ||= attr_name.to_s.humanize unless label == false
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
376
|
+
@current_column_row_count += 1 if @inside_column
|
|
377
|
+
|
|
378
|
+
row_classes = ['form-row']
|
|
379
|
+
label_col = @label_col || 'col-md-3'
|
|
380
|
+
field_col = @field_col || 'col-md-9'
|
|
381
|
+
|
|
382
|
+
content_tag(:div, html_options.merge(class: row_classes.join(' '))) do
|
|
383
|
+
lbl = label
|
|
384
|
+
lbl += ' *' if required && !disabled
|
|
385
|
+
|
|
386
|
+
if @inside_column
|
|
387
|
+
concat content_tag(:div, class: 'col-12') { label_tag(attr_name, lbl) } unless label == false
|
|
388
|
+
concat content_tag(:div, class: inline ? 'col-12 inline-field-wrapper' : 'col-12') { field_html }
|
|
389
|
+
else
|
|
390
|
+
resolved_field_col = label == false ? 'col-md-12' : field_col
|
|
391
|
+
concat content_tag(:div, class: "col-12 #{label_col}") { label_tag(attr_name, lbl) } unless label == false
|
|
392
|
+
concat content_tag(:div, class: inline ? "col-12 #{resolved_field_col} inline-field-wrapper" : "col-12 #{resolved_field_col}") {
|
|
393
|
+
field_html
|
|
394
|
+
}
|
|
395
|
+
end
|
|
266
396
|
end
|
|
267
397
|
end
|
|
268
398
|
|
|
269
399
|
def render_custom_field(view_component_instance, label, html_options, _value)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
400
|
+
@current_column_row_count += 1 if @inside_column
|
|
401
|
+
|
|
402
|
+
row_classes = ['form-row']
|
|
403
|
+
label_col = @label_col || 'col-md-3'
|
|
404
|
+
field_col = @field_col || 'col-md-9'
|
|
405
|
+
|
|
406
|
+
content_tag(:div, html_options.merge(class: row_classes.join(' '))) do
|
|
407
|
+
if @inside_column
|
|
408
|
+
unless label == false
|
|
409
|
+
concat content_tag(:div, class: 'col-12') { label_tag('autocomplete', label) }
|
|
410
|
+
end
|
|
411
|
+
concat content_tag(:div, class: 'col-12') { render view_component_instance }
|
|
412
|
+
else
|
|
413
|
+
unless label == false
|
|
414
|
+
concat content_tag(:div, class: "col-12 #{label_col}") {
|
|
415
|
+
label_tag('autocomplete', label)
|
|
416
|
+
}
|
|
417
|
+
end
|
|
418
|
+
resolved_field_col = label == false ? 'col-md-12' : field_col
|
|
419
|
+
concat content_tag(:div, class: "col-12 #{resolved_field_col}") { render view_component_instance }
|
|
275
420
|
end
|
|
276
|
-
concat content_tag(:div, class: 'col-12 col-md-9') { render view_component_instance }
|
|
277
421
|
end
|
|
278
422
|
end
|
|
279
423
|
|
|
@@ -284,4 +428,4 @@ module FlexiAdmin::Components::Resource
|
|
|
284
428
|
data.merge!(validation_error: resource.errors[attr_name].join(' ')) if resource.errors.present?
|
|
285
429
|
end
|
|
286
430
|
end
|
|
287
|
-
end
|
|
431
|
+
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
- if icon.present?
|
|
2
|
-
= link_to path, **options.merge(data: { turbo_frame: '_top' }, class:
|
|
2
|
+
= link_to path, **options.merge(data: { turbo_frame: '_top' }, class: "dropdown-item #{disabled ? 'disabled' : ''}") do
|
|
3
3
|
i.bi.me-2 class=icon_class
|
|
4
4
|
= label
|
|
5
5
|
/a href="#{path}" data-turbo-frame="_top" class="dropdown-item"
|
|
6
6
|
- else
|
|
7
|
-
= link_to label, path, **options.merge(data: { turbo_frame: '_top' }, class:
|
|
7
|
+
= link_to label, path, **options.merge(data: { turbo_frame: '_top' }, class: "dropdown-item #{disabled ? 'disabled' : ''}")
|
|
8
8
|
/a href="#{path}" data-turbo-frame="_top" class="dropdown-item" = label
|