Skip to content

Commit 7a8b369

Browse files
committed
WIP: Add 'array' block
TODO: - Add tests - Support for translations - Clarify the publishing API payload mapping.
1 parent 5656538 commit 7a8b369

6 files changed

Lines changed: 125 additions & 20 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module ConfigurableContentBlocks
2+
class DefaultArray
3+
attr_reader :block_factory
4+
5+
def initialize(block_factory)
6+
@block_factory = block_factory
7+
end
8+
9+
def to_partial_path
10+
"admin/configurable_content_blocks/default_array"
11+
end
12+
end
13+
end

app/models/configurable_content_blocks/factory.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def build_block(block)
1212
"image_select" => ->(page) { ConfigurableContentBlocks::ImageSelect.new(page.valid_images) },
1313
"lead_image_select" => ->(page) { ConfigurableContentBlocks::LeadImageSelect.new(page.valid_lead_images, default_lead_image: page.default_lead_image, placeholder_image_url: page.placeholder_image_url) },
1414
"default_object" => ->(_page) { ConfigurableContentBlocks::DefaultObject.new(self) },
15+
"default_array" => ->(_page) { ConfigurableContentBlocks::DefaultArray.new(self) },
1516
}.freeze
1617

1718
raise "Block #{block} is not defined" if blocks[block].nil?

app/models/standard_edition/block_content.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ def attributes=(values)
2727
setter = "#{key}="
2828
if nested_schema["type"] == "date"
2929
public_send(setter, pre_validate_date_attribute(key, values[key]))
30+
elsif nested_schema["type"] == "array"
31+
#  Convert
32+
# { "0" => { "foo" => "bar" }, "1" => { "delete_me" => "something", "_destroy" => "1" } }
33+
# to
34+
# [ { "foo" => "bar" } ]
35+
array_elements = values[key].is_a?(Hash) ? values[key].values : values[key]
36+
public_send(setter, array_elements.reject { |h| h["_destroy"] == "1" })
3037
else
3138
public_send(setter, values[key])
3239
end
@@ -74,6 +81,8 @@ def attributes_class_for(attribute_config)
7481
case property_schema["type"]
7582
when "object"
7683
attribute key
84+
when "array"
85+
attribute key
7786
else
7887
attribute key, property_schema["type"].to_sym
7988
end
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<% required ||= false
2+
content ||= {}
3+
translated_content ||= {}
4+
right_to_left ||= false
5+
errors ||= []
6+
required_attributes ||= []
7+
element_properties = schema.fetch("fields") %>
8+
9+
<%
10+
render_fields = ->(element, index) do
11+
element_fields = capture do
12+
element_properties.each do |element_key, property_schema|
13+
block = default_array.block_factory.build_block(property_schema["block"])
14+
%>
15+
<%= render block, {
16+
schema: property_schema,
17+
content: element[element_key],
18+
# TODO: support translations
19+
# translated_content: element[element_key],
20+
path: ConfigurableContentBlocks::Path.new([property_key, index, element_key]),
21+
required: required_attributes.include?(element_key),
22+
required_attributes: property_schema["block"] == "default_array" ? required_attributes : [],
23+
right_to_left: right_to_left,
24+
errors: errors,
25+
} %>
26+
<%
27+
end # element_properties.each loop
28+
end # element_fields = capture
29+
end # render_fields definition
30+
%>
31+
32+
<%= render "govuk_publishing_components/components/fieldset",
33+
{ legend_text: "#{schema['title']}#{required ? ' (required)' : ''}",
34+
heading_size: "l",
35+
id: path.form_control_id } do %>
36+
<%
37+
items = content[property_key].map.with_index do |element, index|
38+
{
39+
fields: render_fields.call(element, index),
40+
destroy_checkbox: render("govuk_publishing_components/components/checkboxes", {
41+
name: "#{path.form_control_name}[#{index}][_destroy]",
42+
items: [{ label: "Remove", value: "1" }],
43+
}),
44+
}
45+
end
46+
%>
47+
48+
<%= render "govuk_publishing_components/components/add_another", {
49+
fieldset_legend: schema["title"],
50+
add_button_text: "Add another",
51+
empty_fields: true,
52+
items: items,
53+
empty: render_fields.call({}, items.length),
54+
} %>
55+
<% end %>

app/views/admin/configurable_content_blocks/_default_object.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<% block = default_object.block_factory.build_block(property_schema["block"]) %>
1313
<%= render block, {
1414
schema: property_schema,
15+
property_key: property_key,
1516
content: property_schema.key?("fields") ? content : content[property_key],
1617
translated_content: property_schema.key?("fields") ? translated_content : translated_content[property_key],
1718
path: ConfigurableContentBlocks::Path.new([property_key]),

public/configurable-document-type.schema.json

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"type": "string",
4848
"description": "Identifier for a content block. Maps to a specific block class.",
4949
"enum": [
50+
"default_array",
5051
"default_date",
5152
"default_object",
5253
"default_string",
@@ -66,6 +67,49 @@
6667
},
6768
"required": ["title", "block"],
6869
"additionalProperties": false
70+
},
71+
"block_attributes": {
72+
"type": "object",
73+
"description": "Map of content block attribute definitions (minimal schema).",
74+
"patternProperties": {
75+
".*": { "$ref": "#/$defs/block_attribute" }
76+
},
77+
"additionalProperties": false
78+
},
79+
"block_attribute": {
80+
"type": "object",
81+
"description": "Minimal schema information for a single content block attribute.",
82+
"properties": {
83+
"type": {
84+
"type": "string",
85+
"description": "Data type for the content block. Should map to an active record type, with the exception of the object type.",
86+
"enum": ["string", "integer", "date", "array"]
87+
},
88+
"attributes": {
89+
"description": "For array types, defines the schema of each array item using the same minimal attribute format.",
90+
"$ref": "#/$defs/block_attributes"
91+
}
92+
},
93+
"required": ["type"],
94+
"additionalProperties": false,
95+
"allOf": [
96+
{
97+
"if": {
98+
"properties": { "type": { "const": "array" } },
99+
"required": ["type"]
100+
},
101+
"then": {
102+
"properties": {
103+
"attributes": { "$ref": "#/$defs/block_attributes" }
104+
}
105+
},
106+
"else": {
107+
"not": {
108+
"required": ["attributes"]
109+
}
110+
}
111+
}
112+
]
69113
}
70114
},
71115
"title": "Configurable Document Type",
@@ -90,26 +134,8 @@
90134
"description": "Defines the content structure for the document type. Each property represents a content block.",
91135
"properties": {
92136
"attributes": {
93-
"type": "object",
94-
"description": "Alternative to `properties`, defining content blocks with minimal schema information.",
95-
"patternProperties": {
96-
".*": {
97-
"type": "object",
98-
"properties": {
99-
"type": {
100-
"type": "string",
101-
"description": "Data type for the content block. Should map to an active record type, with the exception of the object type.",
102-
"enum": [
103-
"string",
104-
"integer",
105-
"date"
106-
]
107-
}
108-
},
109-
"required": ["type"],
110-
"additionalProperties": false
111-
}
112-
}
137+
"$ref": "#/$defs/block_attributes",
138+
"description": "Alternative to `properties`, defining content blocks with minimal schema information."
113139
},
114140
"validations": {
115141
"$ref": "#/$defs/validations"

0 commit comments

Comments
 (0)