This page will guide you through building blocks for an typical content structure: A two-column page.
The page consists of these elements:
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
If you need more, you subclass PageBlock. In the example below, we add the following features:
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.
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.
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)
@_blockBase(data=data, delete=true, edit=true, add=true, color="blue")
@Template.addBlockToSlot(classOf[CollectionBlock], data, "slot1").context(DefaultContentPage.LEFTCOL).cached
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 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).
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).
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)
}