Creating component modifiers

Component modifiers are actions that alter the look and/or behaviour of components. They can be applied to any component on a page in Veva. You can even specify multiple modifiers and you can change the order in which they are applied, because depending on what the modifers do, the order of execution might be important.

Component modifiers are actions that alter the look and/or behaviour of components. They can be applied to any component on a page in Veva. You can even specify multiple modifiers and you can change the order in which they are applied, because depending on what the modifers do, the order of execution might be important.

Veva includes a host of standard component modifiers by default, such as the Padding and Margin modifiers which allow the user to specify a Padding and Margin (respectively) around the component they are applied to. There's the "CSS Class" modifier which allows the user to apply a custom CSS class to a component, the "Authorized users" modifier which allows the user to control whether the component is shown or not, depending on if a user is currently logged on and in which roles he/she is. Then there's the "Date/time activation" modifier which allows the user to control when the component is shown, based on a date/time range or which day of the week is (or both!). Just to name a few ...

As you can see, component modifiers can really give the user more control over the look, feel and behaviour of the content and of course you can easily write your own modifiers!

Let's create a custom modifier

A component modifier is just a class which implements the IContentRenderHook interface in Veva. Let's create a new class, let's call it UppercaseTextModifier.cs. Yes, we are going to create a modifier which manipulates the text content of components and makes it uppercase. This could of course be easily done with pure CSS, but for the sake of this example we are going to implement this in C# code as a modifier.

Add a using statement at the top:

using Lisa.Core.Domain.Components;

Then add the inheritance and then right click the IContentRenderHook interface name and choose "Implement interface ..." and now you should have something like this:

using Lisa.Core.Domain.Components;
using Microsoft.AspNetCore.Components.Rendering;
using System;

namespace Lisa7Website.SimpleTheme.ComponentModifiers
{
public class UppercaseTextModifier : IContentRenderHook
{
public void Invoke(RenderContext context, RenderTreeBuilder builder, Action next)
{
throw new NotImplementedException();
}
}
}

As you can see, the IContentRenderHook only defines one method we need to implement, which is the Invoke() method. Inside this method we have access to the current RenderContext object and that's were we have access to the content which the modifier has been applied to.

Now lets change the implementation of the Invoke() method. We are going to access the PropertyValues object of the context class, this gives us access to all the property values of the component in question. We are going to check if it contains a property whose name is "Text", and if so, we are going to grab it's value, uppercase it and then set the value to the uppercased value. Finally, we need to call the next() method and pass the builder in, to make sure that the render pipeline continues to run.

Your Invoke() method should now look like this:

public void Invoke(RenderContext context, RenderTreeBuilder builder, Action next)
{
if (context.PropertyValues.ContainsKey("Text"))
{
var upperCasedValue = context.PropertyValues["Text"].ToString().ToUpper();
context.PropertyValues["Text"] = upperCasedValue;
}

        next(builder);
    }

As you can see, we are checking if the component has a property called "Text". All the standard content components in Veva (Heading, Paragraph, HTML Content) have this property, so this modifier will work for those components.

Registering the modifier

As with most all custom extensions, themes, components and such in the system, you need to register it to the dependency injection system before you can actually use them.

Open the module configuration class (module.cs) and locate the ConfigureServices() method. Call the AddNamed() method on the services class to register the modifier:

services.AddNamed<IContentRenderHook, UppercaseTextModifier>(ServiceLifetime.Transient);

You might have to add a using statement at the top of the module.cs file, depending on where you placed your modifier

Using the modifier

Now build the solution and run it. Open the Veva backend and go to any page and click an existing paragraph component (or add a new one). Click the "Component modifiers" tab on the right and search for your component.

Click the modifier and it will be applied to the component. Note that the modifier will not be applied until the component is no longer selected (looses focus). As long as the component is still selected in the editor, no modifiers will be applied to it. Click somewhere on the page so the component is no longer selected, and you should see the text get uppercased.

Add custom properties and a friendly name/description to the modifier

As you saw, the modifier name is just "UppercaseTextModifier" which does not look so good. There should also be a description that tells the user what this modifier does. What about lowercasing, maybe it should also be able to do that? Lets make some improvements now.

First we are going to add a [DisplayName] and [Description] attributes to the modifier.

You'll need to add a using statement first:

using System.ComponentModel;

Then add these two attributes just above the class declaration

[DisplayName("Change casing of text")]
[Description("Uppcase/lowercase text of supported components, like the paragraph component")]

Next we are going to add a configurable property to the modifier so the user can specify if he wants the text to be uppercased or lowercased.

Let's start by defining an enum, just above the modifier class:

public enum CasingEnum
{
Uppercase,
Lowercase,
}

Then define a public property which is of this enum type and is uppercase by default:

