Skip to content

Commit

Permalink
Modify Tree component to render mostly in code, via direct manipulati…
Browse files Browse the repository at this point in the history
…on of the RenderQueue, to support recursive rendering of expanded nodes
  • Loading branch information
hlship committed Mar 14, 2011
1 parent 673eba5 commit 66ca066
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

import java.util.List;

import javax.swing.tree.TreeSelectionModel;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ComponentResources;
Expand All @@ -30,8 +28,13 @@
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Flow;
import org.apache.tapestry5.func.Worker;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.runtime.RenderCommand;
import org.apache.tapestry5.runtime.RenderQueue;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

import com.howardlewisship.tapx.core.DefaultTreeExpansionModel;
Expand Down Expand Up @@ -84,18 +87,13 @@ public class Tree
@Parameter
private Object value;

/**
* A renderable (usually a {@link Block}) that can render the label for a tree node.
* This will be invoked after the {@link #value} parameter has been updated.
*/
@Property
@Parameter(value = "block:defaultRenderTreeNodeLabel")
private Block label;

@Inject
private Block children;

@Property
private List<TreeNode> nodes;

@Property
private int nodeIndex;
private RenderCommand label;

@Environmental
private JavaScriptSupport jss;
Expand All @@ -106,59 +104,138 @@ public class Tree
@Persist
private TreeExpansionModel defaultTreeExpansionModel;

@Property
private final Renderable renderButton = new Renderable()
private static RenderCommand RENDER_CLOSE_TAG = new RenderCommand()
{
@Override
public void render(MarkupWriter writer)
public void render(MarkupWriter writer, RenderQueue queue)
{
value = node.getValue();

Element e = writer.element("span", "class", "tx-tree-icon");
writer.end();
};
};

if (node.isLeaf())
e.addClassName("tx-leaf-node");
else if (!node.getHasChildren())
e.addClassName("tx-empty-node");
private static RenderCommand RENDER_LABEL_SPAN = new RenderCommand()
{
public void render(MarkupWriter writer, RenderQueue queue)
{
writer.element("span", "class", "tx-tree-label");
};
};

if (!node.isLeaf() && node.getHasChildren())
/**
* Renders a single node (which may be the last within its containing node).
* This is a mix of immediate rendering, and queuing up various Blocks and Render commands
* to do the rest. May recursively render child nodes of the active node.
*
* @param node
* to render
* @param isLast
* if true, add "tx-last" attribute to the LI element
* @return command to render the node
*/
private RenderCommand toRenderCommand(final TreeNode node, final boolean isLast)
{
return new RenderCommand()
{
@Override
public void render(MarkupWriter writer, RenderQueue queue)
{
String clientId = jss.allocateClientId(resources);
// Inform the component's container about what value is being rendered
// (this may be necessary to generate the correct label for the node).
Tree.this.node = node;

e.attribute("id", clientId);
value = node.getValue();

Link expandChildren = resources.createEventLink("expandChildren", node.getId());
Link markExpanded = resources.createEventLink("markExpanded", node.getId());
Link markCollapsed = resources.createEventLink("markCollapsed", node.getId());
writer.element("li");

JSONObject spec = new JSONObject("clientId", clientId,
if (isLast)
writer.attributes("class", "tx-last");

"expandChildrenURL", expandChildren.toString(),
Element e = writer.element("span", "class", "tx-tree-icon");

"markExpandedURL", markExpanded.toString(),
if (node.isLeaf())
e.addClassName("tx-leaf-node");
else if (!node.getHasChildren())
e.addClassName("tx-empty-node");

"markCollapsedURL", markCollapsed.toString());
boolean expanded = expansionModel.isExpanded(node);

jss.addInitializerCall("tapxTreeNode", spec);
}
if (!node.isLeaf() && node.getHasChildren())
{
String clientId = jss.allocateClientId(resources);

writer.end();
}
};
e.attribute("id", clientId);

public String getContainerClass()
{
return className == null ? "tx-tree-container" : "tx-tree-container " + className;
Link expandChildren = resources.createEventLink("expandChildren", node.getId());
Link markExpanded = resources.createEventLink("markExpanded", node.getId());
Link markCollapsed = resources.createEventLink("markCollapsed", node.getId());

JSONObject spec = new JSONObject("clientId", clientId,

"expandChildrenURL", expandChildren.toString(),

"markExpandedURL", markExpanded.toString(),

"markCollapsedURL", markCollapsed.toString());

if (expanded)
spec.put("expanded", true);

jss.addInitializerCall("tapxTreeNode", spec);
}

writer.end(); // span.tx-tree-icon

// From here on in, we're pushing things onto the queue. Remember that
// execution order is reversed from order commands are pushed.

queue.push(RENDER_CLOSE_TAG); // li

if (expanded)
{
queue.push(new RenderNodes(node.getChildren()));
}

queue.push(RENDER_CLOSE_TAG);
queue.push(label);
queue.push(RENDER_LABEL_SPAN);

}
};
}

