Package org.olat.core.gui.control.generic.ajax.tree

Source Code of org.olat.core.gui.control.generic.ajax.tree.TreeController

/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS,
* <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) 2007-2008 frentix GmbH, Switzerland<br>
* <p>
*/
package org.olat.core.gui.control.generic.ajax.tree;

import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.json.JSONArray;
import org.olat.core.dispatcher.mapper.Mapper;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.media.StringMediaResource;
import org.olat.core.logging.AssertException;
import org.olat.core.util.ConsumableBoolean;
import org.olat.core.util.StringHelper;
import org.olat.core.util.URIHelper;
import org.olat.core.util.prefs.Preferences;

/**
* <h3>Description:</h3>
* The ajax tree controller provides a dynamic tree view with drag and drop
* support. The datamodel is loaded dynamically on request for each hierarchy
* level. <br>
* You must provide a AjaxTreeModel object that implements such a dynamic data
* model.
* <p>
* <h3>Events thrown by this controller:</h3>
* <ul>
* <li>new MoveTreeNodeEvent(node, oldParent, newParent, position)</li>
* <li>new TreeNodeClickedEvent(node)</li>
* <li>new TreeNodeModifiedEvent(node, modifiedValue)</li>
* </ul>
* <p>
* Initial Date: 04.07.2007 <br>
*
* @author Florian Gnaegi, frentix GmbH, http://www.frentix.com
*/
public class TreeController extends BasicController {
  private static final String CMD_PING = "ping";
  private static final String CMD_CLICK = "click";
  private static final String CMD_MOVE = "move";
  private static final String CMD_EDIT = "edit";
  private static final String CMD_EXPAND = "expand";
  private static final String CMD_COLLAPSE = "collapse";
  private static final String PARAM_INDEX = "index";
  private static final String PARAM_NEW_PARENT = "newParent";
  private static final String PARAM_OLD_PARENT = "oldParent";
  private static final String ENCODING_UTF_8 = "utf-8";
  private static final String CONTENT_TYPE_JAVASCRIPT = "application/javascript;";
  private static final String PARAM_NODE = "node";
  private static final String PARAM_PATH = "path";
  private static final String PARAM_VALUE = "value";

  private static final String GUI_PREFS_KEY_PREFIX = "expanded_";
 
  // This media resource is used as generic ok-return values for ajax requests
  private static final StringMediaResource okMediaResource;
  static {
    okMediaResource = new StringMediaResource();
    okMediaResource.setContentType(CONTENT_TYPE_JAVASCRIPT);
    okMediaResource.setEncoding(ENCODING_UTF_8);
    okMediaResource.setData("b_amt_status = true;");
  }

 
  private VelocityContainer mainVC, functionCallsVC;

  private Mapper treeDataMapper;
  private String guiPrefsKey;

