Package org.apache.click.extras.tree

Source Code of org.apache.click.extras.tree.CheckboxTree$CheckboxCookieJavascriptRenderer

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.click.extras.tree;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.click.Context;
import org.apache.click.control.Decorator;
import org.apache.click.control.Form;
import org.apache.click.util.ClickUtils;
import org.apache.click.util.ContainerUtils;
import org.apache.click.util.HtmlStringBuffer;

import org.apache.commons.lang.ArrayUtils;

/**
* Implementation of a tree control that provides checkboxes to enable selection
* of nodes. This implementation assumes the tree is wrapped inside a html form.
* Each time the form is submitted, all checkbox values are processed by this control.
* <p/>
* Below is screenshot of how the tree will render in a browser.
*
* <table cellspacing='10'>
* <tr>
* <td>
* <img align='middle' hspace='2' src='checkbox-tree.png' title='Tree'/>
* </td>
* </tr>
* </table>
*
* <h3>Tree Example</h3>
*
* An example tree usage is provided below (this code was used to produce the screenshot):
*
* <pre class="prettyprint">
* public class PlainTreePage extends BorderPage {
*
*     protected Submit submit;
*     protected Submit cancel;
*     protected Form form;
*
*     public PlainTreePage() {
*         form = new Form("form");
*         addControl(form);
*
*         Tree tree = createTree();
*         form.add(tree);
*
*         submit = new Submit("submit", this, "onSubmitClick");
*         cancel = new Submit("cancel", this, "onCancelClick");
*
*         form.add(submit);
*         form.add(cancel);
*     }
*
*     public Tree createTree() {
*         Tree tree = new CheckboxTree("tree");
*
*         // Build the tree model, by default the root node is not rendered.
*         // This can be changed by calling setRootNodeDisplayed(true);
*         TreeNode root = new TreeNode("c:");
*         TreeNode dev = new TreeNode("dev", "1", root);
*         new TreeNode("java.pdf", "2", dev);
*         new TreeNode("ruby.pdf", "3", dev);
*
*         TreeNode programFiles = new TreeNode("program files", "4", root);
*         TreeNode adobe = new TreeNode("Adobe", "5", programFiles);
*         // This node is a directory not a file, so setChildrenSupported to true.
*         adobe.setChildrenSupported(true);
*
*         TreeNode download = new TreeNode("downloads", "6", root);
*         TreeNode web = new TreeNode("web", "7", download);
*         new TreeNode("html.pdf", "8", web);
*         new TreeNode("css.html", "9", web);
*
*         TreeNode databases = new TreeNode("databases", "10", download);
*         new TreeNode("mysql.html", "11", databases);
*         new TreeNode("oracle.pdf", "12", databases);
*         new TreeNode("postgres", "13", databases);
*
*         tree.setRootNode(root);
*         return tree;
*     }
* } </pre>
*
* <a name="resources"></a>
* <h3>CSS and JavaScript resources</h3>
*
* In addition to <a href="Tree.html#resources">Tree's resources</a>,
* the CheckboxTree control makes use of the following resources
* (which Click automatically deploys to the application directory, <tt>/click/tree</tt>):
*
* <ul>
* <li><tt>click/tree/checkbox-tree.js</tt></li>
* </ul>
*
* To import these Tree files simply reference the variables
* <span class="blue">$headElements</span> and
* <span class="blue">$jsElements</span> in the page template.
*
* @see Tree
*/
public class CheckboxTree extends Tree {

    // -------------------------------------------------------------- Constants

    /** Client side javascript import. This extends on the functions available in {@link Tree}. */
    public static final String HTML_IMPORTS =
            "<script type=\"text/javascript\" src=\"{0}/click/tree/checkbox-tree{1}.js\"></script>\n";

    /** default serial version id. */
    private static final long serialVersionUID = 1L;

    // ---------------------------------------------------- Private variables

