diff --git a/src/Altinn.App.Core/Models/Layout/Components/GridComponent.cs b/src/Altinn.App.Core/Models/Layout/Components/GridComponent.cs
index b944439ae..4315f36e4 100644
--- a/src/Altinn.App.Core/Models/Layout/Components/GridComponent.cs
+++ b/src/Altinn.App.Core/Models/Layout/Components/GridComponent.cs
@@ -12,8 +12,8 @@ public class GridComponent : GroupComponent
///
/// Constructor for RepeatingGroupComponent
///
- public GridComponent(string id, string type, IReadOnlyDictionary? dataModelBindings, IEnumerable children, Expression? hidden, Expression? required, Expression? readOnly, IReadOnlyDictionary? additionalProperties) :
- base(id, type, dataModelBindings, children, hidden, required, readOnly, additionalProperties)
+ public GridComponent(string id, string type, IReadOnlyDictionary? dataModelBindings, IEnumerable children, IEnumerable? childIDs, Expression? hidden, Expression? required, Expression? readOnly, IReadOnlyDictionary? additionalProperties) :
+ base(id, type, dataModelBindings, children, childIDs, hidden, required, readOnly, additionalProperties)
{ }
}
diff --git a/src/Altinn.App.Core/Models/Layout/Components/GroupComponent.cs b/src/Altinn.App.Core/Models/Layout/Components/GroupComponent.cs
index e56cf8d42..9bb1bfd98 100644
--- a/src/Altinn.App.Core/Models/Layout/Components/GroupComponent.cs
+++ b/src/Altinn.App.Core/Models/Layout/Components/GroupComponent.cs
@@ -12,18 +12,76 @@ public class GroupComponent : BaseComponent
///
/// Constructor for GroupComponent
///
- public GroupComponent(string id, string type, IReadOnlyDictionary? dataModelBindings, IEnumerable children, Expression? hidden, Expression? required, Expression? readOnly, IReadOnlyDictionary? additionalProperties) :
+ public GroupComponent(string id, string type, IReadOnlyDictionary? dataModelBindings, IEnumerable children, IEnumerable? childIDs, Expression? hidden, Expression? required, Expression? readOnly, IReadOnlyDictionary? additionalProperties) :
base(id, type, dataModelBindings, hidden, required, readOnly, additionalProperties)
{
+
Children = children;
+ ChildIDs = childIDs ?? children.Select(c => c.Id);
foreach (var child in Children)
{
child.Parent = this;
}
}
-
+
///
/// The children in this group/page
///
- public IEnumerable Children { get; internal set; }
-}
\ No newline at end of file
+ public IEnumerable Children { get; private set; }
+
+ ///
+ /// The child IDs in this group/page
+ ///
+ public IEnumerable ChildIDs { get; internal set; }
+
+ ///
+ /// Adds a child component which is already defined in its child IDs
+ ///
+ public virtual void AddChild(BaseComponent child)
+ {
+ if (!this.ChildIDs.Contains(child.Id))
+ {
+ throw new ArgumentException($"Child with id {child.Id} is not defined in the child IDs of this group");
+ }
+ if (this.Children.FirstOrDefault(c => c.Id == child.Id) != null)
+ {
+ throw new ArgumentException($"Child with id {child.Id} is already added to this group");
+ }
+ child.Parent = this;
+ this.Children = this.Children.Append(child);
+ }
+
+ ///
+ /// Validates that the children in this group matches the child IDs and orders the children according to the child IDs
+ ///
+ public void ValidateChildren()
+ {
+ var childIDs = this.ChildIDs.ToList();
+
+ foreach (var childID in childIDs)
+ {
+ if (!this.Children.Select(c => c.Id).Contains(childID))
+ {
+ throw new ArgumentException($"Child with id {childID} could not be found for the group {this.Id}");
+ }
+ }
+
+ var childIDCount = childIDs.Count;
+ var childCount = this.Children.Count();
+
+ if (childCount != childIDCount)
+ {
+ throw new ArgumentException($"The number of children ({childCount}) in group {this.Id} does not match the number of child IDs provided ({childIDCount})");
+ }
+
+ this.Children = this.Children.OrderBy(c => childIDs.IndexOf(c.Id));
+
+ foreach (var child in this.Children)
+ {
+ if (child is GroupComponent group)
+ {
+ group.ValidateChildren();
+ }
+ }
+ }
+}
diff --git a/src/Altinn.App.Core/Models/Layout/Components/PageComponent.cs b/src/Altinn.App.Core/Models/Layout/Components/PageComponent.cs
index c560c59fb..3bb3aff00 100644
--- a/src/Altinn.App.Core/Models/Layout/Components/PageComponent.cs
+++ b/src/Altinn.App.Core/Models/Layout/Components/PageComponent.cs
@@ -1,5 +1,3 @@
-using System.Collections.Immutable;
-using System.Text.Json;
using System.Text.Json.Serialization;
using Altinn.App.Core.Models.Expressions;
@@ -16,7 +14,7 @@ public class PageComponent : GroupComponent
/// Constructor for PageComponent
///
public PageComponent(string id, List children, Dictionary componentLookup, Expression? hidden, Expression? required, Expression? readOnly, IReadOnlyDictionary? extra) :
- base(id, "page", null, children, hidden, required, readOnly, extra)
+ base(id, "page", null, children, null, hidden, required, readOnly, extra)
{
ComponentLookup = componentLookup;
}
@@ -25,4 +23,12 @@ public PageComponent(string id, List children, Dictionary
public Dictionary ComponentLookup { get; }
+
+ ///
+ /// AddChild is not needed for PageComponent
+ ///
+ public override void AddChild(BaseComponent child)
+ {
+ throw new NotImplementedException();
+ }
}
diff --git a/src/Altinn.App.Core/Models/Layout/Components/RepeatingGroupComponent.cs b/src/Altinn.App.Core/Models/Layout/Components/RepeatingGroupComponent.cs
index d28ab7387..f6302b572 100644
--- a/src/Altinn.App.Core/Models/Layout/Components/RepeatingGroupComponent.cs
+++ b/src/Altinn.App.Core/Models/Layout/Components/RepeatingGroupComponent.cs
@@ -1,7 +1,3 @@
-using System.Collections.Immutable;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
using Altinn.App.Core.Models.Expressions;
namespace Altinn.App.Core.Models.Layout.Components;
@@ -14,8 +10,8 @@ public class RepeatingGroupComponent : GroupComponent
///
/// Constructor for RepeatingGroupComponent
///
- public RepeatingGroupComponent(string id, string type, IReadOnlyDictionary? dataModelBindings, IEnumerable children, int maxCount, Expression? hidden, Expression? required, Expression? readOnly, IReadOnlyDictionary? additionalProperties) :
- base(id, type, dataModelBindings, children, hidden, required, readOnly, additionalProperties)
+ public RepeatingGroupComponent(string id, string type, IReadOnlyDictionary? dataModelBindings, IEnumerable children, IEnumerable? childIDs, int maxCount, Expression? hidden, Expression? required, Expression? readOnly, IReadOnlyDictionary? additionalProperties) :
+ base(id, type, dataModelBindings, children, childIDs, hidden, required, readOnly, additionalProperties)
{
MaxCount = maxCount;
}
@@ -24,4 +20,4 @@ public RepeatingGroupComponent(string id, string type, IReadOnlyDictionary
public int MaxCount { get; }
-}
\ No newline at end of file
+}
diff --git a/src/Altinn.App.Core/Models/Layout/PageComponentConverter.cs b/src/Altinn.App.Core/Models/Layout/PageComponentConverter.cs
index 0ea1b7d31..865e56733 100644
--- a/src/Altinn.App.Core/Models/Layout/PageComponentConverter.cs
+++ b/src/Altinn.App.Core/Models/Layout/PageComponentConverter.cs
@@ -91,7 +91,7 @@ private PageComponent ReadData(ref Utf8JsonReader reader, string pageName, JsonS
var components = new List();
var componentLookup = new Dictionary();
- var childToGroupMapping = new Dictionary();
+ var childToGroupMapping = new Dictionary();
// Hidden is the only property that cascades.
Expression? hidden = null;
@@ -133,11 +133,11 @@ private PageComponent ReadData(ref Utf8JsonReader reader, string pageName, JsonS
}
var page = new PageComponent(pageName, components, componentLookup, hidden, required, readOnly, additionalProperties);
- ValidateGroupChildren(page);
+ page.ValidateChildren();
return page;
}
- private void ReadLayout(ref Utf8JsonReader reader, List components, Dictionary componentLookup, Dictionary childToGroupMapping, JsonSerializerOptions options)
+ private void ReadLayout(ref Utf8JsonReader reader, List components, Dictionary componentLookup, Dictionary childToGroupMapping, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray)
{
@@ -152,16 +152,9 @@ private void ReadLayout(ref Utf8JsonReader reader, List component
AddChildrenToLookup(component, componentLookup);
// Check if component should be added to group children or to page
- if (childToGroupMapping.ContainsKey(component.Id))
+ if (childToGroupMapping.TryGetValue(component.Id, out var groupComponent))
{
- var (parent, index) = childToGroupMapping[component.Id];
- var children = (List)parent.Children;
- while (children.Count <= index)
- {
- children.Add(null!);
- }
- children[index] = component;
- component.Parent = parent;
+ groupComponent.AddChild(component);
}
else
{
@@ -179,23 +172,26 @@ private static void AddChildrenToLookup(BaseComponent component, Dictionary children, Dictionary childToGroupMapping)
+ private static readonly Regex MultiPageIndexRegex = new Regex(@"^(\d+:)?([^\s:]+)$", RegexOptions.None, TimeSpan.FromSeconds(1));
+ private static string GetIdWithoutMultiPageIndex(string id)
{
- for (var index = 0; index < children.Count; index++)
+ var match = MultiPageIndexRegex.Match(id);
+ return match.Groups[2].Value;
+ }
+
+ private static void AddChildrenToMapping(GroupComponent component, List children, Dictionary childToGroupMapping)
+ {
+ foreach (var childId in children)
{
- // Remove MultiPageIndex if present
- var match = MultiPageIndexRegex.Match(children[index]);
- var childId = match.Groups[2].Value;
- if (childToGroupMapping.ContainsKey(childId))
+ if (childToGroupMapping.TryGetValue(childId, out var existingMapping))
{
- throw new JsonException($"Component \"{component.Id}\" tried to claim \"{childId}\" as a child, but that child is already claimed by \"{childToGroupMapping[childId].Item1.Id}\"");
+ throw new JsonException($"Component \"{component.Id}\" tried to claim \"{childId}\" as a child, but that child is already claimed by \"{existingMapping.Id}\"");
}
- childToGroupMapping[childId] = (component, index);
+ childToGroupMapping[childId] = component;
}
}
- private BaseComponent ReadComponent(ref Utf8JsonReader reader, Dictionary childToGroupMapping, JsonSerializerOptions options)
+ private BaseComponent ReadComponent(ref Utf8JsonReader reader, Dictionary childToGroupMapping, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
@@ -248,7 +244,7 @@ private BaseComponent ReadComponent(ref Utf8JsonReader reader, Dictionary>(ref reader, options);
+ children = JsonSerializer.Deserialize>(ref reader, options)?.Select(GetIdWithoutMultiPageIndex).ToList();
break;
case "rows":
children = GridConfig.ReadGridChildren(ref reader, options);
@@ -306,18 +302,18 @@ private BaseComponent ReadComponent(ref Utf8JsonReader reader, Dictionary(), maxCount, hidden, required, readOnly, additionalProperties);
+ var repComponent = new RepeatingGroupComponent(id, type, dataModelBindings, new List(), children, maxCount, hidden, required, readOnly, additionalProperties);
AddChildrenToMapping(repComponent, children, childToGroupMapping);
return repComponent;
}
else
{
- var groupComponent = new GroupComponent(id, type, dataModelBindings, new List(), hidden, required, readOnly, additionalProperties);
+ var groupComponent = new GroupComponent(id, type, dataModelBindings, new List(), children, hidden, required, readOnly, additionalProperties);
AddChildrenToMapping(groupComponent, children, childToGroupMapping);
return groupComponent;
}
case "grid":
- var gridComponent = new GridComponent(id, type, dataModelBindings, new List(), hidden, required, readOnly, additionalProperties);
+ var gridComponent = new GridComponent(id, type, dataModelBindings, new List(), children, hidden, required, readOnly, additionalProperties);
AddChildrenToMapping(gridComponent, children!, childToGroupMapping);
return gridComponent;
case "summary":
@@ -370,22 +366,6 @@ private static void ValidateSummary([NotNull] string? componentRef, [NotNull] st
}
}
- private static void ValidateGroupChildren(GroupComponent groupComponent)
- {
- var children = (List)groupComponent.Children;
- for (var i = 0; i < children.Count; i++)
- {
- if (children[i] is null)
- {
- throw new JsonException($"Group \"{groupComponent.Id}\" has a null child at index {i}");
- }
- if (children[i] is GroupComponent childGroup)
- {
- ValidateGroupChildren(childGroup);
- }
- }
- }
-
///
/// Utility method to recduce so called Coginitve Complexity by writing if in the meth
///