diff --git a/plugins/dojo/src/main/java/org/apache/struts2/dojo/components/Tree.java b/plugins/dojo/src/main/java/org/apache/struts2/dojo/components/Tree.java index f73c2e1d3..1b8b07198 100644 --- a/plugins/dojo/src/main/java/org/apache/struts2/dojo/components/Tree.java +++ b/plugins/dojo/src/main/java/org/apache/struts2/dojo/components/Tree.java @@ -37,18 +37,15 @@ import com.opensymphony.xwork2.util.ValueStack; * * Renders a tree widget with AJAX support.

* - * The "id "attribute is normally specified, such that it could be looked up using - * javascript if necessary. The "id" attribute is required if the "treeSelectedTopic" - * attribute is going to be used.

+ * The "id "attribute is normally specified(recommended), such that it could be looked up using + * javascript if necessary. The "id" attribute is required if the "selectedNotifyTopic" or the + * "href" attributes are going to be used.

* * * - *

Examples - * + * *

- * 
- *
- * <-- statically -->
+ * 

Tree loaded statically

* <s:tree id="..." label="..."> * <s:treenode id="..." label="..." /> * <s:treenode id="..." label="..."> @@ -57,18 +54,62 @@ import com.opensymphony.xwork2.util.ValueStack; * &;lt;/s:treenode> * <s:treenode id="..." label="..." /> * </s:tree> - * - * <-- dynamically --> + *
+ * + * + * + *

Tree loaded dynamically

+ *
  * <s:tree
  *          id="..."
  *          rootNode="..."
  *          nodeIdProperty="..."
  *          nodeTitleProperty="..."
  *          childCollectionProperty="..." />
- *
- * 
  * 
- * + * + * + * + *

Tree loaded dynamically using AJAX

+ *
+ * <s:url id="nodesUrl" namespace="/nodecorate" action="getNodes" />
+ * <div style="float:left; margin-right: 50px;">
+ *     <sx:tree id="tree" href="%{#nodesUrl}" />
+ * </div>
+ * 
+ * + *

On this example the url specified on the "href" attibute will be called to load + * the elements on the root. The response is expected to be a JSON array of objects like: + *

+ *
+ * [
+ *      {
+ *           label: "Node 1",
+ *           hasChildren: false,
+ *           id: "Node1"
+ *      },
+ *      {
+ *           label: "Node 2",
+ *           hasChildren: true,
+ *           id: "Node2"
+ *      },
+ * ]
+ * 
+ * + *

"label" is the text that will be displayed for the node. "hasChildren" marks the node has + * having children or not (if true, a plus icon will be assigned to the node so it can be + * expanded). The "id" attribute will be used to load the children of the node, when the node + * is expanded. When a node is expanded a request will be made to the url in the "href" attribute + * and the node's "id" will be passed in the parameter "nodeId".

+ * + *

The children collection for a node will be loaded only once, to reload the children of a + * node, use the "reload()" function of the treenode widget. To reload the children nodes of "Node1" + * from the example above use the following javascript: + * + *