[EditorBrowsable]
[DisplayName("Case")]
[Description("Upper or lowercase?")]
[DefaultValue(0)]
public CasingEnum Casing { get; set; } = CasingEnum.Uppercase;

Finally we need to modify the implementation so the case conversion is according to the value of the Casing property. Here's the updated Invoke() method:

public void Invoke(RenderContext context, RenderTreeBuilder builder, Action next)
{
if (context.PropertyValues.ContainsKey("Text"))
{
var upperCasedValue = Casing == CasingEnum.Uppercase ? context.PropertyValues["Text"].ToString().ToUpper() : context.PropertyValues["Text"].ToString().ToLower();
context.PropertyValues["Text"] = upperCasedValue;
}

        next(builder);
    }

Now run the solution and when you click the paragraph component which has this modifier, you should see the new name and description. You can also click the down-arrow next to the modifier to see that it now has a Case setting where the user can select the enum values from a dropdown to control the casing performed by the modifier:

Creating a CSS modifier

If you create a modifier which adds CSS properties to a component, you can inherit from the BaseCssHook class, and then your modifier automatically gets a media-query breakpoint selector so the user can specify for which CSS breakpoints (provided by the current theme) the CSS rules are active.

In this example we are going to create a new modifier to add borders around components. Since this is a CSS modifier, we are going to inherit from the BaseCssHook so the user can control for which device sizes this border will be applied.

Create a new class, call it CssBorderModifier.cs. Instead of implementing the IContentRenderHook interface directly, we are going to inherit the BaseCssHook class which is defined in the Lisa.Modules.StandardViewComponents module. We need to add that nuget package to our project.

Right-click the project where your CssBorderModifier.cs file is and select "Manage Nuget packages ...". Make sure that the Veva package source is selected and search for "StandardViewComponents". Click the "Lisa.Modules.StandardViewComponents" package and choose install.

Add a using statement at the top:

using Lisa.Modules.StandardViewComponents.RenderHooks;

Then hover over the CssBorderModifier class name (which should have a red underline) and select "Implement abstract class ..." from the dropdown.

You should now have something like this:

using Lisa.Modules.StandardViewComponents.RenderHooks;
using System;

namespace Lisa7Website.SimpleTheme.ComponentModifiers
{
public class CssBorderModifier : BaseCssHook
{
public override void Invoke(CssHookRule rule)
{
throw new NotImplementedException();
}
}
}

Let's give the modifier a friendly name and description and a property to select the border thickness and now we should have this:

using Lisa.Modules.StandardViewComponents.RenderHooks;
using System;
using System.ComponentModel;

namespace Lisa7Website.SimpleTheme.ComponentModifiers
{
[DisplayName("Apply borders")]
[Description("Apply borders around the component. You can choose the line thickness.")]
public class CssBorderModifier : BaseCssHook
{
[EditorBrowsable]
[DisplayName("Border thickness")]
[Description("Specify the thickness of the border, in pixels")]
[DefaultValue(1)]
public int BorderWidth { get; set; } = 1;

	public override void Invoke(CssHookRule rule)
	{
		throw new NotImplementedException();
	}
}

}

The only thing missing now is to implement the code which actually applies the border CSS rules to the component. We just have to check if the BorderWidth property is more than 0 and then we can leverage the rule collection of the BaseCssHook to add the styles. The media-query breakpoint implemention is taken care of by the BaseCSSHook so we don't have to think about that.

if (BorderWidth > 0)
{
rule.Selector = "> *";
rule.AddDeclaration("border", String.Format("{0}px solid black !important", BorderWidth));
}

So here is our finalized modifier:

using Lisa.Modules.StandardViewComponents.RenderHooks;
using System;
using System.ComponentModel;

namespace Lisa7Website.SimpleTheme.ComponentModifiers
{
[DisplayName("Apply borders")]
[Description("Apply borders around the component. You can choose the line thickness.")]
public class CssBorderModifier : BaseCssHook
{
[EditorBrowsable]
[DisplayName("Border thickness")]
[Description("Specify the thickness of the border, in pixels")]
[DefaultValue(1)]
public int BorderWidth { get; set; } = 1;

	public override void Invoke(CssHookRule rule)
	{
		if (BorderWidth > 0)
		{				
			rule.Selector = "> *";
			rule.AddDeclaration("border", String.Format("{0}px solid black !important", BorderWidth));
		}
	}
}

}

Now you only need to register the modifier into the dependency injection system like we did before.

Open the module configuration class (module.cs) and locate the ConfigureServices() method. Call the AddNamed() method on the services class to register the modifier:

services.AddNamed<IContentRenderHook, CssBorderModifier>(ServiceLifetime.Transient);

Run the solution and try adding this new modifier to a component.

You should see the Border-width setting you specified and also the media-query breakpoint selector which allows the user to specify for which screen sizes the modifier is active. This media-query breakpoint selector is part of the BaseCssHook we inherited.