Cofoundry 0.3.2 is out! This release includes a number of bug fixes and a handful of interesting new features. Here's what's included:

New List Data Annotations & Admin Controls

We've added three new data annotations for list-style controls:

  • SelectList: Renders as a drop-down list of options.
  • RadioList: Renders as list of radio buttons that allows only a single option to be selected.
  • CheckboxList: Renders as list of checkbox options that allows multiple option selection.

Options can be expressed in three different ways:

  • Enum: Use an enum type to derive the options from enum values.
  • IListOptionSource: Define a class that generates option values at runtime.
  • IListOptionApiSource: Define a class that tells Cofoundry how to extract options from an API or static file.

Examples

Enum SelectList

In this simple example, we just provide the enum type to the SelectList data annotation to generate the options.

public enum ColorOption
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}

public class ExampleDataModel : ICustomEntityDataModel
{
    [SelectList(typeof(ColorOption))]
    public ColorOption FavoriteColor { get; set; }
}

Output:

Enum select list rendering in the admin UI

IListOptionSource RadioList

Using an IListOptionSource provides more control of option text and values. Note that the option value should be the same type as the property value, although nullable types are supported which makes the field optional

For optional lists, the default null item text is None but you can customize this with the DefaultItemText property:

using Cofoundry.Domain;
using System.Collections.Generic;

public class ExampleListOptionSource : IListOptionSource
{
    public ICollection<ListOption> Create()
    {
        var options = new List<ListOption>();
        options.Add(new ListOption("Negative", 1));
        options.Add(new ListOption("Neutral", 2));
        options.Add(new ListOption("Positive", 3));

        return options;
    }
}

public class ExampleDataModel : ICustomEntityDataModel
{
    [RadioList(typeof(ExampleListOptionSource), DefaultItemText = "Not Specified")]
    public int? Feedback { get; set; }
}

Output:

ListOptionSource RadioList rendering in the admin UI

IListOptionApiSource CheckboxList

Using an API source give you the flexibility of pulling data from a dynamic source such as a database. The CheckBox list allows for multiple selection and should be used with a collection property type.

public class PetsApiOptionSource : IListOptionApiSource
{
    public string Path => "/admin/api/pets";

    public string NameField => "Title";

    public string ValueField => "Id";
}


public class ExampleDataModel : ICustomEntityDataModel
{
    [CheckboxList(typeof(PetsApiOptionSource))]
    public ICollection<int> PetIds { get; set; }
}

Output:

ListOptionApiSource CheckboxList rendering in the admin UI

Html Editor Improvements

We've had some feedback about issues with how the HTML editor validates and filters HTML code. We use TinyMCE as our html editor which has many settings, but until now you've only been able to customize the toolbar configuration.

First off, we've added documentation for the [Html] data annotation demonstrating the existing functionality for customizing toolbars. For example you can already specify which toolbars to use using the default constructor:

public class ExampleDataModel : ICustomEntityDataModel
{
    [Html(HtmlToolbarPreset.BasicFormatting, HtmlToolbarPreset.Media, HtmlToolbarPreset.Source)]
    public string Content { get; set; }
}

Secondly we've added two different ways to specify custom TinyMCE configuration:

ConfigSource

Use a class to determine any additional configuration options to apply to the html editor. This should be a class that inherits from IHtmlEditorConfigSource, which provides a .NET code generated set of options.

public class ExampleHtmlEditorConfigSource : IHtmlEditorConfigSource
{
    private static readonly Dictionary<string, object> _options = new Dictionary<string, object>()
    {
        { "resize", false },
        { "browser_spellcheck", false }
    };

    public IDictionary<string, object> Create()
    {
        return _options;
    }
}

public class ExampleDataModel : ICustomEntityDataModel
{
    [Html(ConfigSource = typeof(ExampleHtmlEditorConfigSource))]
    public string Content { get; set; }
}

ConfigFilePath

You can also define a path to a json configuration file if you prefer to write your config in json.

public class ExampleDataModel : ICustomEntityDataModel
{
    [Html(ConfigFilePath = "/content/html-editor-config.json")]
    public string Content { get; set; }
}

Custom html editor attributes

If you're using a specific configuration often, you may want to consider deriving a new data annotation using HtmlAttribute as the base.

[AttributeUsage(AttributeTargets.Property)]
public class HtmlWithCustomEditorAttribute : HtmlAttribute
{
    public HtmlWithCustomEditorAttribute()
        : base(HtmlToolbarPreset.BasicFormatting, HtmlToolbarPreset.Media, HtmlToolbarPreset.Source)
    {
        ConfigFilePath = "/content/html-editor-config.json";
        Rows = 40;
    }
}

public class ExampleDataModel : ICustomEntityDataModel
{
    [HtmlWithCustomEditor]
    public string Content { get; set; }
}

DocumentCollection Data Annotation

We've added a new [DocumentCollection] data annotation to mirror similar functionality already available with the [ImageCollection] attribute.

Document file types can be filtered using the optional FileExtensions property.

public class ExampleDataModel : ICustomEntityDataModel
{
    [Display(Name="Documents")]
    [DocumentCollection(FileExtensions = new string[] { "pdf" })]
    public ICollection<int> DocumentIds { get; set; }
}

Output:

DocumentCollection attribute rendering in the admin UI

Document Asset Downloads

Following feedback we've changed the behavior of document asset downloads so that by default they use "inline" content-disposition rather than "attachment", which better mirrors default webserver/browser behavior.

To force a file to download rather than display inline you can instead generate the link using one of the new alternative download methods on IDocumentAssetRouteLibrary or IContentRouteLibrary.

Bug Fixes

  • #209 PageRegion EmptyContentMinHeight renders in non-edit mode
  • #208 Embedding a script (e.g. tweet) in a page block breaks the json rendering in edit mode
  • #213 Custom Entity Ordering: Re-ording one entity type removes ordering from another
  • #214 "Pending draft" link in page list for custom entities is not working
  • #212 Open pdf file on browser

Links