require "formalist/element" require "formalist/types" module Formalist class Elements class Many < Element attribute :action_label, Types::String attribute :sortable, Types::Bool attribute :label, Types::String attribute :max_height, Types::String attribute :placeholder, Types::String attribute :validation, Types::Validation # @api private attr_reader :child_template # @api private def self.build(children: [], **args) child_template = children.dup super(child_template: child_template, **args) end # @api private def initialize(child_template:, **args) @child_template = child_template super(**args) end # @api private def fill(input: {}, errors: {}) input = input.fetch(name) { [] } errors = errors.fetch(name) { {} } children = input.each_with_index.map { |child_input, index| # Child errors look like this: {0=>{:summary=>["must be filled"]} child_errors = errors.fetch(index) { {} } child_template.map { |child| child.fill(input: child_input, errors: child_errors) } } super( input: input, errors: errors, children: children, child_template: child_template, ) end # Until we can put defaults on `Types::Bool`, supply them here # @api private def attributes { allow_create: true, allow_update: true, allow_destroy: true, allow_reorder: true, }.merge(super) end # Converts a collection of "many" repeating elements into an abstract # syntax tree. # # It takes the following format: # # ``` # [:many, [params]] # ``` # # With the following parameters: # # 1. Collection name # 2. Custom form element type (or `:many` otherwise) # 3. Collection-level error messages # 4. Form element attributes # 5. Child element "template" (i.e. the form elements comprising a # single entry in the collection of "many" elements, without any user # data associated) # 6. Child elements, one for each of the entries in the input data (or # none, if there is no or empty input data) # # @see Formalist::Element::Attributes#to_ast "Form element attributes" structure # # @example "locations" collection # many.to_ast # # => [:many, [ # :locations, # :many, # ["locations size cannot be less than 3"], # [:object, [ # [:allow_create, [:value, [true]]], # [:allow_update, [:value, [true]]], # [:allow_destroy, [:value, [true]]], # [:allow_reorder, [:value, [true]]] # ]], # [ # [:field, [:name, :field, nil, [], [], [:object, []]]], # [:field, [:address, :field, nil, [], [], [:object, []]]] # ], # [ # [ # [:field, [:name, :field, "Icelab Canberra", [], [], [:object, []]]], # [:field, [:address, :field, "Canberra, ACT, Australia", [], [], [:object, []]]] # ], # [ # [:field, [:name, :field, "Icelab Melbourne", [], [], [:object, []]]], # [:field, [:address, :field, "Melbourne, VIC, Australia", [], [], [:object, []]]] # ] # ] # ]] # # @return [Array] the collection as an abstract syntax tree. def to_ast local_errors = errors.is_a?(Array) ? errors : [] [:many, [ name, type, local_errors, Element::Attributes.new(attributes).to_ast, child_template.map(&:to_ast), children.map { |el_list| el_list.map(&:to_ast) }, ]] end end end end