3.4Text components and cmsWorks includes

Text components are special tags within a rich text property of a cmsWorks document. They are non standard HTML tags containing the ID of a component cmsWorks document and an information about the positioning of the component.

Allowing text components in a single text property of a document is configured in the q-custom.js of the cmsWorks editors desktop. Also the types of positioning options are configured there.

Why text components

Text components appear to be simple at the first glance, but they are not. Almost every component comes with an additional feature (magnify the picture) or with a structure of information (add a title and a copyright to the picture). The strategy is to separate the content management from presentation. Components could be rendered differently for various products (HTML website / PDF / XML / JSON). Components of the same type should appear always in the same manner. Decide in your programming which information are mandatory and which are optional. Components can be reused in several locations of the website. Change the component content once and all appearances will update immediately. Avoid to copy those contents except for special reasons.

Example of text components

The following code shows the integration of text components using the TinyRichtextParts.

<%@page import="
                app.cmsworks.cms.document.ErrorView,
                app.cmsworks.cms.document.HTMLErrorView,
                app.cmsworks.util.uilink.UILink,
                app.cmsworks.cms.document.ContentInclude
               "
        session="false"
        contentType="text/html;charset=UTF-8"
%><%@include file="includes/documentmodel.jsf"
%><%@include file="includes/util-texthandler.jsf" 
%><%

ErrorView errors = new HTMLErrorView();
DocumentModel dmPage = null;
UILink uiLink = new UILink(request);

String htmlText = "";

// fetch the sensible data
try {
  // get the DocumentModel from request
  dmPage = new DocumentModel(request, new Types());
  // init the errorview
  errors.setPreview(dmPage);

  // use the created MyTinyRichtextParts with the text to render
  MyTinyRichtextParts textPartsText = new MyTinyRichtextParts(dmPage.getString(Types.PT_TEXT));

  // transform the links 
  textPartsText.handleLinks(dmPage);

  // remove empty lines
  textPartsText.deleteEmptyP();

  // fill components
  for (TinyRichtextComponentPart compPart : textPartsText.getComponentParts()) {
    DocumentModel mrComponent = new DocumentModel(compPart.getDocumentId(), dmPage);
    ContentInclude include = null;
    if (mrComponent.isType(Types.RT_MEDIUM)) {
      include = new ContentInclude(dmPage, mrComponent, "cmp-medium.jsp", errors);
    }
    else if (mrComponent.isType(Types.RT_INFOBOX)) {
      include = new ContentInclude(dmPage, mrComponent, "cmp-infobox.jsp", errors);
    }
    ...
    String htmlComponent = null;
    if (include != null) {
      htmlComponent = include.getIncludeContent();
    }
    // if the include has errors or was not assigned by the conditions before the compPart will simply
    // be deactivated
    if (htmlComponent != null) {
      htmlComponent = "<span class=\"txtcomp " + compPart.getPosition() + "\">" + htmlComponent + "</span>";
      compPart.setText(htmlComponent);
    }
    else {
      compPart.setDoProduce(false);
      errors.add(errors.asUILink(mrComponent) + "This document is not usable as text component.");
    }
  }
  
  htmlText = textPartsText.build();
}
catch (Throwable t) {
  if (errors.exit(response, t, dmPage, this.getClass().getName())) {
    return;
  }
}
//now start the HTML-Output
%><!DOCTYPE html>
<html>
<head>
...
  <%= uiLink.getIncludes() %>
</head>
<body>
...
  <!-- placing the text and editable markers into the html code -->
  <div class="richtext"><%= htmlText %></div>
...
  <%= errors.render() %>
</body>
</html>

Using MyRichtextParts to render richtext into the HTML page.

The loop

 for (TinyRichtextComponentPart compPart : textPartsText.getComponentParts()) {
...
}

iterates over the component parts. The document is fetched from the ComponentParts document ID. And after identifying the document type a component include is performed.

In the example either a document of type Medium or Infobox is handled. Any other document type produces an Error that is shown in the preview page but not in the production/live page.

htmlComponent = "<span class=\"txtcomp " + compPart.getPosition() + "\">" + htmlComponent + "</span>";
compPart.setText(htmlComponent);

With these two lines the created component is written into the component text part. Adding the position as a class to a span tag surrounding the component code enables the positioning using CSS within the HTML page.

ContentInclude

ContentInclude include = new ContentInclude([calling document], [called document], [JSP], [ErrorView]);
String htmlComponent = include.getIncludeContent();

The actual include is done with these two lines.

This executes an HTTP call to the own Generator targeting the URL of the called document, using the named JSP to be executed and showing an error if the call was not successful. Reasons may be: The called document is not allowed in the Generator config. The JSP does not exist. The JSP answered with an error code.

An example of a JSP producing a component:

<%@page import="
                app.cmsworks.cms.document.ErrorView,
                app.cmsworks.cms.document.HTMLErrorView,
                app.cmsworks.util.uilink.UILink,
                app.cmsworks.util.uilink.PreviewEditable
               "
        session="false"
        contentType="text/html;charset=UTF-8"
%><%@include file="includes/documentmodel.jsf" 
%><%@include file="includes/util-texthandler.jsf" 
%><%
ErrorView errors = new HTMLErrorView();
DocumentModel dmCmp = null;
UILink uiLink = new UILink(request);

try {
  dmCmp = new DocumentModel(request, new Types());
  errors.setPreview(dmCmp);
  
  PreviewEditable previewEditable = new PreviewEditable(request);

  StringBuffer sbContent = new StringBuffer();
  
  String htmlTitle = dmCmp.getString(Types.PT_INFOBOX_TITLE);
  if (htmlTitle.length() > 0) {
    htmlTitle = previewEditable.createEditableMarker(dmCmp, Types.PT_INFOBOX_TITLE) + 
        "<div class=\"inf-title\">" + htmlTitle + "</div>";
    sbContent.append(htmlTitle);
  }
  MyTinyRichtextParts textParts = new MyTinyRichtextParts(dmCmp.getString(Types.PT_INFOBOX_TEXT));
  String htmlTextEditable = previewEditable.createEditableMarkerForText(dmCmp, Types.PT_INFOBOX_TEXT, textParts);
  
  textParts.handleLinks(dmCmp);
  textParts.deleteEmptyP();
  
  String htmlText = textParts.build();
  if (htmlText.length() > 0) {
    htmlText = "<div class=\"inf-body\">" + htmlTextEditable + htmlText + "</div>";
    sbContent.append(htmlText);
  }
  // now start the HTML-Output!
  if (errors.isEmpty()) {
    %>
<%= uiLink.getPageLink() %>
<%= sbContent.toString() %>
    <%
  }
}
catch (Throwable t) {
  if (errors.exit(response, t, dmCmp, this.getClass().getName())) {
    return;
  }
}
%><%= errors.render() %>

The cmp-infobox.jsp produces a component to be used in richtext but also in any other possible context.

In this example the info box headline and text are optional. If no field is filled the returning code is simply empty.

The general strategy for components should be: If an error arises the component should not be produced at all. This is why all production is executed within the try {} catch () {} block. If errors are produced they are presented in the page calling this include (always only in preview).