    /**
     * Determines if the checkboxes of child nodes should also be
     * selected/deselected, when a parent checkbox is selected/deselected.
     */
    private boolean selectChildNodes = false;

   // ---------------------------------------------------- Public Constructors

    /**
     * Create an Tree control for the given name.
     *
     * @param name the tree name
     * @throws IllegalArgumentException if the name is null
     */
    public CheckboxTree(String name) {
        super(name);
    }

    /**
     * Create a Tree with no name defined.
     * <p/>
     * <b>Please note</b> the control's name must be defined before it is valid.
     */
    public CheckboxTree() {
    }

    // --------------------------------------------------------- Public getters and setters

    /**
     * Create and set the Tree's decorator that will render a Checkbox for
     * each tree node.
     *
     * @see #createDecorator()
     * @see org.apache.click.Control#onInit()
     */
    public void onInit() {
        setDecorator(createDecorator());
    }

    /**
     * Returns true if child nodes will also be selected/deselected.
     *
     * @return true if child nodes will be selected, false otherwise
     */
    public boolean isSelectChildNodes() {
        return selectChildNodes;
    }

    /**
     * Sets whether child nodes will also be selected/deselected.
     * <p/>
     * <b>Please note:</b> this feature only works if
     * {@link #setJavascriptEnabled(boolean) JavaScript} support is enabled.
     *
     * @param selectChildNodes determines if child nodes will be
     * selected/deselected
     */
    public void setSelectChildNodes(boolean selectChildNodes) {
        this.selectChildNodes = selectChildNodes;
    }

    // --------------------------------------------------------- Public Methods

    /**
     * Return the CheckboxTree HTML head imports statements for the following
     * resources:
     * <p/>
     * <ul>
     * <li><tt>click/tree/checkbox-tree.js</tt></li>
     * </ul>
     * <p/>
     * Additionally all the {@link Tree#getHtmlImports() Tree import statements}
     * are also returned.
     *
     * @see org.apache.click.Control#getHtmlImports()
     *
     * @return the HTML head import statements for the control
     */
    public String getHtmlImports() {
        HtmlStringBuffer buffer = new HtmlStringBuffer(256);
        if (isJavascriptEnabled()) {
            buffer.append(ClickUtils.createHtmlImport(HTML_IMPORTS,
                getContext()));
        }
        buffer.append(super.getHtmlImports());
        return buffer.toString();
    }

    /**
     * Binds the users request of selected nodes to the tree's nodes.
     * <p/>
     * This method is automatically invoked when the CheckboxTree's parent form
     * is submitted.
     * <p/>
     * See {@link #onFormSubmission()} for more details.
     * <p/>
     * If you do not want CheckboxTree to automatically invoke this method,
     * you can override {@link #onFormSubmission()} to do nothing by default.
     * <p/>
     * Then you must manually invoke this method when the form is submitted.
     * For example:
     *
     * <pre class="prettyprint">
     * public void onInit() {
     *     CheckboxTree tree = new CheckboxTree("tree") {
     *         public void onFormSubmission() {
     *             // Do nothing
     *         }
     *     }
     *     Form form = createForm();
     *     form.add(tree);
     *     Submit submit = new Submit("submit");
     *     form.add(submit);
     *     submit.setActionListener(new ActionListener() {
     *         public boolean onAction(Control source) {
     *             tree.bindSelectOrDeselectValues();
     *             return true;
     *         }
     *     });
     *     addControl(form);
     * } </pre>
     */
    public void bindSelectOrDeselectValues() {
        // With html forms, only "checked" checkbox values are submitted
        // to the server. So the request does not supply us the information
        // needed to calculate the nodes to be deselected. To find the nodes to
        // deselect, the newly selected nodes are subtracted from the currently
        // selected nodes. This implies that the tree's model is stored between
        // http requests.

        // To find the collection of selected nodes, the HttpServletRequest is
        // checked against the value of the field {@link #SELECT_TREE_NODE_PARAM}.

        // find id's of all the new selected node's'
        String[] nodeIds = getRequestValues(SELECT_TREE_NODE_PARAM);

        // find currently selected nodes
        boolean includeInvisibleNodes = isSelectChildNodes();
        Collection currentlySelected = getSelectedNodes(includeInvisibleNodes);

        // is there any new selected node's
        if (nodeIds == null || nodeIds.length == 0) {
            // deselect all the current selected nodes
            setSelectState(currentlySelected, false);
            return;
        }
        // build hashes of id's for fast lookup
        Set hashes = new HashSet();
        List newSelectedNodes = new ArrayList();
        for (int i = 0; i < nodeIds.length; i++) {
            hashes.add(nodeIds[i]);
        }
        nodeIds = null;

        // build list of newSelectedNodes
        for (Iterator it = iterator(getRootNode()); it.hasNext();) {
            TreeNode result = (TreeNode) it.next();
            if (hashes.contains(result.getId())) {
                newSelectedNodes.add(result);
            }
        }

        // calculate nodes for deselection by removing from currentlySelected nodes
        // those that must be selected.
        currentlySelected.removeAll(newSelectedNodes);

        setSelectState(currentlySelected, false);
        setSelectState(newSelectedNodes, true);
    }

