diff --git a/.rubocop.yml b/.rubocop.yml
index 513697910..70668f85b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -228,6 +228,7 @@ Metrics/MethodLength:
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb'
+ - 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb'
Metrics/BlockLength:
Exclude:
@@ -285,6 +286,7 @@ Layout/LineLength:
- 'packages/forest_admin_agent/lib/forest_admin_agent/http/forest_admin_api_requester.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/list.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb'
+ - 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/override/context/create_override_customization_context.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/override/context/update_override_customization_context.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/override/context/delete_override_customization_context.rb'
diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb
index 5bbe548f6..0836c5bc5 100644
--- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb
+++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb
@@ -29,7 +29,6 @@ def self.build_schema(collection, name)
action = collection.schema[:actions][name]
action_index = collection.schema[:actions].keys.index(name)
slug = get_action_slug(name)
-
form_elements = extract_fields_and_layout(collection.get_form(nil, name))
if action.static_form?
fields = build_fields(collection, form_elements[:fields])
@@ -72,6 +71,14 @@ def self.build_layout_schema(field)
component: field.component.camelize(:lower),
fields: field.fields.map { |f| build_layout_schema(f) }
}
+ elsif field.component == 'Page'
+ return {
+ **field.to_h,
+ component: field.component.camelize(:lower),
+ elements: field.elements.map do |f|
+ build_layout_schema(f)
+ end
+ }
end
{ **field.to_h, component: field.component.camelize(:lower) }
@@ -135,18 +142,15 @@ def self.build_layout(elements)
def self.extract_fields_and_layout(form)
fields = []
layout = []
-
form&.each do |element|
if element.type == Actions::FieldType::LAYOUT
- if element.component == 'Row'
- extract = extract_fields_and_layout(element.fields)
- element.fields = extract[:layout]
+ if %w[Page Row].include?(element.component)
+ extract = extract_fields_and_layout_for_component(element)
layout << element
fields.concat(extract[:fields])
else
layout << element
end
-
else
fields << element
# frontend rule
@@ -156,6 +160,13 @@ def self.extract_fields_and_layout(form)
{ fields: fields, layout: layout }
end
+
+ def self.extract_fields_and_layout_for_component(element)
+ key = element.component == 'Page' ? :elements : :fields
+ extract = extract_fields_and_layout(element.public_send(key))
+ element.public_send(:"#{key}=", extract[:layout])
+ extract
+ end
end
end
end
diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb
index 639bd870c..50f2f488b 100644
--- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb
+++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb
@@ -397,6 +397,80 @@ module Schema
end
end
+ context 'with page element' do
+ before do
+ @collection = collection_build(
+ schema: {
+ actions: {
+ 'Charge credit card' => BaseAction.new(
+ scope: Types::ActionScope::SINGLE
+ )
+ }
+ },
+ get_form: [
+ ActionLayoutElement::PageElement.new(
+ next_button_label: 'Next',
+ previous_button_label: 'Previous',
+ elements: [
+ ActionLayoutElement::HtmlBlockElement.new(content: '
Charge the credit card of the customer
'),
+ ActionLayoutElement::RowElement.new(
+ fields: [
+ ActionField.new(id: 'label', label: 'label', type: 'String'),
+ ActionField.new(id: 'amount', label: 'amount', type: 'String')
+ ]
+ )
+ ]
+ )
+ ]
+ )
+ end
+
+ it 'generate schema correctly' do
+ schema = described_class.build_schema(@collection, 'Charge credit card')
+
+ expect(schema).to eq(
+ {
+ id: 'collection-0-charge-credit-card',
+ name: 'Charge credit card',
+ submitButtonLabel: nil,
+ description: nil,
+ type: 'single',
+ baseUrl: nil,
+ endpoint: '/forest/_actions/collection/0/charge-credit-card',
+ httpMethod: 'POST',
+ redirect: nil,
+ download: false,
+ fields: [
+ {
+ field: 'label',
+ label: 'label',
+ type: 'String',
+ description: nil,
+ isRequired: false,
+ isReadOnly: false,
+ widgetEdit: nil,
+ defaultValue: nil
+ },
+ {
+ field: 'amount',
+ label: 'amount',
+ type: 'String',
+ description: nil,
+ isRequired: false,
+ isReadOnly: false,
+ widgetEdit: nil,
+ defaultValue: nil
+ }
+ ],
+ # layout: [
+ # { component: 'page', type: 'Layout', elements: ['htmlBlock', 'row'] }
+ # ],
+ hooks: { load: false, change: ['changeHook'] }
+ }
+ )
+ end
+ end
+
describe 'extract_fields_and_layout' do
before do
@collection = collection_build(
diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb
index 24a269fe0..13a894f35 100644
--- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb
+++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb
@@ -10,6 +10,7 @@ def initialize(child_collection, datasource)
end
def add_action(name, action)
+ ensure_form_is_correct(action.form, name)
action.build_elements
action.validate_fields_ids
@actions[name] = action
@@ -41,15 +42,13 @@ def get_form(caller, name, data = nil, filter = nil, metas = {})
context = get_context(caller, action, form_values, filter, used, metas[:change_field])
dynamic_fields = action.form
- if metas[:search_field]
- # in the case of a search hook,
- # we don't want to rebuild all the fields. only the one searched
- dynamic_fields = dynamic_fields.select { |field| field.id == metas[:search_field] }
- end
+ dynamic_fields = select_in_form_fields(dynamic_fields, metas[:search_field]) if metas[:search_field]
dynamic_fields = drop_defaults(context, dynamic_fields, form_values)
dynamic_fields = drop_ifs(context, dynamic_fields) unless metas[:include_hidden_fields]
- fields = drop_deferred(context, metas[:search_values], dynamic_fields).compact
+ fields = drop_deferred(context, metas[:search_values], dynamic_fields)
+
+ fields.compact
set_watch_changes_on_fields(form_values, used, fields)
@@ -64,6 +63,31 @@ def refine_schema(sub_schema)
private
+ def ensure_form_is_correct(form, action_name)
+ return if form.nil? || form.empty?
+
+ is_page_component = ->(element) { element[:type] == 'Layout' && element[:component] == 'Page' }
+ pages = is_page_component.call(form.first)
+
+ form.each do |element|
+ if pages != is_page_component.call(element)
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
+ "You cannot mix pages and other form elements in smart action '#{action_name}' form"
+ end
+ end
+ end
+
+ def select_in_form_fields(fields, search_field)
+ fields.select do |field|
+ if nested_layout_element?(field)
+ key = field.component == 'Page' ? :elements : :fields
+ select_in_form_fields(field.public_send(:"#{key}"), search_field)
+ else
+ field.id == search_field
+ end
+ end
+ end
+
def set_watch_changes_on_fields(form_values, used, fields)
fields.each do |field|
if field.type != 'Layout'
@@ -77,14 +101,20 @@ def set_watch_changes_on_fields(form_values, used, fields)
field.watch_changes = used.include?(field.id)
elsif field.component == 'Row'
set_watch_changes_on_fields(form_values, used, field.fields)
+ elsif field.component == 'Page'
+ set_watch_changes_on_fields(form_values, used, field.elements)
end
end
end
def execute_on_sub_fields(field)
- return unless field.type == 'Layout' && field.component == 'Row'
+ return unless nested_layout_element?(field)
- field.fields = yield(field.fields)
+ if field.component == 'Page'
+ field.elements = yield(field.elements)
+ elsif field.component == 'Row'
+ field.fields = yield(field.fields)
+ end
end
def drop_defaults(context, fields, data)
@@ -110,10 +140,12 @@ def drop_ifs(context, fields)
if_values = fields.map do |field|
if evaluate(context, field.if_condition) == false
false
- elsif field.type == 'Layout' && field.component == 'Row'
- field.fields = drop_ifs(context, field.fields || [])
+ elsif nested_layout_element?(field)
+ key = field.component == 'Page' ? :elements : :fields
+ field.public_send(:"#{key}=", drop_ifs(context, field.public_send(:"#{key}") || []))
+
+ true unless field.public_send(:"#{key}").empty?
- true unless field.fields.empty?
else
true
end
@@ -170,6 +202,10 @@ def get_context(caller, action, form_values = [], filter = nil, used = [], chang
Context::ActionContext.new(self, caller, form_values, filter, used, change_field)
end
+
+ def nested_layout_element?(field)
+ field.type == 'Layout' && %w[Page Row].include?(field.component)
+ end
end
end
end
diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb
index 4aed12ddd..6b056aea1 100644
--- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb
+++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb
@@ -14,21 +14,22 @@ def initialize(scope:, form: nil, is_generate_file: false, description: nil, sub
end
def build_elements
- @form = @form&.map do |field|
- if field.key? :widget
- build_widget(field)
- elsif field[:type] == 'Layout'
- build_layout_element(field)
- else
- DynamicField.new(**field)
- end
- end
+ @form = FormFactory.build_elements(form)
+ end
+
+ def static_form?
+ return form&.all?(&:static?) && form&.none? { |field| field.type == 'Layout' } if form
+
+ true
end
def validate_fields_ids(form = @form, used = [])
form&.each do |element|
- if element.type == 'Layout' && element.component == 'Row'
- validate_fields_ids(element.fields, used)
+ if element.type == 'Layout'
+ if %w[Page Row].include?(element.component)
+ key = element.component == 'Page' ? :elements : :fields
+ validate_fields_ids(element.public_send(key), used)
+ end
else
if used.include?(element.id)
raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
@@ -38,69 +39,6 @@ def validate_fields_ids(form = @form, used = [])
end
end
end
-
- def build_widget(field)
- case field[:widget]
- when 'AddressAutocomplete'
- WidgetField::AddressAutocompleteField.new(**field)
- when 'Checkbox'
- WidgetField::CheckboxField.new(**field)
- when 'CheckboxGroup'
- WidgetField::CheckboxGroupField.new(**field)
- when 'ColorPicker'
- WidgetField::ColorPickerField.new(**field)
- when 'CurrencyInput'
- WidgetField::CurrencyInputField.new(**field)
- when 'DatePicker'
- WidgetField::DatePickerField.new(**field)
- when 'Dropdown'
- WidgetField::DropdownField.new(**field)
- when 'FilePicker'
- WidgetField::FilePickerField.new(**field)
- when 'JsonEditor'
- WidgetField::JsonEditorField.new(**field)
- when 'NumberInput'
- WidgetField::NumberInputField.new(**field)
- when 'NumberInputList'
- WidgetField::NumberInputListField.new(**field)
- when 'RadioGroup'
- WidgetField::RadioGroupField.new(**field)
- when 'RichText'
- WidgetField::RichTextField.new(**field)
- when 'TextArea'
- WidgetField::TextAreaField.new(**field)
- when 'TextInput'
- WidgetField::TextInputField.new(**field)
- when 'TextInputList'
- WidgetField::TextInputListField.new(**field)
- when 'TimePicker'
- WidgetField::TimePickerField.new(**field)
- when 'UserDropdown'
- WidgetField::UserDropdownField.new(**field)
- else
- raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unknow widget type: #{field[:widget]}"
- end
- end
-
- def build_layout_element(field)
- case field[:component]
- when 'Separator'
- FormLayoutElement::SeparatorElement.new(**field)
- when 'HtmlBlock'
- FormLayoutElement::HtmlBlockElement.new(**field)
- when 'Row'
- FormLayoutElement::RowElement.new(**field)
- else
- raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
- "Unknow component type: #{field[:component]}"
- end
- end
-
- def static_form?
- return form&.all?(&:static?) && form&.none? { |field| field.type == 'Layout' } if form
-
- true
- end
end
end
end
diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb
new file mode 100644
index 000000000..685d28a04
--- /dev/null
+++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb
@@ -0,0 +1,87 @@
+module ForestAdminDatasourceCustomizer
+ module Decorators
+ module Action
+ class FormFactory
+ def self.build_elements(form)
+ form&.map do |field|
+ case field
+ when Hash
+ if field.key?(:widget)
+ build_widget(field)
+ elsif field[:type] == 'Layout'
+ build_layout_element(field)
+ else
+ DynamicField.new(**field)
+ end
+ when FormLayoutElement::RowElement
+ build_elements(field.fields)
+ when FormLayoutElement::PageElement
+ build_elements(field.elements)
+ else
+ field
+ end
+ end
+ end
+
+ def self.build_widget(field)
+ case field[:widget]
+ when 'AddressAutocomplete'
+ WidgetField::AddressAutocompleteField.new(**field)
+ when 'Checkbox'
+ WidgetField::CheckboxField.new(**field)
+ when 'CheckboxGroup'
+ WidgetField::CheckboxGroupField.new(**field)
+ when 'ColorPicker'
+ WidgetField::ColorPickerField.new(**field)
+ when 'CurrencyInput'
+ WidgetField::CurrencyInputField.new(**field)
+ when 'DatePicker'
+ WidgetField::DatePickerField.new(**field)
+ when 'Dropdown'
+ WidgetField::DropdownField.new(**field)
+ when 'FilePicker'
+ WidgetField::FilePickerField.new(**field)
+ when 'JsonEditor'
+ WidgetField::JsonEditorField.new(**field)
+ when 'NumberInput'
+ WidgetField::NumberInputField.new(**field)
+ when 'NumberInputList'
+ WidgetField::NumberInputListField.new(**field)
+ when 'RadioGroup'
+ WidgetField::RadioGroupField.new(**field)
+ when 'RichText'
+ WidgetField::RichTextField.new(**field)
+ when 'TextArea'
+ WidgetField::TextAreaField.new(**field)
+ when 'TextInput'
+ WidgetField::TextInputField.new(**field)
+ when 'TextInputList'
+ WidgetField::TextInputListField.new(**field)
+ when 'TimePicker'
+ WidgetField::TimePickerField.new(**field)
+ when 'UserDropdown'
+ WidgetField::UserDropdownField.new(**field)
+ else
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unknow widget type: #{field[:widget]}"
+ end
+ end
+
+ def self.build_layout_element(field)
+ case field[:component]
+ when 'Separator'
+ FormLayoutElement::SeparatorElement.new(**field)
+ when 'HtmlBlock'
+ FormLayoutElement::HtmlBlockElement.new(**field)
+ when 'Row'
+ FormLayoutElement::RowElement.new(**field)
+ when 'Page'
+ FormLayoutElement::PageElement.new(**field)
+ else
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
+ "Unknow component type: #{field[:component]}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb
index d74be0d92..ab5b84ece 100644
--- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb
+++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb
@@ -58,10 +58,44 @@ def validate_no_layout_subfields!(fields)
end
def instantiate_subfields(fields)
- fields.map do |field|
- DynamicField.new(**field.to_h)
+ FormFactory.build_elements(fields)
+ end
+ end
+
+ class PageElement < LayoutElement
+ include ForestAdminDatasourceToolkit::Exceptions
+
+ attr_accessor :elements, :next_button_label, :previous_button_label
+
+ def initialize(options)
+ super(component: 'Page', **options)
+
+ validate_elements_presence!(options)
+ validate_no_page_elements!(options[:elements])
+ @next_button_label = options[:next_button_label]
+ @previous_button_label = options[:previous_button_label]
+ @elements = instantiate_elements(options[:elements] || [])
+ end
+
+ private
+
+ def validate_elements_presence!(options)
+ return if options.key?(:elements)
+
+ raise ForestException, "Using 'elements' in a 'Page' configuration is mandatory"
+ end
+
+ def validate_no_page_elements!(elements)
+ elements&.each do |element|
+ if element[:component] == 'Page'
+ raise ForestException, "'Page' component cannot be used within 'elements'"
+ end
end
end
+
+ def instantiate_elements(elements)
+ FormFactory.build_elements(elements)
+ end
end
end
end
diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb
index 2ae07310d..7a86c87b4 100644
--- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb
+++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/types/field_type.rb
@@ -21,6 +21,8 @@ module FieldType
JSON = 'Json'.freeze
+ LAYOUT = 'Layout'.freeze
+
NUMBER = 'Number'.freeze
NUMBER_LIST = 'NumberList'.freeze
diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator_spec.rb
index 7c62798c6..199eb4003 100644
--- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator_spec.rb
+++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator_spec.rb
@@ -127,81 +127,119 @@ module Action
end
describe 'with a single action with a if_conditions' do
- before do
- @decorated_book.add_action(
- 'make photocopy',
- BaseAction.new(
- scope: Types::ActionScope::GLOBAL,
- form: [
- { label: 'noIf', type: Types::FieldType::STRING },
- {
- label: 'dynamicIfFalse',
- type: Types::FieldType::STRING,
- if_condition: proc { false }
- },
- {
- label: 'dynamicIfTrue',
- type: Types::FieldType::STRING,
- if_condition: proc { true }
- },
- {
- type: 'Layout',
- component: 'Row',
- fields: [
- {
- type: Types::FieldType::STRING,
- label: 'sub_field_1',
- if_condition: proc { false }
- },
- {
- type: Types::FieldType::STRING,
- label: 'sub_field_2',
- if_condition: proc { true }
- }
- ]
- },
- {
- type: 'Layout',
- component: 'Row',
- fields: [
- {
- type: Types::FieldType::STRING,
- label: 'sub_field_3',
- if_condition: proc { false }
- }
- ]
- }
- ]
- ) { |_context, result_builder| result_builder.error(message: 'meeh') }
- )
- end
+ context 'with row element' do
+ before do
+ @decorated_book.add_action(
+ 'make photocopy',
+ BaseAction.new(
+ scope: Types::ActionScope::GLOBAL,
+ form: [
+ { label: 'noIf', type: Types::FieldType::STRING },
+ {
+ label: 'dynamicIfFalse',
+ type: Types::FieldType::STRING,
+ if_condition: proc { false }
+ },
+ {
+ label: 'dynamicIfTrue',
+ type: Types::FieldType::STRING,
+ if_condition: proc { true }
+ },
+ {
+ type: 'Layout',
+ component: 'Row',
+ fields: [
+ {
+ type: Types::FieldType::STRING,
+ label: 'sub_field_1',
+ if_condition: proc { false }
+ },
+ {
+ type: Types::FieldType::STRING,
+ label: 'sub_field_2',
+ if_condition: proc { true }
+ }
+ ]
+ },
+ {
+ type: 'Layout',
+ component: 'Row',
+ fields: [
+ {
+ type: Types::FieldType::STRING,
+ label: 'sub_field_3',
+ if_condition: proc { false }
+ }
+ ]
+ }
+ ]
+ ) { |_context, result_builder| result_builder.error(message: 'meeh') }
+ )
+ end
- it 'drop ifs which are false if required' do
- form = @decorated_book.get_form(caller, 'make photocopy', {}, Filter.new, { include_hidden_fields: false })
+ it 'drop ifs which are false if required' do
+ form = @decorated_book.get_form(caller, 'make photocopy', {}, Filter.new, { include_hidden_fields: false })
- expect(form).to include(
- have_attributes(label: 'noIf', type: 'String'),
- have_attributes(label: 'dynamicIfTrue', type: 'String')
- )
- end
+ expect(form).to include(
+ have_attributes(label: 'noIf', type: 'String'),
+ have_attributes(label: 'dynamicIfTrue', type: 'String')
+ )
+ end
- it 'drop row element if fields are empty and remove field not required in row' do
- form = @decorated_book.get_form(caller, 'make photocopy', {}, Filter.new, { include_hidden_fields: false })
+ it 'drop row element if fields are empty and remove field not required in row' do
+ form = @decorated_book.get_form(caller, 'make photocopy', {}, Filter.new, { include_hidden_fields: false })
- expect(form.size).to eq(3)
- expect(form.last.fields).to include(
- have_attributes(label: 'sub_field_2', type: 'String')
- )
+ expect(form.size).to eq(3)
+ expect(form.last.fields).to include(
+ have_attributes(label: 'sub_field_2', type: 'String')
+ )
+ end
+
+ it 'not dropIfs if required' do
+ form = @decorated_book.get_form(caller, 'make photocopy', {}, Filter.new, { include_hidden_fields: true })
+
+ expect(form).to include(
+ have_attributes(label: 'noIf', type: 'String'),
+ have_attributes(label: 'dynamicIfFalse', type: 'String'),
+ have_attributes(label: 'dynamicIfTrue', type: 'String')
+ )
+ end
end
- it 'not dropIfs if required' do
- form = @decorated_book.get_form(caller, 'make photocopy', {}, Filter.new, { include_hidden_fields: true })
+ context 'with page element' do
+ before do
+ @decorated_book.add_action(
+ 'make photocopy',
+ BaseAction.new(
+ scope: Types::ActionScope::GLOBAL,
+ form: [
+ {
+ type: 'Layout',
+ component: 'Page',
+ elements: [
+ { label: 'field_1', type: Types::FieldType::STRING, if_condition: proc { false } }
+ ]
+ },
+ {
+ type: 'Layout',
+ component: 'Page',
+ elements: [
+ { label: 'field_2', type: Types::FieldType::STRING, if_condition: proc { true } }
+ ]
+ }
+ ]
+ ) { |_context, result_builder| result_builder.error(message: 'meeh') }
+ )
+ end
- expect(form).to include(
- have_attributes(label: 'noIf', type: 'String'),
- have_attributes(label: 'dynamicIfFalse', type: 'String'),
- have_attributes(label: 'dynamicIfTrue', type: 'String')
- )
+ it 'drop page if element are empty' do
+ form = @decorated_book.get_form(caller, 'make photocopy', {}, Filter.new, { include_hidden_fields: false })
+
+ expect(form.size).to eq(1)
+ expect(form.first.elements).to include(
+ have_attributes(label: 'field_2', type: 'String')
+ )
+ end
end
end
@@ -212,15 +250,21 @@ module Action
BaseAction.new(
scope: Types::ActionScope::GLOBAL,
form: [
- { label: 'field_1', type: Types::FieldType::STRING, default_value: proc { 'default value field_1' } },
{
type: 'Layout',
- component: 'Row',
- fields: [
+ component: 'Page',
+ elements: [
+ { label: 'field_1', type: Types::FieldType::STRING, default_value: proc { 'default value field_1' } },
{
- type: Types::FieldType::STRING,
- label: 'sub_field_1',
- default_value: proc { 'default value sub_field_1' }
+ type: 'Layout',
+ component: 'Row',
+ fields: [
+ {
+ type: Types::FieldType::STRING,
+ label: 'sub_field_1',
+ default_value: proc { 'default value sub_field_1' }
+ }
+ ]
}
]
}
@@ -233,11 +277,17 @@ module Action
form = @decorated_book.get_form(caller, 'make photocopy', {}, Filter.new, { include_hidden_fields: false })
expect(form).to include(
- have_attributes(label: 'field_1', type: 'String', value: 'default value field_1'),
have_attributes(
type: 'Layout',
- component: 'Row',
- fields: include(have_attributes(label: 'sub_field_1', type: 'String', value: 'default value sub_field_1'))
+ component: 'Page',
+ elements: include(
+ have_attributes(label: 'field_1', type: 'String', value: 'default value field_1'),
+ have_attributes(
+ type: 'Layout',
+ component: 'Row',
+ fields: include(have_attributes(label: 'sub_field_1', type: 'String', value: 'default value sub_field_1'))
+ )
+ )
)
)
end
@@ -458,6 +508,26 @@ module Action
@decorated_book.add_action('make photocopy', action)
end.to raise_error(Exceptions::ForestException, "🌳🌳🌳 A field must have an 'id' or a 'label' defined.")
end
+
+ it 'raise an error if form mix form elements and pages' do
+ action = BaseAction.new(
+ scope: Types::ActionScope::GLOBAL,
+ form: [
+ { label: 'amount', type: Types::FieldType::NUMBER },
+ {
+ type: 'Layout',
+ component: 'Page',
+ elements: [
+ { type: 'Layout', component: 'HtmlBlock', content: 'foo' }
+ ]
+ }
+ ]
+ ) { |_context, result_builder| result_builder.error(message: 'foo') }
+
+ expect do
+ @decorated_book.add_action('make photocopy', action)
+ end.to raise_error(Exceptions::ForestException, "🌳🌳🌳 You cannot mix pages and other form elements in smart action 'make photocopy' form")
+ end
end
end
end
diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb
index 1caaf7f0e..ecf48224a 100644
--- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb
+++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb
@@ -34,230 +34,6 @@ module Action
end
end
- describe 'when build_widget' do
- it 'returns a CheckboxField for widget type "Checkbox"' do
- result = action.build_widget(field_send_notification)
- expect(result).to be_a(WidgetField::CheckboxField)
- end
-
- it 'raises an exception for an unknown widget type' do
- field = { widget: 'UnknownWidget' }
- expect { action.build_widget(field) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException)
- end
-
- context 'when widget is AddressAutocomplete' do
- let(:field) { { label: 'foo', widget: 'AddressAutocomplete', type: 'String' } }
-
- it 'returns an AddressAutocompleteField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::AddressAutocompleteField)
- expect(result.widget).to eq('AddressAutocomplete')
- end
- end
-
- context 'when widget is CheckboxGroup' do
- let(:field) { { label: 'foo', widget: 'CheckboxGroup', type: 'StringList', options: [] } }
-
- it 'returns a CheckboxGroupField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::CheckboxGroupField)
- expect(result.widget).to eq('CheckboxGroup')
- end
- end
-
- context 'when widget is ColorPicker' do
- let(:field) { { label: 'foo', widget: 'ColorPicker', type: 'base_', enable_opacity: true } }
-
- it 'returns a ColorPickerField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::ColorPickerField)
- expect(result.widget).to eq('ColorPicker')
- end
- end
-
- context 'when widget is CurrencyInput' do
- let(:field) { { label: 'foo', widget: 'CurrencyInput', type: 'Number', currency: 'USD' } }
-
- it 'returns a CurrencyInputField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::CurrencyInputField)
- expect(result.widget).to eq('CurrencyInput')
- end
- end
-
- context 'when widget is DatePicker' do
- let(:field) { { label: 'foo', widget: 'DatePicker', type: 'Date' } }
-
- it 'returns a DatePickerField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::DatePickerField)
- expect(result.widget).to eq('DatePicker')
- end
- end
-
- context 'when widget is Dropdown' do
- let(:field) { { label: 'foo', widget: 'Dropdown', type: 'String', options: [] } }
-
- it 'returns a DropdownField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::DropdownField)
- expect(result.widget).to eq('Dropdown')
- end
- end
-
- context 'when widget is FilePicker' do
- let(:field) { { label: 'foo', widget: 'FilePicker', type: 'File', options: [] } }
-
- it 'returns a FilePickerField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::FilePickerField)
- expect(result.widget).to eq('FilePicker')
- end
- end
-
- context 'when widget is JsonEditor' do
- let(:field) { { label: 'foo', widget: 'JsonEditor', type: 'String' } }
-
- it 'returns a JsonEditorField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::JsonEditorField)
- expect(result.widget).to eq('JsonEditor')
- end
- end
-
- context 'when widget is NumberInput' do
- let(:field) { { label: 'foo', widget: 'NumberInput', type: 'Number' } }
-
- it 'returns a NumberInputField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::NumberInputField)
- expect(result.widget).to eq('NumberInput')
- end
- end
-
- context 'when widget is NumberInputList' do
- let(:field) { { label: 'foo', widget: 'NumberInputList', type: 'NumberList', options: [] } }
-
- it 'returns a NumberInputListField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::NumberInputListField)
- expect(result.widget).to eq('NumberInputList')
- end
- end
-
- context 'when widget is RadioGroup' do
- let(:field) { { label: 'foo', widget: 'RadioGroup', type: 'Number', options: [] } }
-
- it 'returns a RadioGroupField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::RadioGroupField)
- expect(result.widget).to eq('RadioGroup')
- end
- end
-
- context 'when widget is RichText' do
- let(:field) { { label: 'foo', widget: 'RichText', type: 'String' } }
-
- it 'returns a RichTextField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::RichTextField)
- expect(result.widget).to eq('RichText')
- end
- end
-
- context 'when widget is TextArea' do
- let(:field) { { label: 'foo', widget: 'TextArea', type: 'String' } }
-
- it 'returns a TextAreaField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::TextAreaField)
- expect(result.widget).to eq('TextArea')
- end
- end
-
- context 'when widget is TextInput' do
- let(:field) { { label: 'foo', widget: 'TextInput', type: 'String' } }
-
- it 'returns a TextInputField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::TextInputField)
- expect(result.widget).to eq('TextInput')
- end
- end
-
- context 'when widget is TextInputList' do
- let(:field) { { label: 'foo', widget: 'TextInputList', type: 'StringList', options: [] } }
-
- it 'returns a TextInputListField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::TextInputListField)
- expect(result.widget).to eq('TextInputList')
- end
- end
-
- context 'when widget is TimePicker' do
- let(:field) { { label: 'foo', widget: 'TimePicker', type: 'Time' } }
-
- it 'returns a TimePickerField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::TimePickerField)
- expect(result.widget).to eq('TimePicker')
- end
- end
-
- context 'when widget is UserDropdown' do
- let(:field) { { label: 'foo', widget: 'UserDropdown', type: 'String', options: [] } }
-
- it 'returns a UserDropdownField' do
- result = action.build_widget(field)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::UserDropdownField)
- expect(result.widget).to eq('UserDropdown')
- end
- end
- end
-
- describe 'when build_layout_element' do
- context 'when element is a separator' do
- let(:element) { { type: 'Layout', component: 'Separator' } }
-
- it 'returns a separator element' do
- result = action.build_layout_element(element)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::FormLayoutElement::SeparatorElement)
- end
- end
-
- context 'when element is a HtmlBlock' do
- let(:element) { { type: 'Layout', component: 'HtmlBlock', content: 'foo
' } }
-
- it 'returns a html block element' do
- result = action.build_layout_element(element)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::FormLayoutElement::HtmlBlockElement)
- expect(result.content).to eq('foo
')
- end
- end
-
- context 'when element is a Row' do
- let(:element) { { type: 'Layout', component: 'Row', fields: [field_send_notification, field_message] } }
-
- it 'returns a row element' do
- result = action.build_layout_element(element)
- expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::FormLayoutElement::RowElement)
- expect(result.fields[0].label).to eq('Send a notification')
- expect(result.fields[1].label).to eq('Notification message')
- end
-
- it 'raises an exception when fields are missing' do
- element.delete(:fields)
- expect { action.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException)
- end
-
- it 'raises an exception when fields contain a layout element' do
- element[:fields] << { type: 'Layout' }
- expect { action.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException)
- end
- end
- end
-
describe 'when check form is static' do
context 'when form is nil' do
let(:action) { described_class.new(scope: :single) }
diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/form_factory_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/form_factory_spec.rb
new file mode 100644
index 000000000..3d451c8f8
--- /dev/null
+++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/form_factory_spec.rb
@@ -0,0 +1,267 @@
+require 'spec_helper'
+
+module ForestAdminDatasourceCustomizer
+ module Decorators
+ module Action
+ describe FormFactory do
+ let(:scope) { Types::ActionScope::SINGLE }
+ let(:field_send_notification) { { label: 'Send a notification', type: 'Boolean', widget: 'Checkbox', is_required: true, default_value: false } }
+ let(:field_message) { { label: 'Notification message', type: 'String', is_required: true, default_value: 'Hello' } }
+ let(:form) do
+ [
+ field_send_notification,
+ field_message
+ ]
+ end
+ let(:action) { BaseAction.new(scope: scope, form: form) }
+
+ describe 'when build_widget' do
+ it 'returns a CheckboxField for widget type "Checkbox"' do
+ result = described_class.build_widget(field_send_notification)
+ expect(result).to be_a(WidgetField::CheckboxField)
+ end
+
+ it 'raises an exception for an unknown widget type' do
+ field = { widget: 'UnknownWidget' }
+ expect { described_class.build_widget(field) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException)
+ end
+
+ context 'when widget is AddressAutocomplete' do
+ let(:field) { { label: 'foo', widget: 'AddressAutocomplete', type: 'String' } }
+
+ it 'returns an AddressAutocompleteField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::AddressAutocompleteField)
+ expect(result.widget).to eq('AddressAutocomplete')
+ end
+ end
+
+ context 'when widget is CheckboxGroup' do
+ let(:field) { { label: 'foo', widget: 'CheckboxGroup', type: 'StringList', options: [] } }
+
+ it 'returns a CheckboxGroupField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::CheckboxGroupField)
+ expect(result.widget).to eq('CheckboxGroup')
+ end
+ end
+
+ context 'when widget is ColorPicker' do
+ let(:field) { { label: 'foo', widget: 'ColorPicker', type: 'base_', enable_opacity: true } }
+
+ it 'returns a ColorPickerField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::ColorPickerField)
+ expect(result.widget).to eq('ColorPicker')
+ end
+ end
+
+ context 'when widget is CurrencyInput' do
+ let(:field) { { label: 'foo', widget: 'CurrencyInput', type: 'Number', currency: 'USD' } }
+
+ it 'returns a CurrencyInputField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::CurrencyInputField)
+ expect(result.widget).to eq('CurrencyInput')
+ end
+ end
+
+ context 'when widget is DatePicker' do
+ let(:field) { { label: 'foo', widget: 'DatePicker', type: 'Date' } }
+
+ it 'returns a DatePickerField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::DatePickerField)
+ expect(result.widget).to eq('DatePicker')
+ end
+ end
+
+ context 'when widget is Dropdown' do
+ let(:field) { { label: 'foo', widget: 'Dropdown', type: 'String', options: [] } }
+
+ it 'returns a DropdownField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::DropdownField)
+ expect(result.widget).to eq('Dropdown')
+ end
+ end
+
+ context 'when widget is FilePicker' do
+ let(:field) { { label: 'foo', widget: 'FilePicker', type: 'File', options: [] } }
+
+ it 'returns a FilePickerField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::FilePickerField)
+ expect(result.widget).to eq('FilePicker')
+ end
+ end
+
+ context 'when widget is JsonEditor' do
+ let(:field) { { label: 'foo', widget: 'JsonEditor', type: 'String' } }
+
+ it 'returns a JsonEditorField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::JsonEditorField)
+ expect(result.widget).to eq('JsonEditor')
+ end
+ end
+
+ context 'when widget is NumberInput' do
+ let(:field) { { label: 'foo', widget: 'NumberInput', type: 'Number' } }
+
+ it 'returns a NumberInputField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::NumberInputField)
+ expect(result.widget).to eq('NumberInput')
+ end
+ end
+
+ context 'when widget is NumberInputList' do
+ let(:field) { { label: 'foo', widget: 'NumberInputList', type: 'NumberList', options: [] } }
+
+ it 'returns a NumberInputListField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::NumberInputListField)
+ expect(result.widget).to eq('NumberInputList')
+ end
+ end
+
+ context 'when widget is RadioGroup' do
+ let(:field) { { label: 'foo', widget: 'RadioGroup', type: 'Number', options: [] } }
+
+ it 'returns a RadioGroupField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::RadioGroupField)
+ expect(result.widget).to eq('RadioGroup')
+ end
+ end
+
+ context 'when widget is RichText' do
+ let(:field) { { label: 'foo', widget: 'RichText', type: 'String' } }
+
+ it 'returns a RichTextField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::RichTextField)
+ expect(result.widget).to eq('RichText')
+ end
+ end
+
+ context 'when widget is TextArea' do
+ let(:field) { { label: 'foo', widget: 'TextArea', type: 'String' } }
+
+ it 'returns a TextAreaField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::TextAreaField)
+ expect(result.widget).to eq('TextArea')
+ end
+ end
+
+ context 'when widget is TextInput' do
+ let(:field) { { label: 'foo', widget: 'TextInput', type: 'String' } }
+
+ it 'returns a TextInputField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::TextInputField)
+ expect(result.widget).to eq('TextInput')
+ end
+ end
+
+ context 'when widget is TextInputList' do
+ let(:field) { { label: 'foo', widget: 'TextInputList', type: 'StringList', options: [] } }
+
+ it 'returns a TextInputListField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::TextInputListField)
+ expect(result.widget).to eq('TextInputList')
+ end
+ end
+
+ context 'when widget is TimePicker' do
+ let(:field) { { label: 'foo', widget: 'TimePicker', type: 'Time' } }
+
+ it 'returns a TimePickerField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::TimePickerField)
+ expect(result.widget).to eq('TimePicker')
+ end
+ end
+
+ context 'when widget is UserDropdown' do
+ let(:field) { { label: 'foo', widget: 'UserDropdown', type: 'String', options: [] } }
+
+ it 'returns a UserDropdownField' do
+ result = described_class.build_widget(field)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::WidgetField::UserDropdownField)
+ expect(result.widget).to eq('UserDropdown')
+ end
+ end
+ end
+
+ describe 'when build_layout_element' do
+ context 'when element is a separator' do
+ let(:element) { { type: 'Layout', component: 'Separator' } }
+
+ it 'returns a separator element' do
+ result = described_class.build_layout_element(element)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::FormLayoutElement::SeparatorElement)
+ end
+ end
+
+ context 'when element is a HtmlBlock' do
+ let(:element) { { type: 'Layout', component: 'HtmlBlock', content: 'foo
' } }
+
+ it 'returns a html block element' do
+ result = described_class.build_layout_element(element)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::FormLayoutElement::HtmlBlockElement)
+ expect(result.content).to eq('foo
')
+ end
+ end
+
+ context 'when element is a Row' do
+ let(:element) { { type: 'Layout', component: 'Row', fields: [field_send_notification, field_message] } }
+
+ it 'returns a row element' do
+ result = described_class.build_layout_element(element)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::FormLayoutElement::RowElement)
+ expect(result.fields[0].label).to eq('Send a notification')
+ expect(result.fields[1].label).to eq('Notification message')
+ end
+
+ it 'raises an exception when fields are missing' do
+ element.delete(:fields)
+ expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException)
+ end
+
+ it 'raises an exception when fields contain a layout element' do
+ element[:fields] << { type: 'Layout' }
+ expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException)
+ end
+ end
+
+ context 'when element is a Page' do
+ let(:element) { { type: 'Layout', component: 'Page', elements: [field_send_notification, field_message], next_button_label: proc { 'Next' }, previous_button_label: proc { 'Previous' } } }
+
+ it 'returns a page element' do
+ result = described_class.build_layout_element(element)
+ expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::FormLayoutElement::PageElement)
+ expect(result.elements[0].label).to eq('Send a notification')
+ expect(result.elements[1].label).to eq('Notification message')
+ expect(result.next_button_label).to be_a(Proc)
+ expect(result.previous_button_label).to be_a(Proc)
+ end
+
+ it 'raises an exception when there is no elements' do
+ element.delete(:elements)
+ expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, "🌳🌳🌳 Using 'elements' in a 'Page' configuration is mandatory")
+ end
+
+ it 'raises an error when element contains a Page' do
+ element[:elements] = [element]
+ expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, "🌳🌳🌳 'Page' component cannot be used within 'elements'")
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field.rb
index a226155bd..e1865e49f 100644
--- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field.rb
+++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field.rb
@@ -37,6 +37,15 @@ def initialize(
def watch_changes?
@watch_changes
end
+
+ def to_h
+ result = {}
+ instance_variables.each do |attribute|
+ result[attribute.to_s.delete('@').to_sym] = instance_variable_get(attribute)
+ end
+
+ result
+ end
end
end
end
diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb
index 0a7fd1183..074e785bc 100644
--- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb
+++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb
@@ -3,7 +3,7 @@ module Components
module Actions
class ActionFieldFactory
def self.build(field)
- if field.key? :widget
+ if field.key?(:widget) && !field[:widget].nil?
build_widget(field)
elsif field[:type] == 'Layout'
build_layout_element(field)
@@ -21,6 +21,10 @@ def self.build_layout_element(field)
when 'Row'
return ActionLayoutElement::RowElement.new(**field) unless field[:fields].empty?
+ nil
+ when 'Page'
+ return ActionLayoutElement::PageElement.new(**field) unless field[:elements].empty?
+
nil
end
end
diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_layout_element.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_layout_element.rb
index 3ab0569b3..410fb41a4 100644
--- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_layout_element.rb
+++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_layout_element.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/inflections'
+
module ForestAdminDatasourceToolkit
module Components
module Actions
@@ -49,7 +51,31 @@ class RowElement < BaseLayoutElement
def initialize(fields:, **options)
super(component: 'Row', **options)
- @fields = fields
+ @fields = instantiate_subfields(fields)
+ end
+
+ def instantiate_subfields(fields)
+ fields.map do |field|
+ ActionFieldFactory.build(field.to_h)
+ end
+ end
+ end
+
+ class PageElement < BaseLayoutElement
+ attr_accessor :elements, :next_button_label, :previous_button_label
+
+ def initialize(elements:, previous_button_label:, next_button_label:, **options)
+ super(component: 'Page', **options)
+ @elements = elements
+ @next_button_label = next_button_label
+ @previous_button_label = previous_button_label
+ @elements = instantiate_elements(elements)
+ end
+
+ def instantiate_elements(elements)
+ elements.map do |element|
+ ActionFieldFactory.build(element.to_h)
+ end
end
end
end