Skip to main content

Adding 4.5 Features Guide

Brightspot version 4.5 introduces multiple new features. For a complete list, see the Brightspot 4.5 Product Guide. While many features are automatically included with the new version, others require development implementation. This guide outlines steps for adding boilerplate implementations of those features, which apply to data models and code patterns common to many applications built on Brightspot. Specific applications built on Brightspot may have differences from the examples contained in this document, but the approaches outlined below are intended to be easily extrapolated to include other data models and situations. For information on 4.5 features requiring development that are not covered in this guide, please contact your account representative.

Pre-Publish Actions

To implement this feature, implement the interface Suggestible on noted types.

Caution

Your project may already have versions of these classes implementing Suggestable. This is NOT a typo and serves different functionality than Suggestible.

Implement on below classes if applicable to project:

  • Article
  • Listicle
  • Gallery
  • Story

Example implementations of getSuggestibleFields()

Caution

Ensure that the field names listed in your implementation of getSuggestibleFields correctly match the field names including internal prefixes as specified on your data model. Verify that the specified fields are presented in the pre-publish action diaglog as described in the user documentation.

// --- Suggestible support ---

@Override
public List<String> getSuggestibleFields() {
return Stream.of(
"cms.seo.title",
"cms.seo.description",
"promotable.promoTitle",
"promotable.promoDescription",
"promotable.promoImage",
"shareable.shareTitle",
"shareable.shareDescription",
"shareable.shareImage"
).collect(Collectors.toList());
}
// --- Suggestible support ---

@Override
public List<String> getSuggestibleFields() {
return Stream.of(
"seo.title",
"seo.description",
"pagePromotable.promoTitle",
"pagePromotable.promoDescription",
"pagePromotable.promoImage",
"shareable.shareTitle",
"shareable.shareDescription",
"shareable.shareImage"
).collect(Collectors.toList());
}

Pre-Publish Actions is disabled by default. To enable it, go into Sites & Settings > CMS (tab) > UI (cluster) > Enable Pre-Publish Actions (toggle on). See Enabling pre-publish actions

Convert & Copy

Convert & Copy is enabled by default.

The standard implementation of Convert & Copy relies on concepts of "shared" and "inline" object placements. The implementation consists of implementing Interchangeable on both the "shared" and "inline" placement classes in the data model. If your project contains multiple pairs of shared / inline placement objects, Interchangeable will need to be implemented on each of those pairings.

The methods required to be implemented from Interchangeable for Convert & Copy functionality are loadTo() and loadableTypes which will vary greatly among multiple classes as applicable to your codebase. Recommended implementations can be found below.

If your project contains Module.java, Shared.java , or ModuleType.java

If your project contains either of these classes, implement Interchangeable on the classes exactly as detailed below.

Shared.java

// --- Interchangeable Support ---
@Override
public boolean loadTo(Object target) {
if (target instanceof ModuleType && module != null) {
State.getInstance(target).setValues(module.getType().getState().getSimpleValues());

return true;
}
return false;
}

@Override
public List<UUID> loadableTypes(Recordable recordable) {
Module module = getModule();
if (module != null) {
return new ImmutableList.Builder<UUID>()
.add(ObjectType.getInstance(module.getType().getClass()).getId())
.build();
} else {
return Collections.emptyList();
}
}

ModuleType.java

// --- Interchangeable Support ---
@Override
public boolean loadTo(Object target) {
WebRequest current = WebRequest.getCurrent();
if (target instanceof Shared && Boolean.TRUE.equals(current.getParameter(boolean.class, "isConvert"))) {
UUID itemTypeId = this.getState().getTypeId();
Object currentModuleType = ObjectType.getInstance(itemTypeId).createObject(null);

if (currentModuleType instanceof ModuleType) {
State moduleTypeState = this.getState();
String moduleLabel = moduleTypeState.getLabel();
String convertedModuleLabel = ToolLocalization.text(new LocalizationContext(
Module.class,
ImmutableMap.of(
"label",
ObjectUtils.to(UUID.class, moduleLabel) != null
? ToolLocalization.text(moduleTypeState.getType(), "label.untitled")
: moduleLabel,
"date",
ToolLocalization.dateTime(new Date().getTime()))), "convertLabel");

Shared sharedModule = (Shared) target;
Module newModule = new Module();
newModule.setName(convertedModuleLabel);
newModule.setType(this);
sharedModule.setModule(newModule);
State newModuleState = newModule.getState();

if (newModuleState.validate()) {
// Publish converted module
Content.Static.publish(
newModuleState,
current.as(ToolRequest.class).getCurrentSite(),
current.as(ToolRequest.class).getCurrentUser());

return true;
}
}

}
return false;
}

