With UI Builder - how would I create a context app that displays on a member only?

Hello :waving_hand:
I am new to using UI Builder and I want to try and display a context app on the member to display an audit log of some content pages that have been marked read or seen that we store in a custom DB table like so.

using System.ComponentModel.DataAnnotations.Schema;

namespace Infrastructure.Database.Tables;

/// <summary>
/// We only insert DB records for members that have read or seen a page
/// So if a member has not read or seen a page then there will be no record in the DB
/// A record will always exist with a seen date/time but possibly not a read date/time
/// </summary>
[Table("MustReadPages")]
public class MustReadPages
{
    /// <summary>
    /// This is the int based PK primary key for the DB table
    /// </summary>
    public int ID { get; set; }

    /// <summary>
    /// The GUID of the member
    /// </summary>
    public Guid UmbracoMemberKey { get; set; }

    /// <summary>
    /// The GUID of the content page that the member has read or seen
    /// </summary>
    public Guid UmbracoContentPageKey { get; set; }

    /// <summary>
    /// The date and time that the member has seen the page
    /// </summary>
    public DateTime? PageSeenAt { get; set; }

    /// <summary>
    /// The date and time that the member has marked the page as read
    /// </summary>
    /// <remarks>If null/empty then the page has not been read yet</remarks>
    public DateTime? PageMarkedReadAt { get; set; }
}

Goals

  • Display a content/context app on the member to display just their entries and match the property UmbracoMemberKey
  • The list view of data needs to display the GUID/key of the content node as the friendly node name and perhaps a link back to the content node

I am trying to wrap my head around the docs and stuff I am finding to see if this can be done or not, so any pointers if this can be done or not and how I might achieve it would be fab :slight_smile:

I think you would start out with a config that’s something like this:

