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();
     }
 }
예제 #2
0
        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);
        }
예제 #3
0
        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);
        }