1 /**
  2  * Copyright (C) 2005-2010 Alfresco Software Limited.
  3  *
  4  * This file is part of Alfresco
  5  *
  6  * Alfresco is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU Lesser General Public License as published by
  8  * the Free Software Foundation, either version 3 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * Alfresco is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU Lesser General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU Lesser General Public License
 17  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 18  */
 19  
 20 /**
 21  * DocumentList TreeView component.
 22  * 
 23  * @namespace Alfresco
 24  * @class Alfresco.DocListTree
 25  */
 26 (function()
 27 {
 28    /**
 29     * YUI Library aliases
 30     */
 31    var Dom = YAHOO.util.Dom,
 32       Event = YAHOO.util.Event;
 33 
 34    /**
 35     * Alfresco Slingshot aliases
 36     */
 37     var $combine = Alfresco.util.combinePaths;
 38 
 39    /**
 40     * DocumentList TreeView constructor.
 41     * 
 42     * @param {String} htmlId The HTML id of the parent element
 43     * @return {Alfresco.DocListTree} The new DocListTree instance
 44     * @constructor
 45     */
 46    Alfresco.DocListTree = function DLT_constructor(htmlId)
 47    {
 48       Alfresco.DocListTree.superclass.constructor.call(this, "Alfresco.DocListTree", htmlId, ["treeview", "json"]);
 49 
 50       // Path filterId
 51       this.filterId = "path";
 52       
 53       // Register with Filter Manager
 54       Alfresco.util.FilterManager.register(this.name, this.filterId);
 55       
 56       // Initialise prototype properties
 57       this.currentFilter = {};
 58       this.pathsToExpand = [];
 59 
 60       // Decoupled event listeners
 61       YAHOO.Bubbling.on("folderCopied", this.onFolderCopied, this);
 62       YAHOO.Bubbling.on("folderCreated", this.onFolderCreated, this);
 63       YAHOO.Bubbling.on("folderDeleted", this.onFolderDeleted, this);
 64       YAHOO.Bubbling.on("folderMoved", this.onFolderMoved, this);
 65       YAHOO.Bubbling.on("folderRenamed", this.onFolderRenamed, this);
 66       YAHOO.Bubbling.on("filterChanged", this.onFilterChanged, this);
 67       YAHOO.Bubbling.on("dropTargetOwnerRequest", this.onDropTargetOwnerRequest, this);
 68       YAHOO.Bubbling.on("documentDragOver", this.onDocumentDragOver, this);
 69       YAHOO.Bubbling.on("documentDragOut", this.onDocumentDragOut, this);
 70 
 71       return this;
 72    };
 73    
 74    YAHOO.extend(Alfresco.DocListTree, Alfresco.component.Base,
 75    {
 76       /**
 77        * Object container for initialization options
 78        */
 79       options:
 80       {
 81          /**
 82           * Current siteId.
 83           * 
 84           * @property siteId
 85           * @type string
 86           */
 87          siteId: "",
 88 
 89          /**
 90           * ContainerId representing root container
 91           *
 92           * @property containerId
 93           * @type string
 94           * @default "documentLibrary"
 95           */
 96          containerId: "documentLibrary",
 97          
 98          /**
 99           * Evaluate child folders flag
100           *
101           * @property evaluateChildFolders
102           * @type boolean
103           * @default true
104           */
105          evaluateChildFolders: true,
106 
107          /**
108           * Maximum folder count configuration setting
109           *
110           * @property maximumFolderCount
111           * @type int
112           * @default -1
113           */
114          maximumFolderCount: -1,
115          
116          /**
117           * Indicates whether or not to set each tree node as a YUI Drag and Drop
118           * target.
119           * 
120           * @property setDropTargets
121           * @type boolean
122           * @default false
123           */
124          setDropTargets: false
125       },
126       
127       /**
128        * Flag set after TreeView instantiated.
129        * 
130        * @property isReady
131        * @type boolean
132        */
133       isReady: false,
134 
135       /**
136        * Initial filter on page load.
137        * 
138        * @property initialFilter
139        * @type string
140        */
141       initialFilter: null,
142 
143       /**
144        * Current path being browsed.
145        * 
146        * @property currentPath
147        * @type string
148        */
149       currentPath: "",
150 
151       /**
152        * Currently active filter
153        * 
154        * @property currentFilter
155        * @type object
156        */
157        currentFilter: null,
158 
159       /**
160        * Tracks if this component is the active filter owner.
161        * 
162        * @property isFilterOwner
163        * @type boolean
164        */
165       isFilterOwner: false,
166 
167       /**
168        * Paths we have to expand as a result of a deep navigation event.
169        * 
170        * @property pathsToExpand
171        * @type array
172        */
173       pathsToExpand: null,
174 
175       /**
176        * Selected tree node.
177        * 
178        * @property selectedNode
179        * @type {YAHOO.widget.Node}
180        */
181       selectedNode: null,
182 
183       /**
184        * Fired by YUI when parent element is available for scripting
185        * @method onReady
186        */
187       onReady: function DLT_onReady()
188       {
189          // Reference to self - used in inline functions
190          var me = this;
191          
192          // Create twister from our H2 tag
193          Alfresco.util.createTwister(this.id + "-h2", this.name.substring(this.name.lastIndexOf(".") + 1));
194          
195          /**
196           * Dynamically loads TreeView nodes.
197           * This MUST be inline in order to have access to the Alfresco.DocListTree class.
198           * @method fnLoadNodeData
199           * @param node {object} Parent node
200           * @param fnLoadComplete {function} Expanding node's callback function
201           */
202          this.fnLoadNodeData = function DLT_oR_fnLoadNodeData(node, fnLoadComplete)
203          {
204             // Get the path this node refers to
205             var nodePath = node.data.path;
206 
207             // Prepare URI for XHR data request
208             var uri = me._buildTreeNodeUrl.call(me, nodePath);
209 
210             // Prepare the XHR callback object
211             var callback =
212             {
213                success: function DLT_lND_success(oResponse)
214                {
215                   var results = YAHOO.lang.JSON.parse(oResponse.responseText), item, treeNode;
216                   
217                   // Update parent node's nodeRef if we didn't have it before
218                   if (results.parent && node.data.nodeRef.length === 0)
219                   {
220                      node.data.nodeRef = results.parent.nodeRef;
221                   }
222 
223                   if (results.items)
224                   {
225                      for (var i = 0, j = results.items.length; i < j; i++)
226                      {
227                         item = results.items[i];
228                         item.path = $combine(nodePath, item.name);
229                         treeNode = this._buildTreeNode(item, node, false);
230 
231                         if (!item.hasChildren)
232                         {
233                            treeNode.isLeaf = true;
234                         }
235                      }
236                   }
237                   
238                   if (results.resultsTrimmed)
239                   {
240                      tempNode = new YAHOO.widget.TextNode(
241                      {
242                         label: "<" + this.msg("message.folders-trimmed") + ">",
243                         style: "folders-trimmed"
244                      }, node, false);
245                   }
246                   
247                   /**
248                   * Execute the node's loadComplete callback method which comes in via the argument
249                   * in the response object
250                   */
251                   oResponse.argument.fnLoadComplete();
252                },
253 
254                // If the XHR call is not successful, fire the TreeView callback anyway
255                failure: function DLT_lND_failure(oResponse)
256                {
257                   if (oResponse.status == 401)
258                   {
259                      // Our session has likely timed-out, so refresh to offer the login page
260                      window.location.reload();
261                   }
262                   else
263                   {
264                      try
265                      {
266                         var response = YAHOO.lang.JSON.parse(oResponse.responseText);
267                         
268                         // Get the "Documents" node
269                         var rootNode = this.widgets.treeview.getRoot();
270                         var docNode = rootNode.children[0];
271                         docNode.isLoading = false;
272                         docNode.isLeaf = true;
273                         docNode.label = response.message;
274                         docNode.labelStyle = "ygtverror";
275                         rootNode.refresh();
276                      }
277                      catch(e)
278                      {
279                      }
280                   }
281                },
282                
283                // Callback function scope
284                scope: me,
285 
286                // XHR response argument information
287                argument:
288                {
289                   "node": node,
290                   "fnLoadComplete": fnLoadComplete
291                }
292             };
293 
294             // Make the XHR call using Connection Manager's asyncRequest method
295             YAHOO.util.Connect.asyncRequest('GET', uri, callback);
296          };
297 
298          // Build the TreeView widget
299          this._buildTree();
300          
301          this.isReady = true;
302          if (this.initialFilter !== null)
303          {
304             // We weren't ready for the first filterChanged event, so fake it here
305             this.onFilterChanged("filterChanged",
306             [
307                null,
308                this.initialFilter
309             ]);
310          }
311       },
312 
313       /**
314        * Handles "dropTargetOwnerRequest" by determining whether or not the target belongs to the TreeView
315        * widget, and if it does determines it's nodeRef and uses the callback function with it.
316        * 
317        * @method onDropTargetOwnerRequest
318        * @property layer The name of the event
319        * @property args The event payload
320        */
321       onDropTargetOwnerRequest: function DLT_onDropTargetOwnerRequest(layer, args)
322       {
323          if (args && args[1] && args[1].elementId)
324          {
325             var node = this.widgets.treeview.getNodeByElement(Dom.get(args[1].elementId));
326             if (node != null)
327             {
328                // Perform the drag out to clear the highlight...
329                this.onDocumentDragOut(layer, args);
330                
331                var nodeRef = node.data.nodeRef;
332                var path = node.data.path;
333                args[1].callback.call(args[1].scope, nodeRef, path);
334             }
335          }
336       },
337       
338       /**
339        * Handles applying the styling and node creation required when a document is dragged
340        * over a tree node.
341        * 
342        * @method onDocumentDragOver
343        * @property layer The name of the event
344        * @property args The event payload
345        */
346       onDocumentDragOver: function DLTB_onDocumentDragOver(layer, args)
347       {
348          if (args && args[1] && args[1].elementId)
349          {
350             var rootEl = this.widgets.treeview.getEl();
351             if (args[1].event.clientX > rootEl.clientWidth)
352             {
353                // If the current x co-ordinate of the mouse pointer is greater than the width
354                // of the tree element then we shouldn't add a highlight. This is to address
355                // the issue where the overflow of wide tree nodes is hidden behind the 
356                // document list. Without this test it is possible to show a highlight on 
357                // a tree node when it appears as though the mouse is not over it.
358             }
359             else
360             {
361                // The current x co-ordinate of the mouse pointer is within the tree element so
362                // the node can be highlighted...
363                var dropTargetEl = Dom.get(args[1].elementId); 
364                if (dropTargetEl != this.widgets.treeview.getEl())
365                {
366                   var node = this.widgets.treeview.getNodeByElement(dropTargetEl);
367                   if (node != null)
368                   {
369                      var currEl = dropTargetEl;
370                      while (currEl.tagName != "TABLE")
371                      {
372                         currEl = currEl.parentNode;
373                      }
374                      Dom.addClass(currEl, "documentDragOverHighlight");
375                      
376                      var folderCell = dropTargetEl.parentNode.children[dropTargetEl.parentNode.children.length - 2];
377                      while (folderCell.children.length == 1)
378                      {
379                         var arrowSpan = document.createElement("span");
380                         Dom.addClass(arrowSpan, "documentDragOverArrow");
381                         folderCell.appendChild(arrowSpan);
382                      }
383                   }
384                }
385             }
386          }
387       },
388       
389       /**
390        * Handles applying the styling and node deletion required when a document is dragged
391        * out of a tree node.
392        *
393        * @method onDocumentDragOut
394        * @property layer The name of the event
395        * @property args The event payload
396        */
397       onDocumentDragOut: function DLTB_onDocumentDragOut(layer, args)
398       {
399          if (args && args[1] && args[1].elementId)
400          {
401             var dropTargetEl = Dom.get(args[1].elementId); 
402             if (dropTargetEl == this.widgets.treeview.getEl())
403             {
404                // If the document has been dragged out of the tree element then we need 
405                // to remove any highlight and arrow from previously highlighted tree nodes.
406                // This would be the case if the highlighted tree node is wider than the 
407                // tree element and the mouse has moved to the right of the splitter so is
408                // outside of the tree but still over the tree node...
409                var highlights = Dom.getElementsByClassName("documentDragOverHighlight", "table", dropTargetEl); // Should be only one
410                for (var i = 0, j = highlights.length; i < j ; i++)
411                {
412                   Dom.removeClass(highlights[i], "documentDragOverHighlight");
413                }
414                var arrows = Dom.getElementsByClassName("documentDragOverArrow", "span", dropTargetEl);
415                for (var i = 0, j = arrows.length; i < j ; i++)
416                {
417                   arrows[i].parentNode.removeChild(arrows[i]);
418                }
419             }
420             else
421             {
422                // If the document has been dragged out of a tree node then we need to 
423                // remove the highlight and arrow previously added when the document was
424                // dragged over it...
425                var node = this.widgets.treeview.getNodeByElement(dropTargetEl);
426                if (node != null)
427                {
428                   var currEl = dropTargetEl;
429                   while (currEl.tagName != "TABLE")
430                   {
431                      currEl = currEl.parentNode;
432                   }
433                   Dom.removeClass(currEl, "documentDragOverHighlight");
434                   var folderCell = dropTargetEl.parentNode.children[dropTargetEl.parentNode.children.length - 2];
435                   while (folderCell.children.length > 1)
436                   {
437                      folderCell.removeChild(Dom.getLastChild(folderCell));
438                   }
439                }
440             }
441          }
442       },
443       
444       /**
445        * Fired by YUI TreeView when a node has finished expanding
446        * @method onExpandComplete
447        * @param oNode {YAHOO.widget.Node} the node recently expanded
448        */
449       onExpandComplete: function DLT_onExpandComplete(oNode)
450       {
451          // Make sure the tree's Dom has been updated
452          this.widgets.treeview.render();
453          
454          // Redrawing the tree will clear the highlight
455          if (this.isFilterOwner)
456          {
457             this._showHighlight(true);
458          }
459          
460          if (this.pathsToExpand && this.pathsToExpand.length > 0)
461          {
462             var node = this.widgets.treeview.getNodeByProperty("path", this.pathsToExpand.shift());
463             if (node !== null)
464             {
465                if (node.data.path == this.currentPath)
466                {
467                   this._updateSelectedNode(node);
468                }
469                Alfresco.logger.debug("node.expand: DLT_onExpandComplete");
470                node.expand();
471             }
472          }
473          else if (this.initialFilter !== null)
474          {
475             // We missed the filterChanged event, so fake it here
476             this.onFilterChanged("filterChanged",
477             [
478                null,
479                {
480                   filterId: this.initialFilter.filterId,
481                   filterOwner: this.initialFilter.filterOwner,
482                   filterData: this.initialFilter.filterData
483                }
484             ]);
485             this.initialFilter = null;
486          }
487          else
488          {
489             // Finished expanding, can now safely set DND targets...
490             this._applyDropTargets();
491          }
492       },
493 
494       /**
495        * Creates the drag and drop targets within the tree. The targets get removed
496        * each time that the tree is refreshed in anyway, so it is imperative that they
497        * get reset when required.
498        * 
499        * @method _applyDropTargets
500        */
501       _applyDropTargets: function DLT__applyDropTargets()
502       {
503          if (this.options.setDropTargets)
504          {
505             var rootEl = this.widgets.treeview.getEl();
506             
507             // Set the root element of the tree as a drop target. This is necessary in order
508             // to handle the specific problem of the hidden overflow of tree nodes being at
509             // the same location of the screen as the main DocumentList drop targets. Drop events
510             // will be ignored for this element, but dragOut events will be used to ensure that
511             // all tree highlights are cleared.
512             new YAHOO.util.DDTarget(rootEl);
513             Dom.addClass(rootEl, "documentDroppableHighlights");
514             
515             var dndTargets = Dom.getElementsByClassName("ygtvcell", "td", rootEl);
516             for (var i = 0, j = dndTargets.length; i < j; i++)
517             {
518                new YAHOO.util.DDTarget(dndTargets[i]);
519                Dom.addClass(dndTargets[i], "documentDroppable");
520                Dom.addClass(dndTargets[i], "documentDroppableHighlights");
521             }
522          }
523       },
524          
525       /**
526        * Fired by YUI TreeView when a node label is clicked
527        * @method onNodeClicked
528        * @param args.event {HTML Event} the event object
529        * @param args.node {YAHOO.widget.Node} the node clicked
530        * @return allowExpand {boolean} allow or disallow node expansion
531        */
532       onNodeClicked: function DLT_onNodeClicked(args)
533       {
534          var node = args.node;
535          
536          if (this.isFilterOwner && node == this.selectedNode)
537          {
538             YAHOO.Bubbling.fire("metadataRefresh");
539          }
540          else
541          {
542             this._updateSelectedNode(node);
543 
544             // Fire the change filter event
545             YAHOO.Bubbling.fire("changeFilter",
546             {
547                filterOwner: this.name,
548                filterId: this.filterId,
549                filterData: node.data.path
550             });
551          }
552 
553          Event.stopEvent(args.event);
554          // Prevent the tree node from expanding (TODO: user preference?)
555          return false;
556       },
557 
558       /**
559        * Path changed handler
560        * @method pathChanged
561        * @param path {string} New path
562        * @param flags {object} Logic control flags
563        */
564       pathChanged: function DLT_pathChanged(path, flags)
565       {
566          // ensure path starts with leading slash
567          path = $combine("/", path);
568          this.currentPath = path;
569 
570          // Search the tree to see if this path's node is expanded
571          var node = this.widgets.treeview.getNodeByProperty("path", path);
572          if (node !== null)
573          {
574             // Node found
575             this._updateSelectedNode(node);
576             if (!node.expanded)
577             {
578                Alfresco.logger.debug("node.expand: DLT_pathChanged", path);
579                node.expand();
580             }
581             while (node.parent !== null)
582             {
583                node = node.parent;
584                if (!node.expanded)
585                {
586                   Alfresco.logger.debug("node.expand: DLT_onPathChanged (parent)", path);
587                   node.expand();
588                }
589             }
590             return;
591          }
592          
593          /**
594           * The path's node hasn't been loaded into the tree. Create a stack
595           * of parent paths that we need to expand one-by-one in order to
596           * eventually display the current path's node
597           */
598          var paths = path.split("/"),
599             expandPath = "/";
600          // Check for root path special case (split will have created 2 empty array members)
601          if (path === "/")
602          {
603             paths = [""];
604          }
605          for (var i = 0; i < paths.length; i++)
606          {
607             // Push the path onto the list of paths to be expanded
608             expandPath = $combine(expandPath, paths[i]);
609             this.pathsToExpand.push(expandPath);
610          }
611          
612          // Kick off the expansion process by expanding the first unexpanded path
613          do
614          {
615             node = this.widgets.treeview.getNodeByProperty("path", this.pathsToExpand.shift());
616          } while (this.pathsToExpand.length > 0 && node && node.expanded);
617          
618          if (node !== null)
619          {
620             Alfresco.logger.debug("node.expand: DLT_onPathChanged (pathsToExpand)", this.pathsToExpand);
621             node.expand();
622          }
623       },
624 
625 
626       /**
627        * BUBBLING LIBRARY EVENT HANDLERS FOR PAGE EVENTS
628        * Disconnected event handlers for inter-component event notification
629        */
630       
631       /**
632        * Fired when a folder has been renamed
633        * @method onFolderRenamed
634        * @param layer {string} the event source
635        * @param args {object} arguments object
636        */
637       onFolderRenamed: function DLT_onFolderRenamed(layer, args)
638       {
639          var obj = args[1];
640          if (obj && (obj.file !== null))
641          {
642             var node = this.widgets.treeview.getNodeByProperty("nodeRef", obj.file.node.nodeRef);
643             if (node !== null)
644             {
645                // Node found, so rename it
646                node.label = obj.file.displayName;
647                node.data.path = $combine(obj.file.location.path, obj.file.location.file);
648                this.widgets.treeview.render();
649                this._showHighlight(true);
650             }
651          }
652          
653          // Make sure that the drag and drop targets are correctly set...
654          this._applyDropTargets();
655       },
656 
657       /**
658        * Fired when a folder has been copied
659        *
660        * Event data contains:
661        *    nodeRef - the nodeRef of the newly copied object
662        *    destination - new parent path
663        * @method onFolderCopied
664        * @param layer {string} the event source
665        * @param args {object} arguments object
666        */
667       onFolderCopied: function DLT_onFolderCopied(layer, args)
668       {
669          var obj = args[1];
670          if (obj !== null)
671          {
672             if (obj.nodeRef && obj.destination)
673             {
674                // Do we have the parent of the node's copy loaded?
675                var nodeDest = this.widgets.treeview.getNodeByProperty("path", $combine("/", obj.destination));
676                if (nodeDest)
677                {
678                   if (nodeDest.expanded)
679                   {
680                      this._sortNodeChildren(nodeDest);
681                   }
682                   else
683                   {
684                      nodeDest.isLeaf = false;
685                   }
686                }
687                
688                this.widgets.treeview.render();
689                this._showHighlight(true);
690             }
691          }
692          
693          // Make sure that the drag and drop targets are correctly set...
694          this._applyDropTargets();
695       },
696 
697       /**
698        * Fired when a folder has been created
699        * @method onFolderCreated
700        * @param layer {string} the event source
701        * @param args {object} arguments object
702        */
703       onFolderCreated: function DLT_onFolderCreated(layer, args)
704       {
705          var obj = args[1];
706          if (obj && (obj.parentNodeRef !== null))
707          {
708             var parentNode = this.widgets.treeview.getNodeByProperty("nodeRef", obj.parentNodeRef);
709             if (parentNode !== null)
710             {
711                this._sortNodeChildren(parentNode);
712             }
713          }
714          
715          // Make sure that the drag and drop targets are correctly set...
716          this._applyDropTargets();
717 
718       },
719 
720       /**
721        * Fired when a folder has been deleted
722        * @method onFolderDeleted
723        * @param layer {string} the event source
724        * @param args {object} arguments object
725        */
726       onFolderDeleted: function DLT_onFolderDeleted(layer, args)
727       {
728          var obj = args[1];
729          if (obj !== null)
730          {
731             var node = null;
732             
733             if (obj.path)
734             {
735                // ensure path starts with leading slash
736                node = this.widgets.treeview.getNodeByProperty("path", $combine("/", obj.path));
737             }
738             else if (obj.nodeRef)
739             {
740                node = this.widgets.treeview.getNodeByProperty("nodeRef", obj.nodeRef);
741             }
742             
743             if (node !== null)
744             {
745                var parentNode = node.parent;
746                // Node found, so delete it
747                this.widgets.treeview.removeNode(node);
748                // Have all the parent child nodes been removed now?
749                if (parentNode !== null)
750                {
751                   if (!parentNode.hasChildren())
752                   {
753                      parentNode.isLeaf = true;
754                   }
755                }
756                this.widgets.treeview.render();
757                this._showHighlight(true);
758             }
759          }
760          
761          // Make sure that the drag and drop targets are correctly set...
762          this._applyDropTargets();
763       },
764 
765       /**
766        * Fired when a folder has been moved
767        *
768        * Event data contains:
769        *    nodeRef - the nodeRef of the moved object
770        *    destination - new parent path
771        * @method onFolderMoved
772        * @param layer {string} the event source
773        * @param args {object} arguments object
774        */
775       onFolderMoved: function DLT_onFolderMoved(layer, args)
776       {
777          var obj = args[1];
778          if (obj !== null)
779          {
780             if (typeof obj.nodeRef !== "undefined" && typeof obj.destination !== "undefined")
781             {
782                var nodeSrc = null;
783                
784                // we should be able to find the original node
785                nodeSrc = this.widgets.treeview.getNodeByProperty("nodeRef", obj.nodeRef);
786             
787                if (nodeSrc !== null)
788                {
789                   var parentNode = nodeSrc.parent;
790                   // Node found, so delete it
791                   this.widgets.treeview.removeNode(nodeSrc, true);
792                   // Have all the parent's child nodes been removed now?
793                   if (parentNode !== null)
794                   {
795                      if (!parentNode.hasChildren())
796                      {
797                         parentNode.isLeaf = true;
798                      }
799                   }
800                   // Do we have the node's new parent loaded?
801                   var nodeDest = this.widgets.treeview.getNodeByProperty("path", $combine("/", obj.destination));
802                   if (nodeDest)
803                   {
804                      // The node may already be loading if this was a multiple-folder move
805                      if (!nodeDest.isLoading)
806                      {
807                         if (nodeDest.isLeaf)
808                         {
809                            nodeDest.isLeaf = false;
810                         }
811                         else if (nodeDest.expanded)
812                         {
813                            this._sortNodeChildren(nodeDest);
814                         }
815                         this.widgets.treeview.render();
816                         this._showHighlight(true);
817                      }
818                   }
819                }
820             }
821          }
822          
823          // Make sure that the drag and drop targets are correctly set...
824          this._applyDropTargets();
825       },
826 
827       /**
828        * Fired when the currently active filter has changed
829        * @method onFilterChanged
830        * @param layer {string} the event source
831        * @param args {object} arguments object
832        */
833       onFilterChanged: function DLT_onFilterChanged(layer, args)
834       {
835          var obj = args[1];
836          if ((obj !== null) && (obj.filterId !== null))
837          {
838             obj.filterOwner = obj.filterOwner || Alfresco.util.FilterManager.getOwner(obj.filterId);
839             
840             // Defer if event received before we're ready
841             if (!this.isReady)
842             {
843                this.initialFilter = Alfresco.util.cleanBubblingObject(obj);
844                Alfresco.logger.debug("DLT_onFilterChanged (deferring)", this.initialFilter);
845                return;
846             }
847             
848             Alfresco.logger.debug("DLT_onFilterChanged", obj);
849             this.initialFilter = null;
850             
851             this.currentFilter = Alfresco.util.cleanBubblingObject(obj);
852             this.isFilterOwner = (obj.filterOwner == this.name);
853             if (this.isFilterOwner)
854             {
855                this.pathChanged(this.currentFilter.filterData, obj);
856             }
857             this._showHighlight(this.isFilterOwner);
858          }
859       },
860 
861 
862       /**
863        * PRIVATE FUNCTIONS
864        */
865 
866       /**
867        * Creates the TreeView control and renders it to the parent element.
868        * @method _buildTree
869        * @private
870        */
871       _buildTree: function DLT__buildTree()
872       {
873          // Create a new tree
874          var tree = new YAHOO.widget.TreeView(this.id + "-treeview");
875          this.widgets.treeview = tree;
876          
877          // Having both focus and highlight are just confusing (YUI 2.7.0 addition)
878          YAHOO.widget.TreeView.FOCUS_CLASS_NAME = "";
879 
880          // Turn dynamic loading on for entire tree
881          tree.setDynamicLoad(this.fnLoadNodeData);
882 
883          // Get root node for tree
884          var root = tree.getRoot();
885 
886          // Add default top-level node
887          this._buildTreeNode(
888          {
889             name: Alfresco.util.message("node.root", this.name),
890             path: "/",
891             nodeRef: ""
892          }, root, false);
893 
894          // Register tree-level listeners
895          tree.subscribe("clickEvent", this.onNodeClicked, this, true);
896          tree.subscribe("expandComplete", this.onExpandComplete, this, true);
897 
898          // Render tree with this one top-level node
899          tree.render();
900       },
901       
902       /**
903        * @method _sortNodeChildren
904        * @param node {object} Parent node
905        * @param onSortComplete {object} Optional callback object literal
906        * @private
907        */
908       _sortNodeChildren: function DLT__sortNodeChildren(node, onSortComplete)
909       {
910          // Is the node a leaf?
911          if (node.isLeaf)
912          {
913             // Yes, so clearing the leaf flag and redrawing will automatically query the child nodes
914             node.isLeaf = false;
915             this.widgets.treeview.render();
916             this._showHighlight(true);
917             return;
918          }
919          
920          // Get the path this node refers to
921          var nodePath = node.data.path;
922 
923          // Prepare URI for XHR data request
924          var uri = this._buildTreeNodeUrl(nodePath);
925 
926          // Prepare the XHR callback object
927          var callback =
928          {
929             success: function DLT_sNC_success(oResponse)
930             {
931                var results = YAHOO.lang.JSON.parse(oResponse.responseText);
932 
933                if (results.items)
934                {
935                   var kids = oResponse.argument.node.children;
936                   var items = results.items;
937                   for (var i = 0, j = items.length; i < j; i++)
938                   {
939                      if ((kids.length <= i) || (kids[i].data.nodeRef != items[i].nodeRef))
940                      {
941                         // Node has moved - search for correct node for this position and swap if found
942                         var kidFound = false;
943                         for (var m = i, n = kids.length; m < n; m++)
944                         {
945                            if (kids[m].data.nodeRef == items[i].nodeRef)
946                            {
947                               var temp = kids[i];
948                               kids[i] = kids[m];
949                               kids[m] = temp;
950                               kidFound = true;
951                               break;
952                            }
953                         }
954                            
955                         // If we get here we couldn't find the node, so create one and insert it
956                         if (!kidFound)
957                         {
958                            var item = items[i];
959                            item.path = $combine(oResponse.argument.node.data.path, item.name);
960                            var tempNode = this._buildTreeNode(item);
961 
962                            if (!item.hasChildren)
963                            {
964                               tempNode.isLeaf = true;
965                            }
966                            
967                            if (kids.length === 0)
968                            {
969                               var parentNode = oResponse.argument.node;
970                               parentNode.isLeaf = false;
971                               tempNode.appendTo(parentNode);
972                            }
973                            else if (kids.length > i)
974                            {
975                               tempNode.insertBefore(kids[i]);
976                            }
977                            else
978                            {
979                               tempNode.insertAfter(kids[kids.length - 1]);
980                            }
981                         }
982                      }
983                   }
984                   
985                   // Update the tree
986                   this.widgets.treeview.render();
987                   this._showHighlight(true);
988                   
989                   // Execute the onSortComplete callback
990                   var callback = oResponse.argument.onSortComplete;
991                   if (callback && typeof callback.fn == "function")
992                   {
993                      callback.fn.call(callback.scope ? callback.scope : this, callback.obj);
994                   }
995                }
996             },
997 
998             // If the XHR call is not successful, no further processing - tree may not be sorted correctly
999             failure: function DLT_sNC_failure(oResponse)
1000             {
1001                Alfresco.logger.error("DLT_sNC_failure", oResponse);
1002             },
1003 
1004             // XHR response argument information
1005             argument:
1006             {
1007                node: node,
1008                onSortComplete: onSortComplete
1009             },
1010             
1011             scope: this,
1012 
1013             // Timeout -- abort the transaction after 7 seconds
1014             timeout: 7000
1015          };
1016 
1017          // Make the XHR call using Connection Manager's asyncRequest method
1018          YAHOO.util.Connect.asyncRequest('GET', uri, callback);
1019       },
1020 
1021       /**
1022        * Highlights the currently selected node.
1023        * @method _showHighlight
1024        * @param isVisible {boolean} Whether the highlight is visible or not
1025        * @private
1026        */
1027       _showHighlight: function DLT__showHighlight(isVisible)
1028       {
1029          if (this.selectedNode !== null)
1030          {
1031             if (isVisible)
1032             {
1033                Dom.addClass(this.selectedNode.getEl(), "selected");
1034             }
1035             else
1036             {
1037                Dom.removeClass(this.selectedNode.getEl(), "selected");
1038             }
1039          }
1040       },
1041       
1042       /**
1043        * Updates the currently selected node.
1044        * @method _updateSelectedNode
1045        * @param node {object} New node to set as currently selected one
1046        * @private
1047        */
1048       _updateSelectedNode: function DLT__updateSelectedNode(node)
1049       {
1050          if (this.isFilterOwner)
1051          {
1052             this._showHighlight(false);
1053             this.selectedNode = node;
1054             this._showHighlight(true);
1055          }
1056          else
1057          {
1058             this.selectedNode = node;
1059          }
1060       },
1061 
1062       /**
1063        * Build a tree node using passed-in data
1064        *
1065        * @method _buildTreeNode
1066        * @param p_oData {object} Object literal containing required data for new node
1067        * @param p_oParent {object} Optional parent node
1068        * @param p_expanded {object} Optional expanded/collaped state flag
1069        * @return {YAHOO.widget.TextNode} The new tree node
1070        */
1071       _buildTreeNode: function DLT__buildTreeNode(p_oData, p_oParent, p_expanded)
1072       {
1073          return new YAHOO.widget.TextNode(
1074          {
1075             label: p_oData.name,
1076             path: p_oData.path,
1077             nodeRef: p_oData.nodeRef,
1078             description: p_oData.description
1079          }, p_oParent, p_expanded);
1080       },
1081 
1082       /**
1083        * Build URI parameter string for treenode JSON data webscript
1084        *
1085        * @method _buildTreeNodeUrl
1086        * @param path {string} Path to query
1087        */
1088        _buildTreeNodeUrl: function DLT__buildTreeNodeUrl(path)
1089        {
1090           var uriTemplate ="slingshot/doclib/treenode/site/" + $combine(encodeURIComponent(this.options.siteId), encodeURIComponent(this.options.containerId), Alfresco.util.encodeURIPath(path));
1091           uriTemplate += "?perms=false&children=" + this.options.evaluateChildFolders + "&max=" + this.options.maximumFolderCount;
1092           return  Alfresco.constants.PROXY_URI + uriTemplate;
1093        }
1094    });
1095 })();
1096