public String getItemClass()
/** Renders an &lt;ul&gt; element and renders each node recusively inside the element. */
private class RenderNodes implements RenderCommand
{
return nodeIndex == nodes.size() - 1 ? "tx-last" : null;
private final Flow<TreeNode> nodes;

public RenderNodes(List<TreeNode> nodes)
{
assert !nodes.isEmpty();

this.nodes = F.flow(nodes).reverse();
}

@Override
public void render(MarkupWriter writer, final RenderQueue queue)
{
writer.element("ul");
queue.push(RENDER_CLOSE_TAG);

queue.push(toRenderCommand(nodes.first(), true));

nodes.rest().each(new Worker<TreeNode>()
{
@Override
public void work(TreeNode element)
{
queue.push(toRenderCommand(element, false));
}
});
}
}

void setupRender()
public String getContainerClass()
{
nodes = model.getRootNodes();
return className == null ? "tx-tree-container" : "tx-tree-container " + className;
}

Object onExpandChildren(String nodeId)
Expand All @@ -167,12 +244,7 @@ Object onExpandChildren(String nodeId)

expansionModel.markExpanded(container);

nodes = container.getChildren();

// The children block contains what needs to be rendered. This will end up as a JSON response, as with a Zone
// component.

return children;
return new RenderNodes(container.getChildren());
}

Object onMarkExpanded(String nodeId)
Expand All @@ -196,4 +268,15 @@ public TreeExpansionModel getDefaultTreeExpansionModel()

return defaultTreeExpansionModel;
}

public Object getRenderRootNodes()
{
return new RenderNodes(model.getRootNodes());
}

/** Clears the tree's {@link TreeExpansionModel}. */
public void clearExpansions()
{
expansionModel.clear();
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter">

<div class="${containerClass}">
<t:delegate to="block:children"/>
<t:delegate to="renderRootNodes"/>
</div>

<t:remove>
This block is rendered for the root nodes, then re-rendered inside an Ajax call as each non-leaf node is
first expanded.
</t:remove>
<t:block t:id="children">
<ul>
<li class="${itemClass}" t:id="loop" t:type="loop" source="nodes" value="node" index="nodeIndex">
<t:delegate to="renderButton"/>
<span class="tx-tree-label">
<t:delegate to="label"/>
</span>
<t:trigger event="generateClientHandler"/>
</li>

</ul>
</t:block>


<t:block id="defaultRenderTreeNodeLabel">
${node.label}
</t:block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ Tapestry.Initializer.tapxSetEditor = function(spec) {

Tapestry.Initializer.tapxTreeNode = function(spec) {

var loaded = false;
var expanded = false;
var loaded = spec.expanded;
var expanded = spec.expanded;
var loading = false;

function successHandler(reply) {
Expand Down

0 comments on commit 66ca066

Please sign in to comment.