  /**
   * Constructor for a dynamic ajax tree controller.
   *
   * @param ureq
   *            The user request
   * @param wControl
   *            The window control
   * @param titleRootNode
   *            Title displayed on the root node
   * @param dataModel
   *            The dynamic datamodel for this tree
   * @param nodeDragOverCallback
   *            Javascript method used as callback when a node is draged and
   *            moved around over the tree. The method must be defined
   *            somewhere else in your code, in this case you provide the
   *            method name. Alternatively you cen define the method right
   *            here in the string with function(event){}. Use NULL if not
   *            used.
   */
  public TreeController(UserRequest ureq, WindowControl wControl, final String titleRootNode, final AjaxTreeModel dataModel,
      String nodeDragOverCallback) {
    super(ureq, wControl);
    // The velocity main container
    mainVC = createVelocityContainer("tree");
    // The container that contains the JS code to select, remove or reload a
    // specific path. Is in a separate container so that a node can be
    // selected without making the mainVC dirty.
    functionCallsVC = createVelocityContainer("functioncalls");
    mainVC.put("functioncalls", functionCallsVC);
    // init with false values
    functionCallsVC.contextPut("selectPath", Boolean.FALSE);
    functionCallsVC.contextPut("reloadPath", Boolean.FALSE);
    functionCallsVC.contextPut("removePath", Boolean.FALSE);

    // The data mapper provides the dynamic data model to the view
    treeDataMapper = new Mapper() {
      public MediaResource handle(String relPath, HttpServletRequest request) {
        // each call is done for a specific node: get the child elements
        // for this node
        String data;
        String nodeId = request.getParameter(PARAM_NODE);
        if (StringHelper.containsNonWhitespace(nodeId)) {
          JSONArray jsonData = new JSONArray();
          List<AjaxTreeNode> children = dataModel.getChildrenFor(nodeId);
          for (AjaxTreeNode child : children) {
            jsonData.put(child);
          }
          data = jsonData.toString();
        } else {
          // something is wrong, maybe some hacker attack
          logError("Got a tree data mapper request but node parameter is missing. Root node title was::" + titleRootNode, null);
          data = "";
        }
        StringMediaResource mediaResource = new StringMediaResource();
        mediaResource.setEncoding(ENCODING_UTF_8);
        mediaResource.setContentType(CONTENT_TYPE_JAVASCRIPT);
        mediaResource.setData(data);
        return mediaResource;
      }
    };
    String dataMapperUri = registerMapper(treeDataMapper);
    mainVC.contextPut("dataMapperUri", dataMapperUri);

    // Some variables for the tree
    String treeID = dataModel.getTreeModelIdentifyer();
    mainVC.contextPut("treeId", dataModel.getTreeModelIdentifyer());
    mainVC.contextPut("titleRootNode", titleRootNode);

    // Add the drag over callback. Can also be null
    mainVC.contextPut("nodeDragOverCallback", nodeDragOverCallback);
   
    // Set default sort order
    setTreeSorting(true, true, true);
   
    // Disable tree inline editing by default
    setTreeInlineEditing(false, null, null);
   
    // expand pathes from last time
    Set<String> expandedPathes = null;
    if (treeID != null) {
      guiPrefsKey = GUI_PREFS_KEY_PREFIX + treeID;
      Preferences guiPrefs = ureq.getUserSession().getGuiPreferences();
      expandedPathes = (Set<String>) guiPrefs.get(TreeController.class, guiPrefsKey);
      mainVC.contextPut("presistPathes", Boolean.TRUE);
    } else {
      mainVC.contextPut("presistPathes", Boolean.FALSE);
    }
    if (expandedPathes == null) {
      expandedPathes = new HashSet<String>();
      // expand root by default
      expandedPathes.add("/" +dataModel.getTreeModelIdentifyer());
    }
    mainVC.contextPut("expandedPathes", expandedPathes);
   
    // Add the custom root icon if available
    String customRootIconCss = dataModel.getCustomRootIconCssClass();
    if (customRootIconCss == null) {
      mainVC.contextPut("hasCustomRootCSS", Boolean.FALSE);
    } else {
      mainVC.contextPut("hasCustomRootCSS", Boolean.TRUE);
      mainVC.contextPut("customRootCSS", customRootIconCss);
    }

    putInitialPanel(mainVC);
  }

  /**
   * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
   *      org.olat.core.gui.components.Component,
   *      org.olat.core.gui.control.Event)
   */
  public void event(UserRequest ureq, Component source, Event event) {
    if (source == mainVC) {
      // First parse the request using the URI helper. By default tomcat
      // has problems to decode the URI parameters properly because by
      // definition the URI is ISO encoded. ureq.getParameter() does use
      // the standard tomcat behaviour. Quite a mess, see here:
      // http://marc.info/?l=tomcat-user&m=121762788714431
      // The URIHelper decodes the uri parameters using UTF-8
      // (OLAT-3846)
      URIHelper uriHelper;
      String reqUri = ureq.getHttpReq().getRequestURI();
      String query = ureq.getHttpReq().getQueryString();
      String getUri = reqUri + "?" + query;
      try {
        uriHelper = new URIHelper(getUri);
      } catch (URISyntaxException e) {
        logWarn("Could not generate URIHelper for URI::" + getUri, e);
        return;
      }

      // handle move events: fire event to parent controller and let him decide
      // if the move should be allowed or not
      if (event.getCommand().equals(CMD_MOVE)) {
        String node = uriHelper.getParameter(PARAM_NODE);
        String oldParent = uriHelper.getParameter(PARAM_OLD_PARENT);
        String newParent = uriHelper.getParameter(PARAM_NEW_PARENT);
        String index = uriHelper.getParameter(PARAM_INDEX);
        int position = Integer.parseInt(index);
        // notify parent controller about the move
        MoveTreeNodeEvent moveEvent = new MoveTreeNodeEvent(node, oldParent, newParent, position);
        fireEvent(ureq, moveEvent);
        // prepare response as javascript string
        StringMediaResource smr = new StringMediaResource();
        // content type javascript forces menu tree to eval result
        smr.setContentType(CONTENT_TYPE_JAVASCRIPT);
        smr.setEncoding(ENCODING_UTF_8);
        if (moveEvent.isResultSuccess()) {
          // send ok back
          smr.setData("b_amt_status=true;");
        } else {
          // send failure and some messages for the user
          smr.setData("b_amt_status_title=\"" + moveEvent.getResultFailureTitle() + "\", b_amt_status_msg=\""
              + moveEvent.getResultFailureMessage() + "\"; b_amt_status=false;");
        }
        ureq.getDispatchResult().setResultingMediaResource(smr);

      } else if (event.getCommand().equals(CMD_CLICK)) {
        String node = uriHelper.getParameter(PARAM_NODE);
        TreeNodeClickedEvent clickedEvent = new TreeNodeClickedEvent(node);
        fireEvent(ureq, clickedEvent);

      } else if (event.getCommand().equals(CMD_EDIT)) {
        String node = uriHelper.getParameter(PARAM_NODE);
        String newValue = uriHelper.getParameter(PARAM_VALUE);
        TreeNodeModifiedEvent editedEvent = new TreeNodeModifiedEvent(node, newValue);
        fireEvent(ureq, editedEvent);

      } else if (event.getCommand().equals(CMD_EXPAND)) {
        Preferences guiPrefs = ureq.getUserSession().getGuiPreferences();
        String expandedPath = uriHelper.getParameter(PARAM_PATH);
        Set<String> oldPathes = (Set<String>) guiPrefs.get(TreeController.class, guiPrefsKey);
        if (oldPathes == null) oldPathes = new HashSet<String>();
        Set<String> newPathes = new HashSet();
        for (String oldPath : oldPathes) {
          // remove all parent pathes to reduce redundancy
          if (!expandedPath.startsWith(oldPath)) newPathes.add(oldPath);
        }
        // add newly expaned node
        newPathes.add(expandedPath);
        guiPrefs.putAndSave(TreeController.class, guiPrefsKey, newPathes);
        // return empty resource
        ureq.getDispatchResult().setResultingMediaResource(okMediaResource);
       
      } else if (event.getCommand().equals(CMD_COLLAPSE)) {
        Preferences guiPrefs = ureq.getUserSession().getGuiPreferences();
        String collapsedPath = uriHelper.getParameter(PARAM_PATH);
        Set<String> oldPathes = (Set<String>) guiPrefs.get(TreeController.class, guiPrefsKey);
        if (oldPathes == null) oldPathes = new HashSet<String>();
        Set<String> newPathes = new HashSet();
        for (String oldPath : oldPathes) {
          // remove all child pathes
          if (!oldPath.startsWith(collapsedPath)) newPathes.add(oldPath);
        }
        guiPrefs.putAndSave(TreeController.class, guiPrefsKey, newPathes);
        // return empty resource
        ureq.getDispatchResult().setResultingMediaResource(okMediaResource);

      } else if (event.getCommand().equals(CMD_PING)) {
        // Nothing special to do. If any component is dirty, the framework has
        // now the possibility to clean up, this is why this ping is necessary
      }
     
      // in all cases: don't re-render container, this are all ajax events!
      mainVC.setDirty(false);
    }
  }

  /**
   * Enable sorting in tree
   *
   * @param doSort
   *            true: sorting enabled; false: no sorting
   * @param asc
   *            true: sort ascending; false: sort descending
   * @param foldersFirst
   *            true: show sorted folder items first (items with attribute
   *            'isTypeLeaf') and then sorted leafs
   */
  public void setTreeSorting(boolean doSort, boolean asc, boolean foldersFirst) {
    if ((asc || foldersFirst) && !doSort) {
      throw new AssertException("Programming error: can not sort ascending or folderFirst when sorting flag is set to false");
    }
    mainVC.contextPut("doSort", Boolean.valueOf(doSort));
    mainVC.contextPut("sortOrder", (asc ? "asc" : "desc"));
    mainVC.contextPut("foldersFirst", Boolean.valueOf(foldersFirst));   
  }
 
