Upgrades
Upgrades
Brightspot Enterprise 4.5 Developer Upgrade Guide

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.

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 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


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
Previous Topic
Brightspot Gradle Guide
Next Topic
Post-Upgrade Verification Guide
Was this topic helpful?
Thanks for your feedback.

Browse All Docs

Everything you need to know when creating, managing, and administering content within Brightspot CMS.

Dashboards
Publishing
Workflows
Admin configurations
A guide for installing, supporting, extending, modifying and administering code on the Brightspot platform.

Field types
Content modeling
Rich-text elements
Images
A guide to configuring Brightspot's library of integrations, including pre-built options and developer-configured extensions.

Google Analytics
Shopify
Apple News
Brightspot is packaged with content types that get you up and running in a matter of days, including assets, modules and landing pages.

Assets
Modules
Landing pages
Our robust, flexible Design System provides hundreds of pre-built components you can use to build the presentation layer of your dreams.

Asset types
Module types
Page types