@Override
public List<UUID> loadableTypes(Recordable recordable) {
if (Module.hasPermission()) {
return new ImmutableList.Builder<UUID>()
.add(ObjectType.getInstance(Shared.class).getId())
.build();
} else {
return ImmutableList.of();
}
}

Module.java

You need to implement hasPermission on this class to ensure that users have the correct permission to convert between the different module types.

static boolean hasPermission() {
return WebRequest.getCurrent()
.as(ToolRequest.class)
.hasPermission("type/" + ObjectType.getInstance(Module.class).getId() + "/publish");
}

If your project has inline/shared list implementations that implement ModulePlacement

Note

These classes commonly occur in pairs. Some common examples of classes that will implement ModulePlacement are PageListModulePlacementInline and PageListModulePlacementShared

If your project has classes like this, you will need to implement Interchangeable on these list types, and Copyable on the inline type. This will drive the conversion between a shared module and an inline module.

@Recordable.DisplayName("List Module")
@Recordable.Embedded
public class PageListModulePlacementInline extends AbstractAuthorListModule implements ModulePlacement,
Copyable,
Interchangeable {

// --- Copyable support ---

@Override
public void onCopy(Object source) {
if (source instanceof PageListModulePlacementShared) {
getState().remove("internalName");
}
}

// --- Interchangeable support ---

@Override
public boolean loadTo(Object target) {

if (target instanceof PageListModulePlacementShared) {

PageListModule sharedModule = Copyable.copy(ObjectType.getInstance(PageListModule.class), this);
String inlineLabel = getLabel();
String sharedLabel = ToolLocalization.text(new LocalizationContext(
ModulePlacement.class,
ImmutableMap.of(
"label",
ObjectUtils.to(UUID.class, inlineLabel) != null
? ToolLocalization.text(this.getClass(), "label.untitled")
: inlineLabel,
"date",
ToolLocalization.dateTime(new Date().getTime()))), "convertLabel");
// Internal Name field of the shared module will be set to this inline module's label with a converted copy text suffix
sharedModule.setInternalName(sharedLabel);

// Publish converted module
Content.Static.publish(
sharedModule,
WebRequest.getCurrent().as(ToolRequest.class).getCurrentSite(),
WebRequest.getCurrent().as(ToolRequest.class).getCurrentUser());

// Update the shared placement to reference the newly-published shared module
PageListModulePlacementShared sharedPlacement = ((PageListModulePlacementShared) target);
sharedPlacement.setShared(sharedModule);

return true;
}
return false;
}

@Override
public List<UUID> loadableTypes(Recordable recordable) {

return ImmutableList.of(
ObjectType.getInstance(PageListModulePlacementShared.class).getId()
);
}
}
@Recordable.DisplayName("Shared List Module")
@Recordable.Embedded
public class PageListModulePlacementShared extends Record implements
ModelWrapper,
ModulePlacement,
Interchangeable {

// --- Interchangeable support ---

@Override
public boolean loadTo(Object target) {

if (getShared() == null) {
return false;
}

if (target instanceof PageListModulePlacementInline) {

PageListModule sharedModule = getShared();
State targetState = State.getInstance(target);
targetState.putAll(State.getInstance(Copyable.copy(targetState.getType(), sharedModule)).getSimpleValues());

return true;
}
return false;
}

@Override
public List<UUID> loadableTypes(Recordable recordable) {

return ImmutableList.of(
ObjectType.getInstance(PageListModulePlacementInline.class).getId()
);
}
}