+ * dojo.widget.byId("Node1").reload();
+ * 
+ * */ @StrutsTag(name="tree", tldTagClass="org.apache.struts2.dojo.views.jsp.ui.TreeTag", description="Render a tree widget.") public class Tree extends ClosingUIBean { @@ -76,31 +117,33 @@ public class Tree extends ClosingUIBean { private static final String TEMPLATE = "tree-close"; private static final String OPEN_TEMPLATE = "tree"; - private String toggle = "fade"; - private String selectedNotifyTopics; - private String expandedNotifyTopics; - private String collapsedNotifyTopics; + protected String toggle; + protected String selectedNotifyTopics; + protected String expandedNotifyTopics; + protected String collapsedNotifyTopics; protected String rootNodeAttr; protected String childCollectionProperty; protected String nodeTitleProperty; protected String nodeIdProperty; - private String showRootGrid; - - private String showGrid; - private String blankIconSrc; - private String gridIconSrcL; - private String gridIconSrcV; - private String gridIconSrcP; - private String gridIconSrcC; - private String gridIconSrcX; - private String gridIconSrcY; - private String expandIconSrcPlus; - private String expandIconSrcMinus; - private String iconWidth; - private String iconHeight; - private String toggleDuration; - private String templateCssPath; + protected String showRootGrid; + protected String showGrid; + protected String blankIconSrc; + protected String gridIconSrcL; + protected String gridIconSrcV; + protected String gridIconSrcP; + protected String gridIconSrcC; + protected String gridIconSrcX; + protected String gridIconSrcY; + protected String expandIconSrcPlus; + protected String expandIconSrcMinus; + protected String iconWidth; + protected String iconHeight; + protected String toggleDuration; + protected String templateCssPath; + protected String href; + protected String errorNotifyTopics; + public Tree(ValueStack stack, HttpServletRequest request, HttpServletResponse response) { super(stack, request, response); } @@ -108,12 +151,12 @@ public class Tree extends ClosingUIBean { public boolean start(Writer writer) { boolean result = super.start(writer); - if (this.label == null) { + if (this.label == null && (href == null)) { if ((rootNodeAttr == null) || (childCollectionProperty == null) || (nodeTitleProperty == null) || (nodeIdProperty == null)) { - fieldError("label","The TreeTag requires either a value for 'label' or ALL of 'rootNode', " + + fieldError("label","The TreeTag requires either a value for 'label' or 'href' or ALL of 'rootNode', " + "'childCollectionProperty', 'nodeTitleProperty', and 'nodeIdProperty'", null); } } @@ -125,6 +168,8 @@ public class Tree extends ClosingUIBean { if (toggle != null) { addParameter("toggle", findString(toggle)); + } else { + addParameter("toggle", "fade"); } if (selectedNotifyTopics != null) { @@ -212,6 +257,11 @@ public class Tree extends ClosingUIBean { if (templateCssPath != null) { addParameter("templateCssPath", findString(templateCssPath)); } + if (href != null) + addParameter("href", findString(href)); + if (errorNotifyTopics != null) + addParameter("errorNotifyTopics", findString(errorNotifyTopics)); + } @Override @@ -469,5 +519,17 @@ public class Tree extends ClosingUIBean { public void setSelectedNotifyTopics(String selectedNotifyTopics) { this.selectedNotifyTopics = selectedNotifyTopics; } + + @StrutsTagAttribute(description="Url used to load the list of children nodes for an specific node, whose id will be " + + "passed as a parameter named 'nodeId' (empty for root)") + public void setHref(String href) { + this.href = href; + } + + @StrutsTagAttribute(description="Comma delimmited list of topics that will published after the request(if the request fails)." + + "Only valid if 'href' is set") + public void setErrorNotifyTopics(String errorNotifyTopics) { + this.errorNotifyTopics = errorNotifyTopics; + } } diff --git a/plugins/dojo/src/main/java/org/apache/struts2/dojo/views/jsp/ui/TreeTag.java b/plugins/dojo/src/main/java/org/apache/struts2/dojo/views/jsp/ui/TreeTag.java index a00e2c533..4eb673212 100644 --- a/plugins/dojo/src/main/java/org/apache/struts2/dojo/views/jsp/ui/TreeTag.java +++ b/plugins/dojo/src/main/java/org/apache/struts2/dojo/views/jsp/ui/TreeTag.java @@ -36,32 +36,33 @@ public class TreeTag extends AbstractClosingTag { private static final long serialVersionUID = 2735218501058548013L; - private String toggle; - private String selectedNotifyTopics; - private String expandedNotifyTopics; - private String collapsedNotifyTopics; - private String rootNode; - private String childCollectionProperty; - private String nodeTitleProperty; - private String nodeIdProperty; - private String showRootGrid; - - private String showGrid; - private String blankIconSrc; - private String gridIconSrcL; - private String gridIconSrcV; - private String gridIconSrcP; - private String gridIconSrcC; - private String gridIconSrcX; - private String gridIconSrcY; - private String expandIconSrcPlus; - private String expandIconSrcMinus; - private String iconWidth; - private String iconHeight; - private String toggleDuration; - private String templateCssPath; - + protected String toggle; + protected String selectedNotifyTopics; + protected String expandedNotifyTopics; + protected String collapsedNotifyTopics; + protected String rootNode; + protected String childCollectionProperty; + protected String nodeTitleProperty; + protected String nodeIdProperty; + protected String showRootGrid; + protected String showGrid; + protected String blankIconSrc; + protected String gridIconSrcL; + protected String gridIconSrcV; + protected String gridIconSrcP; + protected String gridIconSrcC; + protected String gridIconSrcX; + protected String gridIconSrcY; + protected String expandIconSrcPlus; + protected String expandIconSrcMinus; + protected String iconWidth; + protected String iconHeight; + protected String toggleDuration; + protected String templateCssPath; + protected String href; + protected String errorNotifyTopics; + public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { return new Tree(stack,req,res); } @@ -70,53 +71,32 @@ public class TreeTag extends AbstractClosingTag { super.populateParams(); Tree tree = (Tree) component; - if (childCollectionProperty != null) - tree.setChildCollectionProperty(childCollectionProperty); - if (nodeIdProperty != null) - tree.setNodeIdProperty(nodeIdProperty); - if (nodeTitleProperty != null) - tree.setNodeTitleProperty(nodeTitleProperty); - if (rootNode != null) - tree.setRootNode(rootNode); - if (toggle != null) - tree.setToggle(toggle); - if (selectedNotifyTopics != null) - tree.setSelectedNotifyTopics(selectedNotifyTopics); - if (expandedNotifyTopics != null) - tree.setExpandedNotifyTopics(expandedNotifyTopics); - if (collapsedNotifyTopics != null) - tree.setCollapsedNotifyTopics(collapsedNotifyTopics); - if (showRootGrid != null) - tree.setShowRootGrid(showRootGrid); + tree.setChildCollectionProperty(childCollectionProperty); + tree.setNodeIdProperty(nodeIdProperty); + tree.setNodeTitleProperty(nodeTitleProperty); + tree.setRootNode(rootNode); + tree.setToggle(toggle); + tree.setSelectedNotifyTopics(selectedNotifyTopics); + tree.setExpandedNotifyTopics(expandedNotifyTopics); + tree.setCollapsedNotifyTopics(collapsedNotifyTopics); + tree.setShowRootGrid(showRootGrid); - if (showGrid != null) - tree.setShowGrid(showGrid); - if (blankIconSrc != null) - tree.setBlankIconSrc(blankIconSrc); - if (gridIconSrcL != null) - tree.setGridIconSrcL(gridIconSrcC); - if (gridIconSrcV != null) - tree.setGridIconSrcV(gridIconSrcV); - if (gridIconSrcP != null) - tree.setGridIconSrcP(gridIconSrcP); - if (gridIconSrcC != null) - tree.setGridIconSrcC(gridIconSrcC); - if (gridIconSrcX != null) - tree.setGridIconSrcX(gridIconSrcX); - if (gridIconSrcY != null) - tree.setGridIconSrcY(gridIconSrcY); - if (expandIconSrcPlus != null) - tree.setExpandIconSrcPlus(expandIconSrcPlus); - if (expandIconSrcMinus != null) - tree.setExpandIconSrcMinus(expandIconSrcMinus); - if (iconWidth != null) - tree.setIconWidth(iconWidth); - if (iconHeight != null) - tree.setIconHeight(iconHeight); - if (toggleDuration != null) - tree.setToggleDuration(toggleDuration); - if (templateCssPath != null) - tree.setTemplateCssPath(templateCssPath); + tree.setShowGrid(showGrid); + tree.setBlankIconSrc(blankIconSrc); + tree.setGridIconSrcL(gridIconSrcC); + tree.setGridIconSrcV(gridIconSrcV); + tree.setGridIconSrcP(gridIconSrcP); + tree.setGridIconSrcC(gridIconSrcC); + tree.setGridIconSrcX(gridIconSrcX); + tree.setGridIconSrcY(gridIconSrcY); + tree.setExpandIconSrcPlus(expandIconSrcPlus); + tree.setExpandIconSrcMinus(expandIconSrcMinus); + tree.setIconWidth(iconWidth); + tree.setIconHeight(iconHeight); + tree.setToggleDuration(toggleDuration); + tree.setTemplateCssPath(templateCssPath); + tree.setHref(href); + tree.setErrorNotifyTopics(errorNotifyTopics); } public String getToggle() { @@ -305,5 +285,13 @@ public class TreeTag extends AbstractClosingTag { public void setSelectedNotifyTopics(String selectedNotifyTopics) { this.selectedNotifyTopics = selectedNotifyTopics; } + + public void setHref(String href) { + this.href = href; + } + + public void setErrorNotifyTopics(String errorNotifyTopics) { + this.errorNotifyTopics = errorNotifyTopics; + } } diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTree.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTree.js new file mode 100644 index 000000000..fa4a26330 --- /dev/null +++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTree.js @@ -0,0 +1,59 @@ +dojo.provide("struts.widget.StrutsTree"); + +dojo.require("dojo.widget.Tree"); + +dojo.widget.defineWidget( + "struts.widget.StrutsTree", + dojo.widget.Tree, { + widgetType : "StrutsTree", + + href : "", + errorNotifyTopics : "", + errorNotifyTopicsArray : null, + + postCreate : function() { + struts.widget.StrutsTree.superclass.postCreate.apply(this); + + //error topics + if(!dojo.string.isBlank(this.errorNotifyTopics)) { + this.errorNotifyTopicsArray = this.errorNotifyTopics.split(","); + } + + var self = this; + if(!dojo.string.isBlank(this.href)) { + dojo.io.bind({ + url: this.href, + useCache: false, + preventCache: true, + handler: function(type, data, e) { + if(type == 'load') { + //data should be an array + if(data) { + dojo.lang.forEach(data, function(descr) { + //create node for eachd descriptor + var newNode = dojo.widget.createWidget("struts:StrutsTreeNode",{ + title : descr.label, + isFolder: descr.hasChildren, + widgetId: descr.id + }); + self.addChild(newNode); + }); + } + } else { + //publish error topics + if(self.errorNotifyTopicsArray) { + dojo.lang.forEach(self.errorNotifyTopicsArray, function(topic) { + try { + dojo.event.topic.publish(topic, data, e, self); + } catch(ex){ + dojo.debug(ex); + } + }); + } + } + }, + mimetype: "text/json" + }); + } + } +}); \ No newline at end of file diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTreeNode.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTreeNode.js new file mode 100644 index 000000000..1c5a80660 --- /dev/null +++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/StrutsTreeNode.js @@ -0,0 +1,66 @@ +dojo.provide("struts.widget.StrutsTreeNode"); + +dojo.require("dojo.widget.TreeNode"); + +dojo.widget.defineWidget( + "struts.widget.StrutsTreeNode", + dojo.widget.TreeNode, { + widgetType : "StrutsTreeNode", + + loaded : false, + + expand : function() { + if(!this.loaded) { + this.reload(); + } + struts.widget.StrutsTreeNode.superclass.expand.apply(this); + }, + + removeChildren : function() { + var self = this; + var childrenCopy = dojo.lang.toArray(this.children); + dojo.lang.forEach(childrenCopy, function(node) { + self.removeNode(node); + }); + }, + + reload : function() { + var href = this.tree.href; + this.loaded = true; + + if(!dojo.string.isBlank(href)) { + //clear children list + this.removeChildren(); + //pass widgetId as parameter + var tmpHref = href + (href.indexOf("?") > -1 ? "&" : "?") + "nodeId=" + this.widgetId; + + var self = this; + this.markLoading(); + + dojo.io.bind({ + url: tmpHref, + useCache: false, + preventCache: true, + handler: function(type, data, e) { + if(type == 'load') { + //data should be an array + if(data) { + dojo.lang.forEach(data, function(descr) { + //create node for eachd descriptor + var newNode = dojo.widget.createWidget("struts:StrutsTreeNode",{ + title : descr.label, + isFolder: descr.hasChildren, + widgetId: descr.id + }); + self.addChild(newNode); + }); + } + } + + self.unMarkLoading(); + }, + mimetype: "text/json" + }); + } + } +}); \ No newline at end of file diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/__package__.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/__package__.js index ddf55e554..8f1f6fcd2 100644 --- a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/__package__.js +++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts/widget/__package__.js @@ -7,6 +7,8 @@ dojo.kwCompoundRequire({ "struts.widget.StrutsDatePicker", "struts.widget.BindEvent", "struts.widget.StrutsTreeSelector", - "struts.widget.StrutsTabContainer"] + "struts.widget.StrutsTabContainer", + "struts.widget.StrutsTreeNode", + "struts.widget.StrutsTree"] }); dojo.provide("struts.widget.*"); diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js index 54dc8658f..cdd0bbf5f 100644 --- a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js +++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js @@ -17706,6 +17706,968 @@ return t; } } }}); -dojo.kwCompoundRequire({common:["struts.widget.Bind","struts.widget.BindDiv","struts.widget.BindAnchor","struts.widget.ComboBox","struts.widget.StrutsTimePicker","struts.widget.StrutsDatePicker","struts.widget.BindEvent","struts.widget.StrutsTreeSelector","struts.widget.StrutsTabContainer"]}); +dojo.provide("dojo.widget.TreeNode"); +dojo.widget.defineWidget("dojo.widget.TreeNode",dojo.widget.HtmlWidget,function(){ +this.actionsDisabled=[]; +},{widgetType:"TreeNode",loadStates:{UNCHECKED:"UNCHECKED",LOADING:"LOADING",LOADED:"LOADED"},actions:{MOVE:"MOVE",REMOVE:"REMOVE",EDIT:"EDIT",ADDCHILD:"ADDCHILD"},isContainer:true,lockLevel:0,templateString:("
"+" "+"\t\t${this.title} "+" "+"${this.afterLabel} "+"
"+"
").replace(/(>|<)\s+/g,"$1"),childIconSrc:"",childIconFolderSrc:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/closed.gif"),childIconDocumentSrc:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/document.gif"),childIcon:null,isTreeNode:true,objectId:"",afterLabel:"",afterLabelNode:null,expandIcon:null,title:"",object:"",isFolder:false,labelNode:null,titleNode:null,imgs:null,expandLevel:"",tree:null,depth:0,isExpanded:false,state:null,domNodeInitialized:false,isFirstChild:function(){ +return this.getParentIndex()==0?true:false; +},isLastChild:function(){ +return this.getParentIndex()==this.parent.children.length-1?true:false; +},lock:function(){ +return this.tree.lock.apply(this,arguments); +},unlock:function(){ +return this.tree.unlock.apply(this,arguments); +},isLocked:function(){ +return this.tree.isLocked.apply(this,arguments); +},cleanLock:function(){ +return this.tree.cleanLock.apply(this,arguments); +},actionIsDisabled:function(_ead){ +var _eae=this; +var _eaf=false; +if(this.tree.strictFolders&&_ead==this.actions.ADDCHILD&&!this.isFolder){ +_eaf=true; +} +if(dojo.lang.inArray(_eae.actionsDisabled,_ead)){ +_eaf=true; +} +if(this.isLocked()){ +_eaf=true; +} +return _eaf; +},getInfo:function(){ +var info={widgetId:this.widgetId,objectId:this.objectId,index:this.getParentIndex(),isFolder:this.isFolder}; +return info; +},initialize:function(args,frag){ +this.state=this.loadStates.UNCHECKED; +for(var i=0;i0){ +for(var i=0;i<_eb4;i++){ +var img=this.tree.makeBlankImg(); +this.imgs.unshift(img); +dojo.html.insertBefore(this.imgs[0],this.domNode.firstChild); +} +} +if(_eb4<0){ +for(var i=0;i<-_eb4;i++){ +this.imgs.shift(); +dojo.html.removeNode(this.domNode.firstChild); +} +} +},markLoading:function(){ +this._markLoadingSavedIcon=this.expandIcon.src; +this.expandIcon.src=this.tree.expandIconSrcLoading; +},unMarkLoading:function(){ +if(!this._markLoadingSavedIcon){ +return; +} +var im=new Image(); +im.src=this.tree.expandIconSrcLoading; +if(this.expandIcon.src==im.src){ +this.expandIcon.src=this._markLoadingSavedIcon; +} +this._markLoadingSavedIcon=null; +},setFolder:function(){ +dojo.event.connect(this.expandIcon,"onclick",this,"onTreeClick"); +this.expandIcon.src=this.isExpanded?this.tree.expandIconSrcMinus:this.tree.expandIconSrcPlus; +this.isFolder=true; +},createDOMNode:function(tree,_eb9){ +this.tree=tree; +this.depth=_eb9; +this.imgs=[]; +for(var i=0;i-1?"&":"?")+"nodeId="+this.widgetId; +var self=this; +this.markLoading(); +dojo.io.bind({url:_ece,useCache:false,preventCache:true,handler:function(type,data,e){ +if(type=="load"){ +if(data){ +dojo.lang.forEach(data,function(_ed3){ +var _ed4=dojo.widget.createWidget("struts:StrutsTreeNode",{title:_ed3.label,isFolder:_ed3.hasChildren,widgetId:_ed3.id}); +self.addChild(_ed4); +}); +} +} +self.unMarkLoading(); +},mimetype:"text/json"}); +} +}}); +dojo.provide("dojo.json"); +dojo.json={jsonRegistry:new dojo.AdapterRegistry(),register:function(name,_ed6,wrap,_ed8){ +dojo.json.jsonRegistry.register(name,_ed6,wrap,_ed8); +},evalJson:function(json){ +try{ +return eval("("+json+")"); +} +catch(e){ +dojo.debug(e); +return json; +} +},serialize:function(o){ +var _edb=typeof (o); +if(_edb=="undefined"){ +return "undefined"; +}else{ +if((_edb=="number")||(_edb=="boolean")){ +return o+""; +}else{ +if(o===null){ +return "null"; +} +} +} +if(_edb=="string"){ +return dojo.string.escapeString(o); +} +var me=arguments.callee; +var _edd; +if(typeof (o.__json__)=="function"){ +_edd=o.__json__(); +if(o!==_edd){ +return me(_edd); +} +} +if(typeof (o.json)=="function"){ +_edd=o.json(); +if(o!==_edd){ +return me(_edd); +} +} +if(_edb!="function"&&typeof (o.length)=="number"){ +var res=[]; +for(var i=0;i_f0f.getParentIndex()){ +_f11--; +} +return _f11; +},onDrop:function(e){ +var _f13=this.position; +this.onDragOut(e); +var _f14=e.dragObject.treeNode; +if(!dojo.lang.isObject(_f14)){ +dojo.raise("TreeNode not found in dragObject"); +} +if(_f13=="onto"){ +return this.controller.move(_f14,this.treeNode,0); +}else{ +var _f15=this.getTargetParentIndex(_f14,_f13); +return this.controller.move(_f14,this.treeNode.parent,_f15); +} +}}); +dojo.dnd.TreeDNDController=function(_f16){ +this.treeController=_f16; +this.dragSources={}; +this.dropTargets={}; +}; +dojo.lang.extend(dojo.dnd.TreeDNDController,{listenTree:function(tree){ +dojo.event.topic.subscribe(tree.eventNames.createDOMNode,this,"onCreateDOMNode"); +dojo.event.topic.subscribe(tree.eventNames.moveFrom,this,"onMoveFrom"); +dojo.event.topic.subscribe(tree.eventNames.moveTo,this,"onMoveTo"); +dojo.event.topic.subscribe(tree.eventNames.addChild,this,"onAddChild"); +dojo.event.topic.subscribe(tree.eventNames.removeNode,this,"onRemoveNode"); +dojo.event.topic.subscribe(tree.eventNames.treeDestroy,this,"onTreeDestroy"); +},unlistenTree:function(tree){ +dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode,this,"onCreateDOMNode"); +dojo.event.topic.unsubscribe(tree.eventNames.moveFrom,this,"onMoveFrom"); +dojo.event.topic.unsubscribe(tree.eventNames.moveTo,this,"onMoveTo"); +dojo.event.topic.unsubscribe(tree.eventNames.addChild,this,"onAddChild"); +dojo.event.topic.unsubscribe(tree.eventNames.removeNode,this,"onRemoveNode"); +dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy,this,"onTreeDestroy"); +},onTreeDestroy:function(_f19){ +this.unlistenTree(_f19.source); +},onCreateDOMNode:function(_f1a){ +this.registerDNDNode(_f1a.source); +},onAddChild:function(_f1b){ +this.registerDNDNode(_f1b.child); +},onMoveFrom:function(_f1c){ +var _f1d=this; +dojo.lang.forEach(_f1c.child.getDescendants(),function(node){ +_f1d.unregisterDNDNode(node); +}); +},onMoveTo:function(_f1f){ +var _f20=this; +dojo.lang.forEach(_f1f.child.getDescendants(),function(node){ +_f20.registerDNDNode(node); +}); +},registerDNDNode:function(node){ +if(!node.tree.DNDMode){ +return; +} +var _f23=null; +var _f24=null; +if(!node.actionIsDisabled(node.actions.MOVE)){ +var _f23=new dojo.dnd.TreeDragSource(node.labelNode,this,node.tree.widgetId,node); +this.dragSources[node.widgetId]=_f23; +} +var _f24=new dojo.dnd.TreeDropTarget(node.labelNode,this.treeController,node.tree.DNDAcceptTypes,node); +this.dropTargets[node.widgetId]=_f24; +},unregisterDNDNode:function(node){ +if(this.dragSources[node.widgetId]){ +dojo.dnd.dragManager.unregisterDragSource(this.dragSources[node.widgetId]); +delete this.dragSources[node.widgetId]; +} +if(this.dropTargets[node.widgetId]){ +dojo.dnd.dragManager.unregisterDropTarget(this.dropTargets[node.widgetId]); +delete this.dropTargets[node.widgetId]; +} +}}); +dojo.provide("dojo.widget.TreeBasicController"); +dojo.widget.defineWidget("dojo.widget.TreeBasicController",dojo.widget.HtmlWidget,{widgetType:"TreeBasicController",DNDController:"",dieWithTree:false,initialize:function(args,frag){ +if(this.DNDController=="create"){ +this.DNDController=new dojo.dnd.TreeDNDController(this); +} +},listenTree:function(tree){ +dojo.event.topic.subscribe(tree.eventNames.createDOMNode,this,"onCreateDOMNode"); +dojo.event.topic.subscribe(tree.eventNames.treeClick,this,"onTreeClick"); +dojo.event.topic.subscribe(tree.eventNames.treeCreate,this,"onTreeCreate"); +dojo.event.topic.subscribe(tree.eventNames.treeDestroy,this,"onTreeDestroy"); +if(this.DNDController){ +this.DNDController.listenTree(tree); +} +},unlistenTree:function(tree){ +dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode,this,"onCreateDOMNode"); +dojo.event.topic.unsubscribe(tree.eventNames.treeClick,this,"onTreeClick"); +dojo.event.topic.unsubscribe(tree.eventNames.treeCreate,this,"onTreeCreate"); +dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy,this,"onTreeDestroy"); +},onTreeDestroy:function(_f2a){ +var tree=_f2a.source; +this.unlistenTree(tree); +if(this.dieWithTree){ +this.destroy(); +} +},onCreateDOMNode:function(_f2c){ +var node=_f2c.source; +if(node.expandLevel>0){ +this.expandToLevel(node,node.expandLevel); +} +},onTreeCreate:function(_f2e){ +var tree=_f2e.source; +var _f30=this; +if(tree.expandLevel){ +dojo.lang.forEach(tree.children,function(_f31){ +_f30.expandToLevel(_f31,tree.expandLevel-1); +}); +} +},expandToLevel:function(node,_f33){ +if(_f33==0){ +return; +} +var _f34=node.children; +var _f35=this; +var _f36=function(node,_f38){ +this.node=node; +this.expandLevel=_f38; +this.process=function(){ +for(var i=0;i",isExpanded:true,isTree:true,objectId:"",controller:"",selector:"",menu:"",expandLevel:"",blankIconSrc:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_blank.gif"),gridIconSrcT:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_t.gif"),gridIconSrcL:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_l.gif"),gridIconSrcV:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_v.gif"),gridIconSrcP:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_p.gif"),gridIconSrcC:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_c.gif"),gridIconSrcX:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_x.gif"),gridIconSrcY:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_y.gif"),gridIconSrcZ:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_grid_z.gif"),expandIconSrcPlus:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_expand_plus.gif"),expandIconSrcMinus:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_expand_minus.gif"),expandIconSrcLoading:dojo.uri.moduleUri("dojo.widget","templates/images/Tree/treenode_loading.gif"),iconWidth:18,iconHeight:18,showGrid:true,showRootGrid:true,actionIsDisabled:function(_f63){ +var _f64=this; +return dojo.lang.inArray(_f64.actionsDisabled,_f63); +},actions:{ADDCHILD:"ADDCHILD"},getInfo:function(){ +var info={widgetId:this.widgetId,objectId:this.objectId}; +return info; +},initializeController:function(){ +if(this.controller!="off"){ +if(this.controller){ +this.controller=dojo.widget.byId(this.controller); +}else{ +this.controller=dojo.widget.createWidget("TreeBasicController",{DNDController:(this.DNDMode?"create":""),dieWithTree:true}); +} +this.controller.listenTree(this); +}else{ +this.controller=null; +} +},initializeSelector:function(){ +if(this.selector!="off"){ +if(this.selector){ +this.selector=dojo.widget.byId(this.selector); +}else{ +this.selector=dojo.widget.createWidget("TreeSelector",{dieWithTree:true}); +} +this.selector.listenTree(this); +}else{ +this.selector=null; +} +},initialize:function(args,frag){ +var _f68=this; +for(name in this.eventNamesDefault){ +if(dojo.lang.isUndefined(this.eventNames[name])){ +this.eventNames[name]=this.widgetId+"/"+this.eventNamesDefault[name]; +} +} +for(var i=0;i0){ +_f87[_f88-1].updateExpandGridColumn(); +} +if(_f86 instanceof dojo.widget.Tree&&_f88==0&&_f87.length>0){ +_f87[0].updateExpandGrid(); +} +_f85.parent=_f85.tree=null; +return _f85; +},markLoading:function(){ +},unMarkLoading:function(){ +},lock:function(){ +!this.lockLevel&&this.markLoading(); +this.lockLevel++; +},unlock:function(){ +if(!this.lockLevel){ +dojo.raise("unlock: not locked"); +} +this.lockLevel--; +!this.lockLevel&&this.unMarkLoading(); +},isLocked:function(){ +var node=this; +while(true){ +if(node.lockLevel){ +return true; +} +if(node instanceof dojo.widget.Tree){ +break; +} +node=node.parent; +} +return false; +},flushLock:function(){ +this.lockLevel=0; +this.unMarkLoading(); +}}); +dojo.provide("struts.widget.StrutsTree"); +dojo.widget.defineWidget("struts.widget.StrutsTree",dojo.widget.Tree,{widgetType:"StrutsTree",href:"",errorNotifyTopics:"",errorNotifyTopicsArray:null,postCreate:function(){ +struts.widget.StrutsTree.superclass.postCreate.apply(this); +if(!dojo.string.isBlank(this.errorNotifyTopics)){ +this.errorNotifyTopicsArray=this.errorNotifyTopics.split(","); +} +var self=this; +if(!dojo.string.isBlank(this.href)){ +dojo.io.bind({url:this.href,useCache:false,preventCache:true,handler:function(type,data,e){ +if(type=="load"){ +if(data){ +dojo.lang.forEach(data,function(_f8e){ +var _f8f=dojo.widget.createWidget("struts:StrutsTreeNode",{title:_f8e.label,isFolder:_f8e.hasChildren,widgetId:_f8e.id}); +self.addChild(_f8f); +}); +} +}else{ +if(self.errorNotifyTopicsArray){ +dojo.lang.forEach(self.errorNotifyTopicsArray,function(_f90){ +try{ +dojo.event.topic.publish(_f90,data,e,self); +} +catch(ex){ +dojo.debug(ex); +} +}); +} +} +},mimetype:"text/json"}); +} +}}); +dojo.kwCompoundRequire({common:["struts.widget.Bind","struts.widget.BindDiv","struts.widget.BindAnchor","struts.widget.ComboBox","struts.widget.StrutsTimePicker","struts.widget.StrutsDatePicker","struts.widget.BindEvent","struts.widget.StrutsTreeSelector","struts.widget.StrutsTabContainer","struts.widget.StrutsTreeNode","struts.widget.StrutsTree"]}); dojo.provide("struts.widget.*"); diff --git a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js index cdf6b58b3..66e5f25c7 100644 --- a/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js +++ b/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js @@ -28831,6 +28831,2089 @@ dojo.widget.defineWidget( } }); +dojo.provide("dojo.widget.TreeNode"); + + + + + +dojo.widget.defineWidget("dojo.widget.TreeNode", dojo.widget.HtmlWidget, function() { + this.actionsDisabled = []; +}, +{ + widgetType: "TreeNode", + + loadStates: { + UNCHECKED: "UNCHECKED", + LOADING: "LOADING", + LOADED: "LOADED" + }, + + + actions: { + MOVE: "MOVE", + REMOVE: "REMOVE", + EDIT: "EDIT", + ADDCHILD: "ADDCHILD" + }, + + isContainer: true, + + lockLevel: 0, // lock ++ unlock --, so nested locking works fine + + + templateString: ('
' + + ' ' + + ' ${this.title} ' + + ' ' + + '${this.afterLabel} ' + + '
' + + '
').replace(/(>|<)\s+/g, '$1'), // strip whitespaces between nodes + + + childIconSrc: "", + childIconFolderSrc: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/closed.gif"), // for under root parent item child icon, + childIconDocumentSrc: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/document.gif"), // for under root parent item child icon, + + childIcon: null, + isTreeNode: true, + + objectId: "", // the widget represents an object + + afterLabel: "", + afterLabelNode: null, // node to the left of labelNode + + // an icon left from childIcon: imgs[-2]. + // if +/- for folders, blank for leaves + expandIcon: null, + + title: "", + object: "", // node may have object attached, settable from HTML + isFolder: false, + + labelNode: null, // the item label + titleNode: null, // the item title + imgs: null, // an array of icons imgs + + expandLevel: "", // expand to level + + tree: null, + + depth: 0, + + isExpanded: false, + + state: null, // after creation will change to loadStates: "loaded/loading/unchecked" + domNodeInitialized: false, // domnode is initialized with icons etc + + + isFirstChild: function() { + return this.getParentIndex() == 0 ? true: false; + }, + + isLastChild: function() { + return this.getParentIndex() == this.parent.children.length-1 ? true : false; + }, + + lock: function(){ return this.tree.lock.apply(this, arguments) }, + unlock: function(){ return this.tree.unlock.apply(this, arguments) }, + isLocked: function(){ return this.tree.isLocked.apply(this, arguments) }, + cleanLock: function(){ return this.tree.cleanLock.apply(this, arguments) }, + + actionIsDisabled: function(action) { + var _this = this; + + var disabled = false; + + if (this.tree.strictFolders && action == this.actions.ADDCHILD && !this.isFolder) { + disabled = true; + } + + if (dojo.lang.inArray(_this.actionsDisabled, action)) { + disabled = true; + } + + if (this.isLocked()) { + disabled = true; + } + + return disabled; + }, + + getInfo: function() { + // No title here (title may be widget) + var info = { + widgetId: this.widgetId, + objectId: this.objectId, + index: this.getParentIndex(), + isFolder: this.isFolder + } + + return info; + }, + + initialize: function(args, frag){ + + //dojo.debug(this.title) + + this.state = this.loadStates.UNCHECKED; + + for(var i=0; i move right, negative => move left + */ + adjustDepth: function(depthDiff) { + + for(var i=0; i0) { + for(var i=0; i= this.imgs.length-2) return; + this.imgs[idx].style.backgroundImage = 'url(' + src + ')'; + }, + + + updateIconTree: function(){ + this.tree.updateIconTree.call(this); + }, + + + + + expand: function(){ + if (this.isExpanded) return; + + if (this.children.length) { + this.showChildren(); + } + + this.isExpanded = true; + + this.updateExpandIcon(); + + dojo.event.topic.publish(this.tree.eventNames.expand, {source: this} ); + }, + + collapse: function(){ + if (!this.isExpanded) return; + + this.hideChildren(); + this.isExpanded = false; + + this.updateExpandIcon(); + + dojo.event.topic.publish(this.tree.eventNames.collapse, {source: this} ); + }, + + hideChildren: function(){ + this.tree.toggleObj.hide( + this.containerNode, this.toggleDuration, this.explodeSrc, dojo.lang.hitch(this, "onHide") + ); + + /* if dnd is in action, recalculate changed coordinates */ + if(dojo.exists(dojo, 'dnd.dragManager.dragObjects') && dojo.dnd.dragManager.dragObjects.length) { + dojo.dnd.dragManager.cacheTargetLocations(); + } + }, + + showChildren: function(){ + this.tree.toggleObj.show( + this.containerNode, this.toggleDuration, this.explodeSrc, dojo.lang.hitch(this, "onShow") + ); + + /* if dnd is in action, recalculate changed coordinates */ + if(dojo.exists(dojo, 'dnd.dragManager.dragObjects') && dojo.dnd.dragManager.dragObjects.length) { + dojo.dnd.dragManager.cacheTargetLocations(); + } + }, + + addChild: function(){ + return this.tree.addChild.apply(this, arguments); + }, + + doAddChild: function(){ + return this.tree.doAddChild.apply(this, arguments); + }, + + + + /* Edit current node : change properties and update contents */ + edit: function(props) { + dojo.lang.mixin(this, props); + if (props.title) { + this.titleNode.innerHTML = this.title; + } + + if (props.afterLabel) { + this.afterLabelNode.innerHTML = this.afterLabel; + } + + if (props.childIconSrc) { + this.buildChildIcon(); + } + + + }, + + + removeNode: function(){ return this.tree.removeNode.apply(this, arguments) }, + doRemoveNode: function(){ return this.tree.doRemoveNode.apply(this, arguments) }, + + + toString: function() { + return "["+this.widgetType+" Tree:"+this.tree+" ID:"+this.widgetId+" Title:"+this.title+"]"; + + } + +}); + + + + + +dojo.provide("struts.widget.StrutsTreeNode"); + + + +dojo.widget.defineWidget( + "struts.widget.StrutsTreeNode", + dojo.widget.TreeNode, { + widgetType : "StrutsTreeNode", + + loaded : false, + + expand : function() { + if(!this.loaded) { + this.reload(); + } + struts.widget.StrutsTreeNode.superclass.expand.apply(this); + }, + + removeChildren : function() { + var self = this; + var childrenCopy = dojo.lang.toArray(this.children); + dojo.lang.forEach(childrenCopy, function(node) { + self.removeNode(node); + }); + }, + + reload : function() { + var href = this.tree.href; + this.loaded = true; + + if(!dojo.string.isBlank(href)) { + //clear children list + this.removeChildren(); + //pass widgetId as parameter + var tmpHref = href + (href.indexOf("?") > -1 ? "&" : "?") + "nodeId=" + this.widgetId; + + var self = this; + this.markLoading(); + + dojo.io.bind({ + url: tmpHref, + useCache: false, + preventCache: true, + handler: function(type, data, e) { + if(type == 'load') { + //data should be an array + if(data) { + dojo.lang.forEach(data, function(descr) { + //create node for eachd descriptor + var newNode = dojo.widget.createWidget("struts:StrutsTreeNode",{ + title : descr.label, + isFolder: descr.hasChildren, + widgetId: descr.id + }); + self.addChild(newNode); + }); + } + } + + self.unMarkLoading(); + }, + mimetype: "text/json" + }); + } + } +}); + +dojo.provide("dojo.json"); + + + + +dojo.json = { + // jsonRegistry: AdapterRegistry a registry of type-based serializers + jsonRegistry: new dojo.AdapterRegistry(), + + register: function( /*String*/ name, + /*function*/ check, + /*function*/ wrap, + /*optional, boolean*/ override){ + // summary: + // Register a JSON serialization function. JSON serialization + // functions should take one argument and return an object + // suitable for JSON serialization: + // - string + // - number + // - boolean + // - undefined + // - object + // - null + // - Array-like (length property that is a number) + // - Objects with a "json" method will have this method called + // - Any other object will be used as {key:value, ...} pairs + // + // If override is given, it is used as the highest priority JSON + // serialization, otherwise it will be used as the lowest. + // name: + // a descriptive type for this serializer + // check: + // a unary function that will be passed an object to determine + // whether or not wrap will be used to serialize the object + // wrap: + // the serialization function + // override: + // optional, determines if the this serialization function will be + // given priority in the test order + + dojo.json.jsonRegistry.register(name, check, wrap, override); + }, + + evalJson: function(/*String*/ json){ + // summary: + // evaluates the passed string-form of a JSON object + // json: + // a string literal of a JSON item, for instance: + // '{ "foo": [ "bar", 1, { "baz": "thud" } ] }' + // return: + // the result of the evaluation + + // FIXME: should this accept mozilla's optional second arg? + try { + return eval("(" + json + ")"); + }catch(e){ + dojo.debug(e); + return json; + } + }, + + serialize: function(/*Object*/ o){ + // summary: + // Create a JSON serialization of an object, note that this + // doesn't check for infinite recursion, so don't do that! + // o: + // an object to be serialized. Objects may define their own + // serialization via a special "__json__" or "json" function + // property. If a specialized serializer has been defined, it will + // be used as a fallback. + // return: + // a String representing the serialized version of the passed + // object + + var objtype = typeof(o); + if(objtype == "undefined"){ + return "undefined"; + }else if((objtype == "number")||(objtype == "boolean")){ + return o + ""; + }else if(o === null){ + return "null"; + } + if (objtype == "string") { return dojo.string.escapeString(o); } + // recurse + var me = arguments.callee; + // short-circuit for objects that support "json" serialization + // if they return "self" then just pass-through... + var newObj; + if(typeof(o.__json__) == "function"){ + newObj = o.__json__(); + if(o !== newObj){ + return me(newObj); + } + } + if(typeof(o.json) == "function"){ + newObj = o.json(); + if (o !== newObj) { + return me(newObj); + } + } + // array + if(objtype != "function" && typeof(o.length) == "number"){ + var res = []; + for(var i = 0; i < o.length; i++){ + var val = me(o[i]); + if(typeof(val) != "string"){ + val = "undefined"; + } + res.push(val); + } + return "[" + res.join(",") + "]"; + } + // look in the registry + try { + window.o = o; + newObj = dojo.json.jsonRegistry.match(o); + return me(newObj); + }catch(e){ + // dojo.debug(e); + } + // it's a function with no adapter, bad + if(objtype == "function"){ + return null; + } + // generic object code path + res = []; + for (var k in o){ + var useKey; + if (typeof(k) == "number"){ + useKey = '"' + k + '"'; + }else if (typeof(k) == "string"){ + useKey = dojo.string.escapeString(k); + }else{ + // skip non-string or number keys + continue; + } + val = me(o[k]); + if(typeof(val) != "string"){ + // skip non-serializable values + continue; + } + res.push(useKey + ":" + val); + } + return "{" + res.join(",") + "}"; + } +}; + +/** + * TreeDrag* specialized on managing subtree drags + * It selects nodes and visualises what's going on, + * but delegates real actions upon tree to the controller + * + * This code is considered a part of controller +*/ + +dojo.provide("dojo.dnd.TreeDragAndDrop"); + + + + + + + +dojo.dnd.TreeDragSource = function(node, syncController, type, treeNode){ + this.controller = syncController; + this.treeNode = treeNode; + + dojo.dnd.HtmlDragSource.call(this, node, type); +} + +dojo.inherits(dojo.dnd.TreeDragSource, dojo.dnd.HtmlDragSource); + +dojo.lang.extend(dojo.dnd.TreeDragSource, { + onDragStart: function(){ + /* extend adds functions to prototype */ + var dragObject = dojo.dnd.HtmlDragSource.prototype.onDragStart.call(this); + //dojo.debugShallow(dragObject) + + dragObject.treeNode = this.treeNode; + + dragObject.onDragStart = dojo.lang.hitch(dragObject, function(e) { + + /* save selection */ + this.savedSelectedNode = this.treeNode.tree.selector.selectedNode; + if (this.savedSelectedNode) { + this.savedSelectedNode.unMarkSelected(); + } + + var result = dojo.dnd.HtmlDragObject.prototype.onDragStart.apply(this, arguments); + + + /* remove background grid from cloned object */ + var cloneGrid = this.dragClone.getElementsByTagName('img'); + for(var i=0; i it was allowed before, no accept check is needed + if (position=="onto" || + (!this.isAdjacentNode(sourceTreeNode, position) + && this.controller.canMove(sourceTreeNode, this.treeNode.parent) + ) + ) { + return position; + } else { + return false; + } + + }, + + onDragOut: function(e) { + this.clearAutoExpandTimer(); + + this.hideIndicator(); + }, + + + clearAutoExpandTimer: function() { + if (this.autoExpandTimer) { + clearTimeout(this.autoExpandTimer); + this.autoExpandTimer = null; + } + }, + + + + onDragMove: function(e, dragObjects){ + + var sourceTreeNode = dragObjects[0].treeNode; + + var position = this.getAcceptPosition(e, sourceTreeNode); + + if (position) { + this.showIndicator(position); + } + + }, + + isAdjacentNode: function(sourceNode, position) { + + if (sourceNode === this.treeNode) return true; + if (sourceNode.getNextSibling() === this.treeNode && position=="before") return true; + if (sourceNode.getPreviousSibling() === this.treeNode && position=="after") return true; + + return false; + }, + + + /* get DNDMode and see which position e fits */ + getPosition: function(e, DNDMode) { + var node = dojo.byId(this.treeNode.labelNode); + var mousey = e.pageY || e.clientY + dojo.body().scrollTop; + var nodey = dojo.html.getAbsolutePosition(node).y; + var height = dojo.html.getBorderBox(node).height; + + var relY = mousey - nodey; + var p = relY / height; + + var position = ""; // "" <=> forbidden + if (DNDMode & dojo.widget.Tree.prototype.DNDModes.ONTO + && DNDMode & dojo.widget.Tree.prototype.DNDModes.BETWEEN) { + if (p<=0.3) { + position = "before"; + } else if (p<=0.7) { + position = "onto"; + } else { + position = "after"; + } + } else if (DNDMode & dojo.widget.Tree.prototype.DNDModes.BETWEEN) { + if (p<=0.5) { + position = "before"; + } else { + position = "after"; + } + } + else if (DNDMode & dojo.widget.Tree.prototype.DNDModes.ONTO) { + position = "onto"; + } + + + return position; + }, + + + + getTargetParentIndex: function(sourceTreeNode, position) { + + var index = position == "before" ? this.treeNode.getParentIndex() : this.treeNode.getParentIndex()+1; + if (this.treeNode.parent === sourceTreeNode.parent + && this.treeNode.getParentIndex() > sourceTreeNode.getParentIndex()) { + index--; // dragging a node is different for simple move bacause of before-after issues + } + + return index; + }, + + + onDrop: function(e){ + // onDragOut will clean position + + + var position = this.position; + +//dojo.debug(position); + + this.onDragOut(e); + + var sourceTreeNode = e.dragObject.treeNode; + + if (!dojo.lang.isObject(sourceTreeNode)) { + dojo.raise("TreeNode not found in dragObject") + } + + if (position == "onto") { + return this.controller.move(sourceTreeNode, this.treeNode, 0); + } else { + var index = this.getTargetParentIndex(sourceTreeNode, position); + return this.controller.move(sourceTreeNode, this.treeNode.parent, index); + } + + //dojo.debug('drop2'); + + + + } + + +}); + + + +dojo.dnd.TreeDNDController = function(treeController) { + + // I use this controller to perform actions + this.treeController = treeController; + + this.dragSources = {}; + + this.dropTargets = {}; + +} + +dojo.lang.extend(dojo.dnd.TreeDNDController, { + + + listenTree: function(tree) { + //dojo.debug("Listen tree "+tree); + dojo.event.topic.subscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode"); + dojo.event.topic.subscribe(tree.eventNames.moveFrom, this, "onMoveFrom"); + dojo.event.topic.subscribe(tree.eventNames.moveTo, this, "onMoveTo"); + dojo.event.topic.subscribe(tree.eventNames.addChild, this, "onAddChild"); + dojo.event.topic.subscribe(tree.eventNames.removeNode, this, "onRemoveNode"); + dojo.event.topic.subscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy"); + }, + + + unlistenTree: function(tree) { + //dojo.debug("Listen tree "+tree); + dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode"); + dojo.event.topic.unsubscribe(tree.eventNames.moveFrom, this, "onMoveFrom"); + dojo.event.topic.unsubscribe(tree.eventNames.moveTo, this, "onMoveTo"); + dojo.event.topic.unsubscribe(tree.eventNames.addChild, this, "onAddChild"); + dojo.event.topic.unsubscribe(tree.eventNames.removeNode, this, "onRemoveNode"); + dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy"); + }, + + onTreeDestroy: function(message) { + this.unlistenTree(message.source); + // I'm not widget so don't use destroy() call and dieWithTree + }, + + onCreateDOMNode: function(message) { + this.registerDNDNode(message.source); + }, + + onAddChild: function(message) { + this.registerDNDNode(message.child); + }, + + onMoveFrom: function(message) { + var _this = this; + dojo.lang.forEach( + message.child.getDescendants(), + function(node) { _this.unregisterDNDNode(node); } + ); + }, + + onMoveTo: function(message) { + var _this = this; + dojo.lang.forEach( + message.child.getDescendants(), + function(node) { _this.registerDNDNode(node); } + ); + }, + + /** + * Controller(node model) creates DNDNodes because it passes itself to node for synchroneous drops processing + * I can't process DnD with events cause an event can't return result success/false + */ + registerDNDNode: function(node) { + if (!node.tree.DNDMode) return; + +//dojo.debug("registerDNDNode "+node); + + /* I drag label, not domNode, because large domNodes are very slow to copy and large to drag */ + + var source = null; + var target = null; + + if (!node.actionIsDisabled(node.actions.MOVE)) { + //dojo.debug("reg source") + var source = new dojo.dnd.TreeDragSource(node.labelNode, this, node.tree.widgetId, node); + this.dragSources[node.widgetId] = source; + } + + var target = new dojo.dnd.TreeDropTarget(node.labelNode, this.treeController, node.tree.DNDAcceptTypes, node); + + this.dropTargets[node.widgetId] = target; + + }, + + + unregisterDNDNode: function(node) { + + if (this.dragSources[node.widgetId]) { + dojo.dnd.dragManager.unregisterDragSource(this.dragSources[node.widgetId]); + delete this.dragSources[node.widgetId]; + } + + if (this.dropTargets[node.widgetId]) { + dojo.dnd.dragManager.unregisterDropTarget(this.dropTargets[node.widgetId]); + delete this.dropTargets[node.widgetId]; + } + } + + + + + +}); + + +dojo.provide("dojo.widget.TreeBasicController"); + + + + + + +dojo.widget.defineWidget("dojo.widget.TreeBasicController", dojo.widget.HtmlWidget, { + widgetType: "TreeBasicController", + + DNDController: "", + + dieWithTree: false, + + initialize: function(args, frag){ + + /* no DND by default for compatibility */ + if (this.DNDController == "create") { + + this.DNDController = new dojo.dnd.TreeDNDController(this); + } + + + + }, + + + /** + * Binds controller to all tree events + */ + listenTree: function(tree) { + //dojo.debug("Event "+tree.eventNames.treeClick); + dojo.event.topic.subscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode"); + dojo.event.topic.subscribe(tree.eventNames.treeClick, this, "onTreeClick"); + dojo.event.topic.subscribe(tree.eventNames.treeCreate, this, "onTreeCreate"); + dojo.event.topic.subscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy"); + + if (this.DNDController) { + this.DNDController.listenTree(tree); + } + }, + + unlistenTree: function(tree) { + dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode"); + dojo.event.topic.unsubscribe(tree.eventNames.treeClick, this, "onTreeClick"); + dojo.event.topic.unsubscribe(tree.eventNames.treeCreate, this, "onTreeCreate"); + dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy"); + }, + + onTreeDestroy: function(message) { + var tree = message.source; + + this.unlistenTree(tree); + + if (this.dieWithTree) { + //alert("Killing myself "+this.widgetId); + this.destroy(); + //dojo.debug("done"); + } + }, + + onCreateDOMNode: function(message) { + + var node = message.source; + + + if (node.expandLevel > 0) { + this.expandToLevel(node, node.expandLevel); + } + }, + + // perform actions-initializers for tree + onTreeCreate: function(message) { + var tree = message.source; + var _this = this; + if (tree.expandLevel) { + dojo.lang.forEach(tree.children, + function(child) { + _this.expandToLevel(child, tree.expandLevel-1) + } + ); + } + }, + + expandToLevel: function(node, level) { + if (level == 0) return; + + var children = node.children; + var _this = this; + + var handler = function(node, expandLevel) { + this.node = node; + this.expandLevel = expandLevel; + // recursively expand opened node + this.process = function() { + //dojo.debug("Process "+node+" level "+level); + for(var i=0; icreateDOMNode is called(program way) OR createDOMNode (html-way) + // hook events to operate on new DOMNode, create dropTargets etc + createDOMNode: "createDOMNode", + // tree created.. Perform tree-wide actions if needed + treeCreate: "treeCreate", + treeDestroy: "treeDestroy", + // expand icon clicked + treeClick: "treeClick", + // node icon clicked + iconClick: "iconClick", + // node title clicked + titleClick: "titleClick", + + moveFrom: "moveFrom", + moveTo: "moveTo", + addChild: "addChild", + removeNode: "removeNode", + expand: "expand", + collapse: "collapse" + }, + + isContainer: true, + + DNDMode: "off", + + lockLevel: 0, // lock ++ unlock --, so nested locking works fine + + strictFolders: true, + + DNDModes: { + BETWEEN: 1, + ONTO: 2 + }, + + DNDAcceptTypes: "", + + templateCssPath: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/Tree.css"), + + templateString: '
', + + isExpanded: true, // consider this "root node" to be always expanded + + isTree: true, + + objectId: "", + + // autoCreate if not "off" + // used to get the autocreated controller ONLY. + // generally, tree DOES NOT KNOW about its CONTROLLER, it just doesn't care + // controller gets messages via dojo.event + controller: "", + + // autoCreate if not "off" + // used to get the autocreated selector ONLY. + // generally, tree DOES NOT KNOW its SELECTOR + // binding is made with dojo.event + selector: "", + + // used ONLY at initialization time + menu: "", // autobind menu if menu's widgetId is set here + + expandLevel: "", // expand to level automatically + + // + // these icons control the grid and expando buttons for the whole tree + // + + blankIconSrc: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_blank.gif"), + + gridIconSrcT: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_t.gif"), // for non-last child grid + gridIconSrcL: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_l.gif"), // for last child grid + gridIconSrcV: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_v.gif"), // vertical line + gridIconSrcP: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_p.gif"), // for under parent item child icons + gridIconSrcC: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_c.gif"), // for under child item child icons + gridIconSrcX: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_x.gif"), // grid for sole root item + gridIconSrcY: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_y.gif"), // grid for last rrot item + gridIconSrcZ: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_grid_z.gif"), // for under root parent item child icon + + expandIconSrcPlus: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_expand_plus.gif"), + expandIconSrcMinus: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_expand_minus.gif"), + expandIconSrcLoading: dojo.uri.moduleUri("dojo.widget", "templates/images/Tree/treenode_loading.gif"), + + + iconWidth: 18, + iconHeight: 18, + + + // + // tree options + // + + showGrid: true, + showRootGrid: true, + + actionIsDisabled: function(action) { + var _this = this; + return dojo.lang.inArray(_this.actionsDisabled, action) + }, + + + actions: { + ADDCHILD: "ADDCHILD" + }, + + + getInfo: function() { + var info = { + widgetId: this.widgetId, + objectId: this.objectId + } + + return info; + }, + + initializeController: function() { + if (this.controller != "off") { + if (this.controller) { + this.controller = dojo.widget.byId(this.controller); + } + else { + // create default controller here + + this.controller = dojo.widget.createWidget("TreeBasicController", + { DNDController: (this.DNDMode ? "create" : ""), dieWithTree: true } + ); + + } + this.controller.listenTree(this); // controller listens to my events + + } else { + this.controller = null; + } + }, + + initializeSelector: function() { + + if (this.selector != "off") { + if (this.selector) { + this.selector = dojo.widget.byId(this.selector); + } + else { + // create default controller here + + this.selector = dojo.widget.createWidget("TreeSelector", {dieWithTree: true}); + } + + this.selector.listenTree(this); + + } else { + this.selector = null; + } + }, + + initialize: function(args, frag){ + + var _this = this; + + for(name in this.eventNamesDefault) { + if (dojo.lang.isUndefined(this.eventNames[name])) { + this.eventNames[name] = this.widgetId+"/"+this.eventNamesDefault[name]; + } + } + + for(var i=0; i no createDOMNode => no createDOMNode event + domNodeInitialized: child.domNodeInitialized + } + + this.doAddChild.apply(this, arguments); + + dojo.event.topic.publish(this.tree.eventNames.addChild, message); + }, + + + // not called for initial tree building. See createDOMNode instead. + // builds child html node if needed + // index is "last node" by default + /** + * FIXME: Is it possible that removeNode from the tree will cause leaks cause of attached events ? + * if yes, then only attach events in addChild and detach in remove.. Seems all ok yet. + */ + doAddChild: function(child, index){ + + if (dojo.lang.isUndefined(index)) { + index = this.children.length; + } + + if (!child.isTreeNode){ + dojo.raise("You can only add TreeNode widgets to a "+this.widgetType+" widget!"); + return; + } + + // usually it is impossible to change "isFolder" state, but if anyone wants to add a child to leaf, + // it is possible program-way. + if (this.isTreeNode){ + if (!this.isFolder) { // just became a folder. + //dojo.debug("becoming folder "+this); + this.setFolder(); + } + } + + // adjust tree + var _this = this; + dojo.lang.forEach(child.getDescendants(), function(elem) { elem.tree = _this.tree; }); + + // fix parent + child.parent = this; + + + // no dynamic loading for those who become parents + if (this.isTreeNode) { + this.state = this.loadStates.LOADED; + } + + // add new child into DOM after it was added into children + if (index < this.children.length) { // children[] already has child + //dojo.debug("Inserting before "+this.children[index].title); + dojo.html.insertBefore(child.domNode, this.children[index].domNode); + } else { + this.containerNode.appendChild(child.domNode); + if (this.isExpanded && this.isTreeNode) { + /* When I add children to hidden containerNode => show container w/ them */ + this.showChildren(); + } + } + + + this.children.splice(index, 0, child); + + //dojo.debugShallow(this.children); + + + // if node exists - adjust its depth, otherwise build it + if (child.domNodeInitialized) { + var d = this.isTreeNode ? this.depth : -1; + child.adjustDepth( d - child.depth + 1 ); + + + // update icons to link generated dom with Tree => updateParentGrid + // if I moved child from LastNode inside the tree => need to link it up'n'down => + // updateExpandGridColumn + // if I change depth => need to update all grid.. + child.updateIconTree(); + } else { + //dojo.debug("Create domnode "); + child.depth = this.isTreeNode ? this.depth+1 : 0; + child.createDOMNode(child.tree, child.depth); + } + + + + // Use-case: + // When previous sibling was created => it was last, no children after it + // so it did not create link down => let's add it for all descendants + // Use-case: + // a child was moved down under the last node so last node should be updated + var prevSibling = child.getPreviousSibling(); + if (child.isLastChild() && prevSibling) { + prevSibling.updateExpandGridColumn(); + } + + + //dojo.debug("Added child "+child); + + + + }, + + + + + makeBlankImg: function() { + var img = document.createElement('img'); + + img.style.width = this.iconWidth + 'px'; + img.style.height = this.iconHeight + 'px'; + img.src = this.blankIconSrc; + img.style.verticalAlign = 'middle'; + + return img; + }, + + + updateIconTree: function(){ + + //dojo.debug("Update icons for "+this) + if (!this.isTree) { + this.updateIcons(); + } + + for(var i=0; i0) { + children[index-1].updateExpandGridColumn(); + } + // if it WAS first node in WHOLE TREE - + // update link up of its former lower neighbour(if exists still) + if (parent instanceof dojo.widget.Tree && index == 0 && children.length>0) { + children[0].updateExpandGrid(); + } + + //parent.updateIconTree(); + + + child.parent = child.tree = null; + + return child; + }, + + markLoading: function() { + // no way to mark tree loading + }, + + unMarkLoading: function() { + // no way to show that tree finished loading + }, + + + lock: function() { + !this.lockLevel && this.markLoading(); + this.lockLevel++; + }, + unlock: function() { + if (!this.lockLevel) { + dojo.raise("unlock: not locked"); + } + this.lockLevel--; + !this.lockLevel && this.unMarkLoading(); + }, + + isLocked: function() { + var node = this; + while (true) { + if (node.lockLevel) { + return true; + } + if (node instanceof dojo.widget.Tree) { + break; + } + node = node.parent; + } + + return false; + }, + + flushLock: function() { + this.lockLevel = 0; + this.unMarkLoading(); + } +}); + + + +dojo.provide("struts.widget.StrutsTree"); + + + +dojo.widget.defineWidget( + "struts.widget.StrutsTree", + dojo.widget.Tree, { + widgetType : "StrutsTree", + + href : "", + errorNotifyTopics : "", + errorNotifyTopicsArray : null, + + postCreate : function() { + struts.widget.StrutsTree.superclass.postCreate.apply(this); + + //error topics + if(!dojo.string.isBlank(this.errorNotifyTopics)) { + this.errorNotifyTopicsArray = this.errorNotifyTopics.split(","); + } + + var self = this; + if(!dojo.string.isBlank(this.href)) { + dojo.io.bind({ + url: this.href, + useCache: false, + preventCache: true, + handler: function(type, data, e) { + if(type == 'load') { + //data should be an array + if(data) { + dojo.lang.forEach(data, function(descr) { + //create node for eachd descriptor + var newNode = dojo.widget.createWidget("struts:StrutsTreeNode",{ + title : descr.label, + isFolder: descr.hasChildren, + widgetId: descr.id + }); + self.addChild(newNode); + }); + } + } else { + //publish error topics + if(self.errorNotifyTopicsArray) { + dojo.lang.forEach(self.errorNotifyTopicsArray, function(topic) { + try { + dojo.event.topic.publish(topic, data, e, self); + } catch(ex){ + dojo.debug(ex); + } + }); + } + } + }, + mimetype: "text/json" + }); + } + } +}); + dojo.kwCompoundRequire({ common: ["struts.widget.Bind", "struts.widget.BindDiv", @@ -28840,7 +30923,9 @@ dojo.kwCompoundRequire({ "struts.widget.StrutsDatePicker", "struts.widget.BindEvent", "struts.widget.StrutsTreeSelector", - "struts.widget.StrutsTabContainer"] + "struts.widget.StrutsTabContainer", + "struts.widget.StrutsTreeNode", + "struts.widget.StrutsTree"] }); dojo.provide("struts.widget.*"); diff --git a/plugins/dojo/src/main/resources/template/ajax/tree.ftl b/plugins/dojo/src/main/resources/template/ajax/tree.ftl index 2100cc1de..9dea78789 100644 --- a/plugins/dojo/src/main/resources/template/ajax/tree.ftl +++ b/plugins/dojo/src/main/resources/template/ajax/tree.ftl @@ -21,7 +21,13 @@ > -
+ href="${parameters.href}" + + <#if parameters.errorNotifyTopics?if_exists != ""> + errorNotifyTopics="${parameters.errorNotifyTopics?html}"<#rt/> + <#if parameters.blankIconSrc?exists> gridIconSrcT="<@s.url value='${parameters.blankIconSrc}' encode="false" includeParams='none'/>" @@ -77,15 +83,12 @@ || parameters.collapsedNotifyTopics?exists> selector="treeSelector_${parameters.id?default("")}" - <#if parameters.treeCollapsedTopic?exists> - publishCollapsedTopic="${parameters.treeCollapsedTopic?html}" - <#if parameters.toggle?exists> toggle="${parameters.toggle?html}" > <#if parameters.label?exists> -
id="${stack.findValue(parameters.nodeIdProperty)}" <#else> diff --git a/plugins/dojo/src/main/resources/template/ajax/treenode-include.ftl b/plugins/dojo/src/main/resources/template/ajax/treenode-include.ftl index 02c2d1d75..e594e19a6 100644 --- a/plugins/dojo/src/main/resources/template/ajax/treenode-include.ftl +++ b/plugins/dojo/src/main/resources/template/ajax/treenode-include.ftl @@ -1,4 +1,4 @@ -
+
<#list stack.findValue(parameters.childCollectionProperty.toString()) as child> ${stack.push(child)} <#include "/${parameters.templateDir}/ajax/treenode-include.ftl" /> diff --git a/plugins/dojo/src/main/resources/template/ajax/treenode.ftl b/plugins/dojo/src/main/resources/template/ajax/treenode.ftl index 858ed45e0..bffa9c606 100644 --- a/plugins/dojo/src/main/resources/template/ajax/treenode.ftl +++ b/plugins/dojo/src/main/resources/template/ajax/treenode.ftl @@ -1,4 +1,4 @@ -
childIconSrc="<@s.url value='${parameters.childIconSrc}' includeParams='none' encode='false' />" diff --git a/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-1.txt b/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-1.txt index eadb67ce2..302cf16e2 100644 --- a/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-1.txt +++ b/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-1.txt @@ -6,36 +6,36 @@ // dojo.hostenv.writeIncludes(); --> -
-
-
-
-
-
-
-
diff --git a/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-2.txt b/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-2.txt index f64c2b601..a7a871bb3 100644 --- a/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-2.txt +++ b/plugins/dojo/src/test/resources/org/apache/struts2/dojo/views/jsp/ui/tree-2.txt @@ -6,58 +6,58 @@ // dojo.hostenv.writeIncludes(); --> -
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+