public void Configure(UIBuilderConfigBuilder builder)
    {
        builder.WithSection(
            "member",
            withSectionConfig =>
            {
                withSectionConfig.WithTree(
                    "members",
                    withTreeConfig =>
                    {
                        withTreeConfig.AddContextApp(
                            "Comments",
                            contextAppConfig =>
                            {
                                contextAppConfig.AddCollection<MustReadPages>(
                                    p => p.Id,
                                    p => p.UmbracoMemberKey,
                                    "Comments",
                                    "A collection of comments",
                                    "",
                                    collectionConfig =>
                                    {
                                        // Collection configuration here
                                    }
                                );
                            }
                        );
                    }
                );
            }
        );

Then within the config you can define a listview however you like.
Be aware that the section and tree alias’ I’ve written here may not match, not sure what the core uses exactly.

This should atleast let you add a context app on members though.

1 Like

Thanks Jesper,
I will give that a try and most likely try to use/lean on any constants from Umbraco for the tree alias and section alias.

OK yep that helped Jesper

builder
.WithSection(Umbraco.Cms.Core.Constants.Applications.Members) // Content section
.WithTree(Umbraco.Cms.Core.Constants.Trees.Members, withTreeConfig => withTreeConfig // Content Tree
    .AddContextAppBefore("umbInfo", "Audit", "icon-users", ctxAppConfig => ctxAppConfig
        .SetAlias("auditOfMustReadUsers")

        // Assign the DB table to the collection
        .AddCollection<MustReadPages>(x => x.ID, x => x.UmbracoContentPageKey, "Must Read Page", "Must Read Pages", "A collection of must read pages", "icon-book-alt-2", "icon-book-alt-2", collectionConfig => collectionConfig
            .SetAlias("mustReadPages") // All collection aliases must be globally unique
            .SetRepositoryType<MustReadPagesRepo>()

            .DisableCreate()
            .DisableDelete()
            .DisableUpdate()

            // List View & its fields
            .ListView(listViewConfig => listViewConfig
                .AddField(page => page.UmbracoContentPageKey).SetHeading("Page Key")
                .AddField(page => page.UmbracoMemberKey).SetHeading("Member Key")
                .AddField(page => page.PageSeenAt).SetHeading("Seen At")
                .AddField(page => page.PageMarkedReadAt).SetHeading("Read At")
            )
        )
    )

Next steps…

Any pointers or ideas on how I achieve the following would be FAB

  • This lists out all records in the table and not restricted to the current member we are viewing
  • Fix the name column
  • Change the Page Key to be the name of the node

This lists out all records in the table and not restricted to the current member we are viewing

I don’t think there is something built-in for that, but you can apply filters to the collection:
Global Filters | Umbraco UI Builder

So you could probably fetch the guid from the url via HttpContext or something and then do a filter where x.MemberKey == guidFromUrl

The name can be configured as mentioned here:
The Basics | Umbraco UI Builder

Page key to be the name of the node could maybe be done somehow by fetching the node via the content cache and then using this to set the value in the listview:
List Views | Umbraco UI Builder

The easier way would be to add properties to your model where you store the nodenames and then use those in your listview :sweat_smile:

You don’t nessecarily have to edit your db models, you can use the custom repository function in between to fetch your records, then look up names and then convert to a UI builder model you can return to the context app.

Hi Warren

The second parameter in AddCollection is supposed to be the foreign key, in your example x.UmbracoMemberKey

Yep this method is what I needed to use for the Name. As I can’t use the SetNameProperty as it expects a string, which my DB table doesn’t have.

So I needed to use the SetNameFormat

.SetNameFormat(x => $"{x.ID}")

But this method for SetNameFormat does not let me to set the header of the column, so I get a Name header for the ID column.

The second parameter in AddCollection is supposed to be the foreign key, in your example x.UmbracoMemberKey

I have done that and still no luck…

builder
.WithSection(Umbraco.Cms.Core.Constants.Applications.Members) // Content section
.WithTree(Umbraco.Cms.Core.Constants.Trees.Members, withTreeConfig => withTreeConfig // Content Tree
    .AddContextAppBefore("umbInfo", "Audit", "icon-users", ctxAppConfig => ctxAppConfig
        .SetAlias("auditOfMustReadUsers")

        // Assign the DB table to the collection
        .AddCollection<MustReadPages>(x => x.ID, x => x.UmbracoMemberKey, "Must Read Page", "Must Read Pages", "A collection of must read pages", "icon-book-alt-2", "icon-book-alt-2", collectionConfig => collectionConfig
            .SetAlias("mustReadPages") // All collection aliases must be globally unique
            .SetRepositoryType<MustReadPagesRepo>()
            .SetNameFormat(x => $"{x.ID}")

            // Disables CRUD operations
            .MakeReadOnly()

            // List View & its fields
            .ListView(listViewConfig => listViewConfig
                .AddField(page => page.UmbracoContentPageKey).SetHeading("Page Key")
                .AddField(page => page.UmbracoMemberKey).SetHeading("Member Key")
                .AddField(page => page.PageSeenAt).SetHeading("Seen At")
                .AddField(page => page.PageMarkedReadAt).SetHeading("Read At")
            )

            .Editor(editorConfig => editorConfig
                .AddTab("Entry", tabConfig => tabConfig
                    .AddFieldset("General", fieldsetConfig => fieldsetConfig
                        .AddField(c => c.ID).MakeReadOnly().SetLabel("ID").SetDescription("The unique identifier for the record")
                        .AddField(c => c.UmbracoContentPageKey).MakeReadOnly().SetLabel("Page Key").SetDescription("The umbraco Page key")
                        .AddField(c => c.UmbracoMemberKey).MakeReadOnly().SetLabel("Member Key").SetDescription("The umbraco Member key")
                        .AddField(c => c.PageSeenAt).MakeReadOnly().SetLabel("Seen At").SetDescription("When the member viewed the paged")
                        .AddField(c => c.PageMarkedReadAt).MakeReadOnly().SetLabel("Read At").SetDescription("When the member read the paged")
                    )
                )
            )
        )
    )

What is the type of UmbracoMemberKey? I suspect it might have to be a string… At least its a string in my implementation, and that one works :slight_smile:

UmbracoMemberKey is a GUID

/// <summary>
/// The GUID of the member
/// </summary>
public Guid UmbracoMemberKey { get; set; }