    /**
     * This method binds any expand/collapse changes from the request parameters.
     * <p/>
     * In other words the node id's of expanded and collapsed nodes are
     * retrieved from the request.
     *
     * @see #bindExpandOrCollapseValues()
     */
    public void bindRequestValue() {
        bindExpandOrCollapseValues();
    }

    /**
     * This method is invoked when the CheckboxTree parent Form is submitted.
     * <p/>
     * This method delegates to {@link #bindSelectOrDeselectValues()} in order
     * to update the selected and deselected nodes.
     */
    protected void onFormSubmission() {
        bindSelectOrDeselectValues();
    }

    //------------------------------------------------------------Inner classes

    /**
     * Creates and returns a custom {@link Decorator} that will render a Checkbox
     * for each tree node.
     *
     * @return a decorator that renders a Checkbox for each tree node
     */
    protected Decorator createDecorator() {
        return new Decorator() {

            public String render(Object object, Context context) {
                TreeNode treeNode = (TreeNode) object;
                HtmlStringBuffer buffer = new HtmlStringBuffer();

                renderIcon(buffer, treeNode);

                renderCheckbox(buffer, treeNode);

                buffer.elementStart("span");
                if (treeNode.isSelected()) {
                    buffer.appendAttribute("class", "selected");
                } else {
                    buffer.appendAttribute("class", "unselected");
                }
                if (isJavascriptEnabled()) {
                    ((CheckboxJavascriptRenderer) javascriptHandler.getJavascriptRenderer()).renderSelect(
                        buffer);
                }
                buffer.closeTag();

                renderValue(buffer, treeNode);
                buffer.elementEnd("span");

                return buffer.toString();
            }

            /**
             * Render the node's value.
             *
             * @param buffer string buffer containing the markup
             * @param treeNode treeNode to render
             */
            protected void renderValue(HtmlStringBuffer buffer,
                TreeNode treeNode) {

                if (isJavascriptEnabled()) {
                    //create a href to interact with the checkbox on browser
                    buffer.elementStart("a");
                    Map hrefParameters =
                        Collections.singletonMap(SELECT_TREE_NODE_PARAM,
                                                 treeNode.getId());
                    buffer.appendAttribute("href", getHref(hrefParameters));

                    ((CheckboxJavascriptRenderer) javascriptHandler.getJavascriptRenderer()).renderValue(
                        buffer);
                    buffer.closeTag();
                    if (treeNode.getValue() != null) {
                        buffer.append(treeNode.getValue());
                    }
                    buffer.elementEnd("a");
                    buffer.append("\n");

                } else {
                    //just print normal value
                    if (treeNode.getValue() != null) {
                        buffer.append(treeNode.getValue());
                    }
                    buffer.append("\n");
                }
            }
        };
    }

