Custom control CategoryCheckboxes in Magnolia CMS

Custom control CategoryCheckboxes in Magnolia CMS

In this post I’ll show you how I created a custom control called “CategoryCheckboxes”. It displays all categories of a selected data-folder as checkboxes, to easily select and save them. This control may also replace the categorization tab /modules/categorization/dialogs/generic/tabCategorization which is delivered with the categorization module, as it saves the selected categories exactly the same way.

For our custom control we only need one new class as we will use an existing save handler to save the selected categories. With the 2 control properties controlType and saveHandler we will refer to our custom control class and to the save handler info.magnolia.module.admininterface.MultiValueSaveHandler. The control class tells Magnolia how to render the control. The save handler class tells Magnolia how to save the selected categories.

The control setup will be as follows:

So, let’s start with our control class CategoryCheckboxesControl.java

public class CategoryCheckboxesControl extends DialogBox {

    private Content categoryFolder;
    private List<Content> existingCategoriesInJcr = new ArrayList<Content>();

    @Override
    public void init(HttpServletRequest request, HttpServletResponse response, Content storageNode, Content configNode) throws RepositoryException {
        super.init(request, response, storageNode, configNode);

        setLabel("Categories");
        setOptions(ListUtils.EMPTY_LIST);
    }

    @Override
    public void setOptions(List options) {
        Content siteRoot = null;

        if (newParagraph()) {
            Content currentContent = ContentUtil.getContent(ContentRepository.WEBSITE, MgnlContext.getParameter("mgnlPath"));
            siteRoot = getSiteRoot(currentContent);
        } else {
            siteRoot = getSiteRoot(getStorageNode());
            existingCategoriesInJcr = CategoryUtil.getCategories(getStorageNode(), CategoryUtil.DEFAULT_STORAGE_NAME);
        }

        String categoryFolderUuid = NodeDataUtil.getString(siteRoot, "availableCategories");
        categoryFolder = ContentUtil.getContentByUUID("data", categoryFolderUuid);
    }

    private boolean newParagraph() {
        return getStorageNode() == null;
    }

    private Content getSiteRoot(Content content) {
        try {
            Content root = TemplateCategoryUtil.findParentWithTemplateCategory(content, TemplateCategory.HOME);
            if (root == null) {
                return content.getAncestor(0);
            }
            return root;
        } catch (RepositoryException e) {
            throw new RuntimeException("Can't access site root.", e);
        }
    }

    @Override
    public void drawHtml(Writer out) throws IOException {
        drawHtmlPre(out);
        out.write(drawCheckboxes());
        drawHtmlPost(out);
    }

    private String drawCheckboxes() {
        if (categoryFolder == null)
            return "<p style='color:red'>Please select a news categories folder in the Home Template Settings.</p>";

        StringBuffer htmlBuffer = new StringBuffer();
        if (categoryFolder.hasChildren("category")) {
            renderCheckboxes(categoryFolder, htmlBuffer);
        }

        return htmlBuffer.toString();
    }

    private void renderCheckboxes(Content categoryFolder, StringBuffer htmlBuffer) {
        for (Content category : categoryFolder.getChildren("category")) {
            Button categoryCheckbox = new Button();
            categoryCheckbox.setButtonType(ControlImpl.BUTTONTYPE_CHECKBOX);
            categoryCheckbox.setName("categories"); //has to be the same as the control name, else the custom saveHandler is not found
            categoryCheckbox.setLabel(category.getName());
            categoryCheckbox.setValue(category.getUUID());
            categoryCheckbox.setId(category.getUUID()); //this is needed for the checkbox label to be able to select the corresponding checkbox
            categoryCheckbox.setCssClass("mgnlDialogButtonsetButton");

            for (Content existingCategoryInJcr : existingCategoriesInJcr) {
                if (uuidsAreEqual(existingCategoryInJcr, category))
                    preselect(categoryCheckbox);
            }

            htmlBuffer.append(categoryCheckbox.getHtml());

            if (categoryFolder.hasChildren("category")) {
                htmlBuffer.append("<div style='margin-left:20px'>");
                renderCheckboxes(category, htmlBuffer);
                htmlBuffer.append("</div>");
            }
        }
    }

    private void preselect(Button b) {
        b.setState(ControlImpl.BUTTONSTATE_PUSHED);
    }

    private boolean uuidsAreEqual(Content c1, Content c2) {
        return c1.getUUID().equals(c2.getUUID());
    }
}

As I commented in the setOptions() method, you are free to implement any way of fetching the uuid of a folder containing categories.

With the save handler property set to info.magnolia.module.admininterface.MultiValueSaveHandler the selected checkboxes are saved the way the categorization tab control would do it. Like that we can change between those 2 components without any complications. The save handler will save the selected categories as comma separated uuid’s in a property called categories.

UPDATE
The control now supports sub categories and will display them as a tree. See Screenshot below.

Our final control will then look like this: