public static void FreezeAll(this FormDefinition definition) { definition.Freeze(); foreach (var element in definition.FormRows.SelectMany(r => r.Elements).SelectMany(c => c.Elements)) { element.Freeze(); } }
private FormDefinition BuildDefinition(Type type) { // Only classes are allowed. // Primitives should be retrieved from prebuilt definitions. if (!type.IsClass || typeof(MulticastDelegate).IsAssignableFrom(type.BaseType)) { return(null); } var formDefinition = new FormDefinition(type); var mode = DefaultFields.AllExcludingReadonly; var grid = new[] { 1d }; var beforeFormContent = new List <AttrElementTuple>(); var afterFormContent = new List <AttrElementTuple>(); foreach (var attribute in type.GetCustomAttributes()) { switch (attribute) { case ResourceAttribute resource: formDefinition.Resources.Add(resource.Name, resource.Value is string expr ? (IValueProvider)BoundExpression.Parse(expr) : new LiteralValue(resource.Value)); break; case FormAttribute form: mode = form.Mode; grid = form.Grid; if (grid == null || grid.Length < 1) { grid = new[] { 1d }; } break; case FormContentAttribute contentAttribute: if (contentAttribute.Placement == Placement.After) { afterFormContent.Add(new AttrElementTuple(contentAttribute, contentAttribute.GetElement())); } else if (contentAttribute.Placement == Placement.Before) { beforeFormContent.Add(new AttrElementTuple(contentAttribute, contentAttribute.GetElement())); } break; case MetaAttribute meta: if (!string.IsNullOrEmpty(meta.Name)) { formDefinition.Metadata[meta.Name] = meta.Value; } break; } } beforeFormContent.Sort((a, b) => a.Attr.Position.CompareTo(b.Attr.Position)); afterFormContent.Sort((a, b) => a.Attr.Position.CompareTo(b.Attr.Position)); var gridLength = grid.Length; // Pass one - get list of valid properties. var properties = Utilities .GetProperties(type, mode) .Select(p => new PropertyInfoWrapper(p)) .ToArray(); // Pass two - build form elements. var elements = new List <ElementWrapper>(); foreach (var property in properties) { var deserializer = TryGetDeserializer(property.PropertyType); // Query property builders. var element = Build(property, deserializer); if (element == null) { // Unhandled properties are ignored. continue; } // Pass three - initialize elements. foreach (var initializer in FieldInitializers) { initializer.Initialize(element, property, deserializer); } var wrapper = new ElementWrapper(element, property); // Set layout. var attr = property.GetCustomAttribute <FieldAttribute>(); if (attr != null) { wrapper.Position = attr.Position; wrapper.Row = attr.Row; wrapper.Column = attr.Column; wrapper.ColumnSpan = attr.ColumnSpan; } elements.Add(wrapper); } // Pass four - order elements. elements = elements.OrderBy(element => element.Position).ToList(); // Pass five - group rows and calculate layout. var layout = PerformLayout(grid, elements); // Pass six - add attached elements. var rows = new List <FormRow>(); // Before form. rows.AddRange(CreateRows(beforeFormContent, gridLength)); foreach (var row in layout) { var before = new List <AttrElementTuple>(); var after = new List <AttrElementTuple>(); foreach (var element in row.Elements) { var property = element.Property; foreach (var attr in property.GetCustomAttributes <FormContentAttribute>()) { if (attr.Placement == Placement.Before) { before.Add(new AttrElementTuple(attr, attr.GetElement())); } else if (attr.Placement == Placement.After) { after.Add(new AttrElementTuple(attr, attr.GetElement())); } } } before.Sort((a, b) => a.Attr.Position.CompareTo(b.Attr.Position)); after.Sort((a, b) => a.Attr.Position.CompareTo(b.Attr.Position)); // Before element. rows.AddRange(CreateRows(before, gridLength)); // Field row. var formRow = new FormRow(); formRow.Elements.AddRange( row.Elements.Select(w => { var inlineElements = w.Property .GetCustomAttributes <FormContentAttribute>() .Where(attr => attr.Placement == Placement.Inline) .Select(attr => new AttrElementTuple(attr, attr.GetElement())) .OrderBy(tuple => tuple.Attr.Position) .ToList(); w.Element.LinePosition = (Position)(-1); if (inlineElements.Count != 0) { return(new FormElementContainer(w.Column, w.ColumnSpan, inlineElements .Select(t => t.Element) .Concat(new[] { w.Element }) .ToList())); } return(new FormElementContainer(w.Column, w.ColumnSpan, w.Element)); })); rows.Add(formRow); // After element. rows.AddRange(CreateRows(after, gridLength)); } // After form. rows.AddRange(CreateRows(afterFormContent, gridLength)); // Wrap up everything. formDefinition.Grid = grid; formDefinition.FormRows = rows; formDefinition.Freeze(); foreach (var element in formDefinition.FormRows.SelectMany(r => r.Elements).SelectMany(c => c.Elements)) { element.Freeze(); } return(formDefinition); }
public FormDefinition GetDefinition(string xml, bool freeze) { var document = XDocument.Parse(xml); if (document.Root == null) { throw new InvalidOperationException("Invalid XML document."); } void AddMetadata(IDictionary <string, string> dict, XElement xelement) { const int stroffset = 5; // "meta-".Length foreach (var attr in xelement.Attributes()) { if (attr.Name.LocalName.StartsWith("meta-", StringComparison.OrdinalIgnoreCase)) { dict[attr.Name.LocalName.Substring(stroffset)] = attr.Value; } } } FormElement WithMetadata(FormElement element, XElement xelement) { AddMetadata(element.Metadata, xelement); return(element); } ILayout Terminal(XElement element) { var elementName = element.Name.LocalName.ToLower(); FormElement formElement; switch (elementName) { case "layout": return(Layout(element)); case "input": case "textarea": case "toggle": case "password": { var typeName = element.TryGetAttribute("type") ?? "string"; var attributes = new List <Attribute>(); Type propertyType; if (elementName == "input" && TypeConstructors.TryGetValue(typeName, out var constructor)) { var data = constructor(new XmlConstructionContext(element)); propertyType = data.PropertyType; attributes.AddRange(data.CustomAttributes); } else if (!TypeNames.TryGetValue(typeName, out propertyType)) { throw new InvalidOperationException($"Type '{typeName}' not found."); } var fieldName = element.TryGetAttribute("name"); attributes.Add(Utilities.GetFieldAttributeFromElement(element)); attributes.Add(Utilities.GetBindingAttributeFromElement(element)); switch (elementName) { case "textarea": attributes.Add(new MultiLineAttribute()); propertyType = typeof(string); break; case "toggle": attributes.Add(new ToggleAttribute()); propertyType = typeof(bool); break; case "password": attributes.Add(new PasswordAttribute()); propertyType = typeof(string); break; } attributes.AddRange(Utilities.GetValidatorsFromElement(element)); var property = new DynamicProperty(fieldName, propertyType, attributes.ToArray()); var deserializer = TryGetDeserializer(propertyType); formElement = Build(property, deserializer); if (formElement != null) { foreach (var initializer in FieldInitializers) { initializer.Initialize(formElement, property, deserializer); } formElement.LinePosition = (Position)(-1); } return(new FormElementLayout(WithMetadata(formElement, element))); } case "select": { var from = element.TryGetAttribute("from"); object itemsSource; string typeName; string displayPath; string valuePath; Type propertyType = null; if (!string.IsNullOrEmpty(from)) { if (from.StartsWith("type:")) { var qualifiedType = from.Substring("type:".Length); var nullable = false; if (qualifiedType.EndsWith("?")) { qualifiedType = qualifiedType.Substring(0, qualifiedType.Length - 1); nullable = true; } propertyType = Utilities.FindTypes(t => t.FullName == qualifiedType).FirstOrDefault(); itemsSource = propertyType ?? throw new InvalidOperationException($"Could not find type '{qualifiedType}'."); if (propertyType.IsValueType && nullable) { propertyType = typeof(Nullable <>).MakeGenericType(propertyType); itemsSource = propertyType; } typeName = element.TryGetAttribute("type"); } else { itemsSource = from; typeName = element.TryGetAttribute("type") ?? "string"; } displayPath = element.TryGetAttribute("displayPath"); valuePath = element.TryGetAttribute("valuePath"); } else { typeName = "string"; displayPath = "Name"; valuePath = "Value"; itemsSource = Utilities.GetSelectOptionsFromElement(element); } if (typeName != null && !TypeNames.TryGetValue(typeName, out propertyType)) { throw new InvalidOperationException($"Type '{typeName}' not found."); } if (propertyType.IsValueType && element.TryGetAttribute("nullable") != null && (!propertyType.IsGenericType || propertyType.GetGenericTypeDefinition() != typeof(Nullable <>))) { propertyType = typeof(Nullable <>).MakeGenericType(propertyType); } var fieldName = element.TryGetAttribute("name"); var attributes = new List <Attribute> { new SelectFromAttribute(itemsSource) { SelectionType = Utilities.TryParse(element.TryGetAttribute("as"), SelectionType.ComboBox), DisplayPath = displayPath, ValuePath = valuePath, ItemStringFormat = element.TryGetAttribute("itemStringFormat") }, Utilities.GetFieldAttributeFromElement(element), Utilities.GetBindingAttributeFromElement(element) }; attributes.AddRange(Utilities.GetValidatorsFromElement(element)); var property = new DynamicProperty(fieldName, propertyType, attributes.ToArray()); var deserializer = TryGetDeserializer(propertyType); formElement = Build(property, deserializer); if (formElement != null) { foreach (var initializer in FieldInitializers) { initializer.Initialize(formElement, property, deserializer); } formElement.LinePosition = (Position)(-1); } return(new FormElementLayout(WithMetadata(formElement, element))); } case "title": formElement = new TitleAttribute(element.GetAttributeOrValue("content")) { Icon = element.TryGetAttribute("icon") } .WithBaseProperties(element) .WithTextProperties(element) .GetElement(); return(new FormElementLayout(WithMetadata(formElement, element))); case "heading": formElement = new HeadingAttribute(element.GetAttributeOrValue("content")) { Icon = element.TryGetAttribute("icon") } .WithBaseProperties(element) .WithTextProperties(element) .GetElement(); return(new FormElementLayout(WithMetadata(formElement, element))); case "text": formElement = new TextAttribute(element.GetAttributeOrValue("content")) .WithBaseProperties(element) .WithTextProperties(element) .GetElement(); return(new FormElementLayout(WithMetadata(formElement, element))); case "error": formElement = new ErrorTextAttribute(element.GetAttributeOrValue("content")) .WithBaseProperties(element) .WithTextProperties(element) .GetElement(); return(new FormElementLayout(WithMetadata(formElement, element))); case "img": formElement = new ImageAttribute(element.TryGetAttribute("src")) { Width = element.TryGetAttribute("width"), Height = element.TryGetAttribute("height"), HorizontalAlignment = element.TryGetAttribute("align"), VerticalAlignment = element.TryGetAttribute("valign"), Stretch = element.TryGetAttribute("stretch"), StretchDirection = element.TryGetAttribute("direction") } .WithBaseProperties(element) .GetElement(); return(new FormElementLayout(WithMetadata(formElement, element))); case "br": formElement = new BreakAttribute { Height = element.TryGetAttribute("height") } .WithBaseProperties(element) .GetElement(); return(new FormElementLayout(WithMetadata(formElement, element))); case "hr": var hasMargin = element.TryGetAttribute("hasMargin"); formElement = (hasMargin != null ? new DividerAttribute(bool.Parse(hasMargin)) : new DividerAttribute()) .WithBaseProperties(element) .GetElement(); return(new FormElementLayout(WithMetadata(formElement, element))); case "action": formElement = Utilities.GetAction(element) .WithBaseProperties(element) .GetElement(); return(new FormElementLayout(WithMetadata(formElement, element))); default: throw new InvalidOperationException($"Unknown element '{element.Name.LocalName}'."); } } GridColumnLayout Column(XElement element) { var elements = element.Elements().ToList(); var child = elements.Count == 1 ? Row(elements[0]) : new Layout(elements.Select(Row)); return(new GridColumnLayout( child, Utilities.ParseDouble(element.TryGetAttribute("width"), 1d), Utilities.ParseDouble(element.TryGetAttribute("left"), 0d), Utilities.ParseDouble(element.TryGetAttribute("right"), 0d))); } GridLayout Grid(XElement element) { return(new GridLayout( element.Elements().Select(Column), Utilities.ParseDouble(element.TryGetAttribute("top"), 0d), Utilities.ParseDouble(element.TryGetAttribute("bottom"), 0d))); } InlineLayout Inline(XElement element) { return(new InlineLayout( element.Elements().Select(Terminal), Utilities.ParseDouble(element.TryGetAttribute("top"), 0d), Utilities.ParseDouble(element.TryGetAttribute("bottom"), 0d))); } ILayout Row(XElement element) { if (!string.Equals(element.Name.LocalName, "row", StringComparison.OrdinalIgnoreCase)) { return(Terminal(element)); } if (element .Elements() .All(e => string.Equals(e.Name.LocalName, "col", StringComparison.OrdinalIgnoreCase))) { return(Grid(element)); } return(Inline(element)); } Layout Layout(XElement element) { return(new Layout( element.Elements().Select(Row), Utilities.ParseThickness(element.TryGetAttribute("margin")), Utilities.TryParse(element.TryGetAttribute("valign"), VerticalAlignment.Stretch), Utilities.TryParse(element.TryGetAttribute("align"), HorizontalAlignment.Stretch), Utilities.ParseNullableDouble(element.TryGetAttribute("minHeight")), Utilities.ParseNullableDouble(element.TryGetAttribute("maxHeight")))); } var form = new FormDefinition(null); // null indicates dynamic type AddMetadata(form.Metadata, document.Root); form.FormRows.Add(new FormRow(true, 1) { Elements = { new FormElementContainer(0, 1, Layout(document.Root)) } }); if (freeze) { form.FreezeAll(); } return(form); }