    /**
     * Renders a Checkbox for the specified treeNode to the buffer.
     * <p/>
     * This method invokes {@link #getInputType()} which returns <tt>"checkbox"</tt>
     * by default, but allows subclasses to change the input type if necessary.
     *
     * @param buffer string buffer containing the markup
     * @param treeNode treeNode to render
     */
    protected void renderCheckbox(HtmlStringBuffer buffer, TreeNode treeNode) {
        buffer.append("<input ");
        if (isJavascriptEnabled()) {
            ((CheckboxJavascriptRenderer) javascriptHandler.getJavascriptRenderer()).renderCheckbox(
                buffer);
        }
        buffer.append(" style=\"margin:0\" type=\"");
        buffer.append(getInputType());
        buffer.append("\"");
        buffer.appendAttribute("name", SELECT_TREE_NODE_PARAM);
        buffer.appendAttribute("value", treeNode.getId());

        if (treeNode.isSelected()) {
            buffer.appendAttribute("checked", "checked");
        }

        buffer.elementEnd();
    }

    /**
     * Return the input type of the CheckboxTree, default value is
     * <tt>"checkbox"</tt>.
     * <p/>
     * This method allows subclasses to change the input type if necessary.
     * For example in order to render Radio buttons instead of Checkboxes,
     * override this method and return the input type <tt>"radio"</tt>.
     *
     * @return the input type of the CheckboxTree
     */
    protected String getInputType() {
        return "checkbox";
    }

    /**
     * <strong>Please note</strong> this interface is only meant for
     * developers of this control, not users.
     * <p/>
     * Provides the contract for pluggable javascript renderers, for
     * the CheckboxTree.
     */
    protected interface CheckboxJavascriptRenderer {

        /**
         * Called when a tree node's value is rendered. Enables the renderer
         * to add attributes needed by javascript functionality for example
         * something like:
         * <pre class="codeJava">
         *      buffer.append(<span class="st">"onclick=\"handleNodeSelection(this,event);\""</span>);
         * </pre>
         * The code above adds a javascript function call to the element.
         * <p/>
         * The code above is appended to whichever element the
         * tree is currently rendering at the time renderValue
         * is called.
         *
         * @param buffer string buffer containing the markup
         */
        void renderValue(HtmlStringBuffer buffer);

        /**
         * Called when a tree node's checkbox is rendered. Enables the
         * renderer to add attributes needed by javascript functionality
         * for example:
         * <pre class="codeJava">
         *     buffer.append(<span class="st">" onclick=\"onCheckboxClick(this,event);\""</span>);
         * </pre>
         * The code above adds a javascript function call to the element.
         * <p/>
         * The code above is appended to whichever element the
         * tree is currently rendering at the time renderValue
         * is called.
         *
         * @param buffer string buffer containing the markup
         */
        void renderCheckbox(HtmlStringBuffer buffer);

        /**
         * Called when a tree node's selected state is rendered. Enables
         * the renderer to add attributes needed by javascript functionality
         * for example something like:
         * <pre class="codeJava">
         *     buffer.appendAttribute(<span class="st">"id"</span>, selectId);
         * </pre>
         * The code above adds a javascript function call to the element.
         * <p/>
         * The code above is appended to whichever element the
         * tree is currently rendering at the time renderSelect
         * is called.
         *
         * @param buffer string buffer containing the markup
         */
        void renderSelect(HtmlStringBuffer buffer);
    }