  /**
   * Enable or disable the tree inline editing mode. Disabled by default.
   *
   * @param treeEditingEnabled
   *            true: enable tree editing; false: disable tree editing
   * @param beforeStartEditCallback
   *            Javascript method used as callback when a node editor is
   *            started. The method must be defined somewhere else in your
   *            code, in this case you provide the method name. Alternatively
   *            you cen define the method right here in the string with
   *            function(event){}. Use NULL if not used. The function can
   *            return false to cancel the edit request (e.g. to implement
   *            non-editable nodes)
   * @param beforeStateSaveCallback
   *            Javascript method used as callback when a node is edited and
   *            the user closes the editor to save the changes. The method
   *            must be defined somewhere else in your code, in this case you
   *            provide the method name. Alternatively you cen define the
   *            method right here in the string with function(event){}. Use
   *            NULL if not used. The function can return false to stop the
   *            edit request and revert to the original state (e.g. to check
   *            for valid new values)
   */
  public void setTreeInlineEditing(boolean treeEditingEnabled,
      String beforeStartEditCallback, String beforeStateSaveCallback) {
    mainVC.contextPut("treeEditingEnabled", Boolean
        .valueOf(treeEditingEnabled));
    if (beforeStartEditCallback != null)
      mainVC.contextPut("beforeStartEditCallback", Boolean
          .valueOf(beforeStartEditCallback));
    if (beforeStateSaveCallback != null)
      mainVC.contextPut("beforeStateSaveCallback", Boolean
          .valueOf(beforeStateSaveCallback));
  }

  /**
   * Select a certain node in the tree by the node path. When the tree is
   * already rendered, the activation of the node in the GUI is only performed
   * when the next AJAX render cycle is performed.
   * <br />
   * Example path: /rootNodeId/firstLevelNodeId/secondLevelNodeId
   * This will select the node with the ID secondLevelNodeId
   *
   * @param selectedPath The path of the node or NULL
   */
  public void selectPath(String selectedPath) {
    functionCallsVC.contextPut("selectPath", (selectedPath == null ? Boolean.FALSE : new ConsumableBoolean(true)));
    functionCallsVC.contextPut("selectedPath", selectedPath);
  }

  /**
   * Remove a certain node in the tree by the node path. This is only a
   * rendering helper method to remove a node in the GUI that has already been
   * removed in the datamodel. It does not change the datamodel whatsoever.
   * <br />
   * Example path: /rootNodeId/firstLevelNodeId/secondLevelNodeId
   * This will remove the node with the ID secondLevelNodeId and all its children from
   * the rendered tree
   *
   * @param removedPath The path of the node or NULL
   */
  public void removePath(String removedPath) {
    functionCallsVC.contextPut("removePath", (removedPath == null ? Boolean.FALSE : new ConsumableBoolean(true)));
    functionCallsVC.contextPut("removedPath", removedPath);
  }

  /**
   * Reload a certain node in the tree by the node path. This will force the
   * AJAX tree to reload the datamodel for the given node. This can be used
   * e.g. when on the server side a new node has been added to the model and
   * you don't want to reload the entire tree.
   * <br />
   * Example path: /rootNodeId/firstLevelNodeId/secondLevelNodeId
   * This will reload the node with the ID secondLevelNodeId
   *
   * @param reloadedPath the path of the node or NULL
   */
  public void reloadPath(String reloadedPath) {
    functionCallsVC.contextPut("reloadPath", (reloadedPath == null ? Boolean.FALSE : new ConsumableBoolean(true)));
    functionCallsVC.contextPut("reloadedPath", reloadedPath);
  }

  /**
   * Sets the root node title
   *
   * @param title The title of the root node
   */
  public void setRootNodeTitle(String title) {
    mainVC.contextPut("titleRootNode", title);
  }
 
  /**
   * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
   */
  protected void doDispose() {
    // mapper is auto deregistered by basic controller
    mainVC = null;
    treeDataMapper = null;
  }
}
TOP

Related Classes of org.olat.core.gui.control.generic.ajax.tree.TreeController

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.