Fragments extension: Fragment Entry Processors

How to create your own tags to enhance your content pages fragments

In Liferay 7.1 we presented a new vision to the page authoring process. The main idea was to empower business users to create pages and visualize contents in a very visual way, without a need to know technical stuff like Freemarker or Velocity for the Web Content templates. To make it possible we introduced the fragment concept.

In our vision fragment is a construction block which can be used to build new content pages, display pages or content page templates. Fragment consists of HTML markup, CSS stylesheet, and Javascript code.

Despite the fact that we really wanted to create a business-user-friendly application, we always remember about our strong developers community and their needs. Fragments API is extensible and allows you to create your custom markup tags to enrich your fragments code and in this article, I would like to show you how to create your own processors for the fragments markup.

As an example, we are going to create a custom tag which shows a UNIX-style fortune cookie :). Our fortune cookie module has the following structure:

We use Jsoup library to parse fragments HTML markup so we have to include it into our build file(since it doesn't come within the Portal core) among other dependencies. 

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

dependencies {
   compileInclude group: "org.jsoup", name: "jsoup", version: "1.10.2"

   compileOnly group: "com.liferay", name: "com.liferay.fragment.api", version: "1.0.0"
   compileOnly group: "com.liferay", name: "com.liferay.petra.string", version: "2.0.0"
   compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "3.0.0"
   compileOnly group: "javax.portlet", name: "portlet-api", version: "3.0.0"
   compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
   compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0"
}

OSGi bnd.bnd descriptor has nothing special because we don't export any package and don't provide any capability:

Bundle-Name: Liferay Fragment Entry Processor Fortune
Bundle-SymbolicName: com.liferay.fragment.entry.processor.fortune
Bundle-Version: 1.0.0

Every Fragment Entry Processor implementation has two main methods, first one - to process fragments HTML markup and second - to validate the markup to avoid saving fragments with invalid markup.

/**
 * @param fragmentEntryLink Fragment Entry link object to get editable 
 *        values needed for a particular case processing.
 * @param html Fragment markup to process.
 * @param mode Processing mode (@see FragmentEntryLinkConstants)
 * @return Processed Fragment markup.
 * @throws PortalException
 */
public String processFragmentEntryLinkHTML(
      FragmentEntryLink fragmentEntryLink, String html, String mode)
   throws PortalException;

/**
 * @param html Fragment markup to validate.
 * @throws PortalException In case of any invalid content.
 */
public void validateFragmentEntryHTML(String html) throws PortalException;

FragmentEntryLink object gives us access to the particular fragment usage on a page, display page or page template and can be used if we want our result to depend on the particular usage parameters. Mode parameter can be used to give additional processing(or remove unnecessary processing) options in the EDIT(or VIEW) mode.

In this particular case, we don't need the validation method, but we have a good example in the Portal code.

Let's implement our fortune cookie tag processor! The only thing we have to do here is to iterate through all fortune tags we meet and replace them with a random cookie text. As I mentioned before, we use Jsoup to parse the markup and work with the document:

@Override
public String processFragmentEntryLinkHTML(
   FragmentEntryLink fragmentEntryLink, String html, String mode) {

   Document document = _getDocument(html);

   Elements elements = document.getElementsByTag(_FORTUNE);

   Random random = new Random();

   elements.forEach(
      element -> {
         Element fortuneText = document.createElement("span");

         fortuneText.attr("class", "fortune");

         fortuneText.text(_COOKIES[random.nextInt(7)]);

         element.replaceWith(fortuneText);
      });

   Element bodyElement = document.body();

   return bodyElement.html();
}

private Document _getDocument(String html) {
   Document document = Jsoup.parseBodyFragment(html);

   Document.OutputSettings outputSettings = new Document.OutputSettings();

   outputSettings.prettyPrint(false);

   document.outputSettings(outputSettings);

   return document;
}

private static final String[] _COOKIES = {
   "A friend asks only for your time not your money.",
   "If you refuse to accept anything but the best, you very often get it.",
   "Today it's up to you to create the peacefulness you long for.",
   "A smile is your passport into the hearts of others.",
   "A good way to keep healthy is to eat more Chinese food.",
   "Your high-minded principles spell success.",
   "The only easy day was yesterday."
};

private static final String _FORTUNE = "fortune";

 

That is it. After deploying this module to our Portal instance, fortune tag is ready to use in the Fragments editor:

It is up to you how to render your personal tag, which attributes to use, which technology to use to process tags content. You can even create your own script language, or apply the one which you already have in your CMS to avoid massive refactoring and use existing templates as-is.

Full Fortune Fragment Entry Processor code can be found here.

Hope it helps!

Blogs