    /**
     * <strong>Please note</strong> this class is only meant for
     * developers of this control, not users.
     * <p/>
     * Provides a base implementation of a CheckboxJavascriptRenderer
     * that subclasses can extend from.
     */
    protected class BaseCheckboxJavascriptRenderer extends AbstractJavascriptRenderer
            implements CheckboxJavascriptRenderer {

        /** holds the id of the select html element. */
        protected String selectId;

        /** holds the javascript call to select the node. */
        protected String nodeSelectionString;

        /** holds the id of the checkbox html element. */
        protected String checkboxId;

        /** holds the javascript call when user clicks on checkbox. */
        protected String checkboxOnClickString;

        /**
         * @see #renderValue(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderValue(HtmlStringBuffer buffer) {
            buffer.append(nodeSelectionString);
        }

        /**
         * @see #renderSelect(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderSelect(HtmlStringBuffer buffer) {
            buffer.appendAttribute("id", selectId);
        }

        /**
         * @see #renderCheckbox(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderCheckbox(HtmlStringBuffer buffer) {
            buffer.append(checkboxOnClickString);
            buffer.appendAttribute("id", checkboxId);
        }

        /**
         * @see #init(TreeNode)
         *
         * @param treeNode the current node rendered
         */
        public void init(TreeNode treeNode) {
            super.init(treeNode);
            selectId = buildString("s_", treeNode.getId(), "");
            checkboxId = buildString("c_", treeNode.getId(), "");

            HtmlStringBuffer buffer = new HtmlStringBuffer();
            buffer.append(" onclick=\"handleNodeSelection(this, event,'");
            buffer.append(selectId);
            buffer.append("','");
            buffer.append(checkboxId);
            buffer.append("',false); return false;\"");
            nodeSelectionString = buffer.toString();

            buffer = new HtmlStringBuffer();
            buffer.append(" onclick=\"onCheckboxClick(this,event,'");
            buffer.append(selectId);
            buffer.append("',");
            buffer.append(Boolean.toString(isSelectChildNodes()));
            buffer.append(");\"");
            checkboxOnClickString = buffer.toString();
        }
    }

    /**
     * <strong>Please note</strong> this class is only meant for
     * developers of this control, not users.
     * <p/>
     * Provides the rendering needed when a {@link #JAVASCRIPT_SESSION_POLICY}
     * is in effect.
     */
    protected class CheckboxSessionJavascriptRenderer extends SessionRenderer
            implements CheckboxJavascriptRenderer {

        /** A delegate for javascript rendering. */
        protected BaseCheckboxJavascriptRenderer checkboxRenderer = new BaseCheckboxJavascriptRenderer();

        /**
         * @see #renderValue(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderValue(HtmlStringBuffer buffer) {
            checkboxRenderer.renderValue(buffer);
        }

        /**
         * @see #renderSelect(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderSelect(HtmlStringBuffer buffer) {
            checkboxRenderer.renderSelect(buffer);
        }

        /**
         * @see #renderCheckbox(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderCheckbox(HtmlStringBuffer buffer) {
            checkboxRenderer.renderCheckbox(buffer);
        }

        /**
         * @see #init(TreeNode)
         *
         * @param treeNode the current node rendered
         */
        public void init(TreeNode treeNode) {
            super.init(treeNode);
            checkboxRenderer.init(treeNode);
        }
    }

