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.
To implement this feature, implement the interface Suggestible
on noted types.
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()
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 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
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
Uses the ContentEditDrawerItem
interface to mark which classes will load a shelf within their interface.
Add ContentEditDrawerItem
to the following classes:
Article.java
Image.java
Video.java
Gallery.java
Page.java
(orAbstractPage
)
- Adding
ContentEditDrawerItem
here enables pages and all children of page to access the Shelf and the content within it. If your project hasPage.java
as an interface, you willextend ContentEditDrawerItem
rather thanimplement
.
- Adding
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.
- We are explicitly supporting the ability to drag and drop an Article from the shelf into two types in the
loadTo()
method:Promo
andLinkRichTextElement
- 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