Overview

This page will guide you through building blocks for an typical content structure: A two-column page.  

The page consists of these elements:

  1. A page (PageBlock or subclass)
    Defines the basic page template layout, the navigation item (one per language), and a set of sub blocks
  2. Two collections (CollectionBlock)
    The collection block is used only for grouping togehter content blocks. It usually does not contain any content itself. 
  3. A global (site-wide) footer (ContentBlock or subclass)
    Specific block instances can be placed everywhere, e.g. here used as a global, site-wide footer.


Page

All pages need to be PageBlocks or subclass instances thereof, since PageBlock maintans the navigation and routing features required by any callable page. 

PageBlock itself offers 

  • a page title (muli-lang)
  • a navigation title and virtual path (multi-lang)
  • time- / role-based access control

If you need more, you subclass PageBlock. In the example below, we add the following features:

  • An additional multi-lang property "megaDropdown"
  • Two different templates the admin can choose from
  • Two contexts in which blocks can be rendered


Block class

DefaultContentPage.java:
 

package blocks.defaultcontentpage;
...
@Entity
@DiscriminatorValue("DefaultContentPage")
@Table(name = "cms_default_pages")
public class DefaultContentPage extends PageBlock {
    // Block context constants
    public static final String CTX_LEFTCOL = "leftcol";
    public static final String CTX_RIGHTCOL = "rightcol";
     
    // We have multiple templates
    public static final String TPL_ONE_COL = "one_col";
    public static final String TPL_TWO_COL = "two_col";
    // Adding a static block finder (use an extended finder if you need specific find methods)
    public static BlockFinder<DefaultContentPage> find = new BlockFinder<>(DefaultContentPage.class);
    
    @OneToOne (cascade=CascadeType.ALL)
    private MString megaDropdown = new MString();
    private String selectedTemplate = TPL_TWO_COL;
    public MString getMegaDropdown() {
        return megaDropdown;
    }
    public void setMegaDropdown(MString megaDropdown) {
        this.megaDropdown = megaDropdown;
    }
    public String getSelectedTemplate() {
        return selectedTemplate;
    }
    public void setSelectedTemplate(String selectedTemplate) {
        this.selectedTemplate = selectedTemplate;
    }
    /**
     * Helper to get megadropdowns if available, or null if not, from all page subclasses.
     * @param page
     * @return megadropdown mstring or empty mstring, if type is not a DefaultContentPage instance
     */
    public static MString getMegaDropdownOf(PageBlock page) {
        if (page instanceof DefaultContentPage) {
            return ((DefaultContentPage) page).getMegaDropdown();
        } else {
            return new MString();
        }
    }
    @Override
    public Html render() {
        if(selectedTemplate.equals(TPL_ONE_COL)) {
            return showOneCol.render(this);
        } else {
            return showTwoCol.render(this);
        }
    }
    public Html editForm(Form editForm) {
        return edit.render(this, editForm, Controller.request().getQueryString("backURL"), null);
    }
 
    /**
     * Helper provides a list of templates for select input in backend
     */
    public static Map<String, String> templateOptions(){
        LinkedHashMap<String, String> vals = new LinkedHashMap<>();
        vals.put(TPL_ONE_COL, Messages.get("page.template." + TPL_ONE_COL));
        vals.put(TPL_TWO_COL, Messages.get("page.template." + TPL_TWO_COL));
        return vals;
    }
}

Note: To accessing PageBlock subclass methods like getMegaDropdown() when looping through PageBlocks in a navigation, use something like the static getMegaDropdownOf(). See also the FAQs.

Edit template

The edit template adds the mega dropdown multilang-textarea, plus a template selection.

edit.scala.html:

@(data: blocks.defaultcontentpage.DefaultContentPage, editForm: Form[_], backURL: String)(extension: Html=Html.apply(""))
 
@import helper._
@import views.html.helper.options
@import blocks.defaultcontentpage.DefaultContentPage
@import ch.insign.cms.views.html.admin.pageBlockEdit
@import ch.insign.cms.views.html.helpers._
 
@pageBlockEdit(data, editForm, backURL){
    @mstringTextarea(
        data.getMegaDropdown,
        theForm = editForm,
        formKey = "megaDropdown",
        label = "Mega Dropdown",
        placeholder = ""
    )
    <div class="form-group">
    @select(
        editForm("selectedTemplate"),
        options = options(DefaultContentPage.templateOptions()),
        '_label -> "Template",
        'class -> "form-control"
    )
    </div>
    @extension
}


 Note: @extension allows you to easily create sub classes from your class and enhance the edit template, just like the above template extends pageBlockEdit.scala.html.

 

Frontend template

A corresponding frontend template can look like this:

showTwoCol.scala.html:

@(data: blocks.defaultcontentpage.DefaultContentPage)
 