    /**
     * <strong>Please note</strong> this class is only meant for
     * developers of this control, not users.
     * <p/>
     * Provides the rendering needed when a {@link #JAVASCRIPT_COOKIE_POLICY}
     * is in effect.
     */
    protected class CheckboxCookieJavascriptRenderer extends CookieRenderer
            implements CheckboxJavascriptRenderer {

        /** A delegate for javascript rendering. */
        protected BaseCheckboxJavascriptRenderer checkboxRenderer = new BaseCheckboxJavascriptRenderer();

        /**
         * Default constructor.
         *
         * @param expandedCookieName name of the cookie holding expanded id's
         * @param collapsedCookieName name of the cookie holding collapsed id's
         */
        protected CheckboxCookieJavascriptRenderer(String expandedCookieName, String collapsedCookieName) {
            super(expandedCookieName, collapsedCookieName);
        }

        /**
         * @see #renderValue(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderValue(HtmlStringBuffer buffer) {
            checkboxRenderer.renderValue(buffer);
        }

        /**
         * @see #renderSelect(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderSelect(HtmlStringBuffer buffer) {
            checkboxRenderer.renderSelect(buffer);
        }

        /**
         * @see #renderCheckbox(HtmlStringBuffer)
         *
         * @param buffer string buffer containing the markup
         */
        public void renderCheckbox(HtmlStringBuffer buffer) {
            checkboxRenderer.renderCheckbox(buffer);
        }

        /**
         * @see #init(TreeNode)
         *
         * @param treeNode the current node rendered
         */
        public void init(TreeNode treeNode) {
            super.init(treeNode);
            checkboxRenderer.init(treeNode);
        }
    }

    /**
     * <strong>Please note</strong> this class is only meant for
     * developers of this control, not users.
     * <p/>
     * This class implements a session based javascript handler.
     */
    protected class CheckboxSessionHandler extends SessionHandler {

        /**
         * Creates and initializes a new CheckboxSessionHandler.
         *
         * @param context provides access to the http request, and session
         */
        protected CheckboxSessionHandler(Context context) {
            super(context);
        }

        /**
         * @see Tree.JavascriptHandler#getJavascriptRenderer()
         *
         * @return currently installed javascript renderer
         */
        public JavascriptRenderer getJavascriptRenderer() {
            if (javascriptRenderer == null) {
                javascriptRenderer = new CheckboxSessionJavascriptRenderer();
            }
            return javascriptRenderer;
        }
    }

    /**
     * <strong>Please note</strong> this class is only meant for
     * developers of this control, not users.
     * <p/>
     * This class implements a session based javascript handler.
     */
    protected class CheckboxCookieHandler extends CookieHandler {

        /**
         * Creates and initializes a new CookieHandler.
         *
         * @param context provides access to the http request, and session
         */
        protected CheckboxCookieHandler(Context context) {
            super(context);
        }

        /**
         * @see Tree.JavascriptHandler#getJavascriptRenderer()
         *
         * @return currently installed javascript renderer
         */
        public JavascriptRenderer getJavascriptRenderer() {
            if (javascriptRenderer == null) {
                javascriptRenderer = new CheckboxCookieJavascriptRenderer(expandedCookieName, collapsedCookieName);
            }
            return javascriptRenderer;
        }
    }

    /**
     * Creates and return a new JavascriptHandler for the specified
     * tree node. This implementation overrides the super class, to
     * return its own custom JavascriptHandlers.
     *
     * @param javascriptPolicy the current javascript policy
     * @return newly created JavascriptHandler
     */
    protected JavascriptHandler createJavascriptHandler(int javascriptPolicy) {
        if (javascriptPolicy == JAVASCRIPT_SESSION_POLICY) {
            return new CheckboxSessionHandler(getContext());
        } else {
            return new CheckboxCookieHandler(getContext());
        }
    }

    // ------------------------------------------------ Package Private Methods

    /**
     * Expand / collapse the tree nodes.
     *
     * @return true to continue Page event processing or false otherwise
     */
    boolean postProcess() {
        if (isJavascriptEnabled()) {
            javascriptHandler.init(getContext());
        }

        if (!ArrayUtils.isEmpty(expandOrCollapseNodeIds)) {
            expandOrCollapse(expandOrCollapseNodeIds);
        }

        // Try and locate a parent form
        Form form = ContainerUtils.findForm(this);
        if (form != null) {
            // If the form was submitted, invoke bindSelectOrDeselectValues()
            if (form.isFormSubmission()) {
                onFormSubmission();
            }
        }
        return true;
    }
}
TOP

Related Classes of org.apache.click.extras.tree.CheckboxTree$CheckboxCookieJavascriptRenderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.