Some other types that you might want to implement this on (if present on your project):

  • AuthorListModulePlacementInline.java
  • AuthorListModulePlacementShared.java
  • LogoListModulePlacementInline.java
  • LogoListModulePlacementShared.java
  • PageListModulePlacementInline.java
  • PageListModulePlacementShared.java
  • PersonListModulePlacementInline.java
  • PersonListModulePlacementShared.java
  • PagePromoModulePlacementInline.java
  • PagePromoModulePlacementShared.java

Shelf

Uses the ContentEditDrawerItem interface to mark which classes will load a shelf within their interface.

Add ContentEditDrawerItem to the following classes:

  1. Article.java

  2. Image.java

  3. Video.java

  4. Gallery.java

  5. Page.java (or AbstractPage)

    1. Adding ContentEditDrawerItem here enables pages and all children of page to access the Shelf and the content within it. If your project has Page.java as an interface, you will extend ContentEditDrawerItem rather than implement.

The Shelf is enabled by default and can be disabled per site, see Setting access to The Shelf.

Enabling drag and drop from the shelf

After the shelf has been added to a specific piece of content, you need to implement Interchangeable on types that can be drag and dropped from the shelf into the content. Types that do not implement this cannot be dragged out of the shelf into a drop zone.

Article example

If you would like to be able to drag and drop an article from the shelf into another piece of content (for example, adding an article to a list on a page), then you need to implement Interchangeable on the Article type.

Note

Important notes about the code below:

  • We are explicitly supporting the ability to drag and drop an Article from the shelf into two types in the loadTo() method: Promo and LinkRichTextElement
  • In loadableTypes(), we add the current item's type ID to the list. This is REQUIRED.
// --- Interchangeable Support ---

@Override
public boolean loadTo(Object target) {

if (target instanceof Promo) {
// Convert the current Article type to a Promo

Promo pagePromo = (Promo) target;
InternalPromoItem internalPromoItem = new InternalPromoItem();
internalPromoItem.setItem(this);
pagePromo.setItem(internalPromoItem);

return true;

} else if (target instanceof LinkRichTextElement) {
// Convert the current Article type to a LinkRichTextElement

LinkRichTextElement linkRte = (LinkRichTextElement) target;
InternalLink internalLink = new InternalLink();
internalLink.setItem(this);
linkRte.setLink(internalLink);

return true;
}
return false;
}

@Override
public List<UUID> loadableTypes(Recordable recordable) {
return ImmutableList.of(
getState().getTypeId(), // IMPORTANT! enables dragging and dropping as itself from the shelf
ObjectType.getInstance(Promo.class).getId(),
ObjectType.getInstance(LinkRichTextElement.class).getId()
);
}

PageListModule example

Your project may also have an implementation of PageListModule, which will need a similar treatment above. This will drive the drag and drop of shared lists.

public class PageListModule extends AbstractPageListModule implements
NoUrlsWidget,
Interchangeable,
SharedModule {

// --- Interchangeable support ---

@Override
public boolean loadTo(Object target) {

// Support the following types for Shelf drag and drop:
// - PageListModulePlacementShared
if (target instanceof PageListModulePlacementShared) {
PageListModulePlacementShared pageListModulePlacementShared = (PageListModulePlacementShared) target;
pageListModulePlacementShared.setShared(this);
return true;
} else if (target instanceof PageListRichTextElement) {
PageListRichTextElement pageListRichTextElement = (PageListRichTextElement) target;
pageListRichTextElement.setList(this);
return true;
}
return false;
}

@Override
public List<UUID> loadableTypes(Recordable recordable) {

// Support the following types for Shelf drag and drop:
// - PageListModulePlacementShared
return ImmutableList.of(
getState().getTypeId(), // enable dragging and dropping as itself from the shelf
ObjectType.getInstance(PageListModulePlacementShared.class).getId(),
ObjectType.getInstance(PageListRichTextElement.class).getId()
);
}
}

Other types to consider implementation

This can be added on many different types, below are a list of types that implementation of this should be considered:

  • Article.java
  • Listicle.java
  • Image.java
  • Video.java