@import blocks.defaultcontentpage.DefaultContentPage
@import ch.insign.cms.views.html._blockBase
@import ch.insign.cms.models.Template
@import ch.insign.cms.models.CollectionBlock
@import ch.insign.cms.views.html.main
@main(data){
    @_blockBase(data=data, delete=true, edit=true, add=true, color="blue") {
        <div class="col-md-4">
            <div class="box">
                @Template.addBlockToSlot(classOf[CollectionBlock], data, "slot1").context(DefaultContentPage.CTX_LEFTCOL).cached
            </div>
        </div>
  
        <div class="col-md-8">
            <div class="box">
                @Template.addBlockToSlot(classOf[CollectionBlock], data, "slot2").context(DefaultContentPage.CTX_RIGHTCOL).cached
            </div>
        </div>
    }       
}

@main(data)

  • is the surrounding main layout / template
  • the main layout also contains the global footer block

@_blockBase(data=data, delete=true, edit=true, add=true, color="blue")

  • adds the basic block editing features for the page.
  • You can define the enabled controls and give it a debug color.

@Template.addBlockToSlot(classOf[CollectionBlock], data, "slot1").context(DefaultContentPage.LEFTCOL).cached

  • addBlockToSlot adds a block of the defined type to the given "slot". If such a block already exists for this page and slot, it will load the existing one, and create a new one otherwise.
  • .context() sets a configuration context which can be used to tell the block in which context it is being rendered (e.g. to choose a narrow template in small columns, or to limit available subblocks etc. See also context)
  • .cached() finally returns the rendered block template - either cached or .render()ed.


Context

The context is a text string which gives the block a clue where (in what context) it is being rendered. By default, context is set to "frontend" or "backend", depending on where the block is being shown. You can refine the context, e.g. set it to "maincol" or "sidebar". You can then either read it in your block directly, or use per-context config values:

 application.conf:

cms.context.maincolumn.excludedSubblocks = [SidebarBlock]
cms.context.sidebar.allowedSubblocks = [SidebarBlock, ContentBlock]


Hint: You can also use the per-context block configuration for your own configuration purposes. Just create a Config subclass and in config Namespace() return "cms.context.mycontext". (see also FAQs).


Collections

Collections are simple containers for other sub blocks. They have no edit template, and their frontend template is just outputting the list of subblocks.

In most cases, you should not need to subclass CollectionBlock, unless you have specific collections, e.g. that sort sub blocks by date, limit them etc.

Collections are not added by the user to a template - they're directly added using @Template.addBlockToSlot() (see above)

To limit the list of available subblocks to add, you can use contexts and configure excludedSubblocks / allowedSubblocks (see above).

Footer

If you want to add a specific instance of a block to a template,  use:

@Template.addBlockByKey(classOf[FooterContentBlock], "GlobalFooter").cached 

If you want a global block appearing on every page, add this to the main.scala.html layout file which you (ideally) include everywhere.

Note: Since such blocks are not present in the normal block parent/children hierarchy, you need to take special action when using caching (typically for global blocks you'll want to flush the complete cache when the global block changes).

@Template helpers

The Template class offers various cms-related template helper methods. Please check them out. Below are the most important ones listed:

package ch.insign.cms.models;
 
/**
 * This is a helper class for templates to work with cms objects.
 */
public class Template {
 
    /**
     * Add a content block to a slot in a template. If the slot was previously filled,
     * the existing block is returned, if the slot is empty, a new block is created and returned.
     *
     * @param blockClass
     * @param parentBlocke
     * @param slot
     * @return block
     */
    public static AbstractBlock addBlockToSlot(Class<? extends AbstractBlock> blockClass, AbstractBlock parentBlock, String slot) 
 
    /**
     * Add a block by its key. If no block with this key exists, it is created.
     *
     * @param blockClass
     * @param key
     * @return the block
     */
    public static AbstractBlock addBlockByKey(Class<? extends AbstractBlock> blockClass, String key)
 
    public static void setDebugMode(boolean state)
 
    public static boolean isDebugMode() 
 
    /**
     * Get the current language as a 2-character code (i.e. en_US is returned as en)
     */
    public static String getLanguage()
 
    /**
     * Add the data as html to the template (tags are then rendered, not escaped)
     * @param input
     * @return html
     */
    public static Html html(MString input)
 
    /**
     * Return the first not null, not empty string.
     * Returns "" if none was found.
     */
    public static String nonEmpty(Object... args)
 
    /**
     * Returns whether obj is an instance of clazz
     */
    public static boolean isInstanceOf(Object obj, Class clazz)
 
    /**
     * Filter a block list and return only those that should be shown
     * to the current user (applying time- and right restrictions)
     *
     * Note: If the user has write/modify rights for a block (i.e. an admin),
     * then it is always returned, regardless of time- or read rights.
     *
     * @param input the raw input list, typically parent.getSubblocks()
     * @return the filtered list
     */
    public static <T extends AbstractBlock> List<T> filterPermitted(List<T> input)
 
    /**
     * Add an uncached partial to the template. If the template is cached, the
     * uncached partial will still get rendered on each request.
     *
     * @param key the uncached partial key (as registered in CMS.getUncachedManager.register())
     * @param block the block instance for this template
     * @return a [uncached: <key>] tag (which is parsed later on) or the rendered partial if no cache is used
     */
    public static Html addUncached(String key, AbstractBlock block)   
}