1 /**
  2  * Copyright (C) 2010-2011 Share Extras Contributors.
  3  *
  4  */
  5 
  6 /**
  7  * ConsoleNodeBrowser tool component.
  8  * 
  9  * @namespace Alfresco
 10  * @class Alfresco.ConsoleNodeBrowser
 11  * @author wabson
 12  */
 13 (function()
 14 {
 15    /**
 16     * YUI Library aliases
 17     */
 18    var Dom = YAHOO.util.Dom,
 19        Event = YAHOO.util.Event,
 20        Element = YAHOO.util.Element;
 21    
 22    /**
 23     * Alfresco Slingshot aliases
 24     */
 25    var $html = Alfresco.util.encodeHTML;
 26 
 27    /**
 28     * ConsoleNodeBrowser constructor.
 29     * 
 30     * @param {String} htmlId The HTML id of the parent element
 31     * @return {Alfresco.ConsoleNodeBrowser} The new ConsoleNodeBrowser instance
 32     * @constructor
 33     */
 34    Alfresco.ConsoleNodeBrowser = function(htmlId)
 35    {
 36       this.name = "Alfresco.ConsoleNodeBrowser";
 37       Alfresco.ConsoleNodeBrowser.superclass.constructor.call(this, htmlId);
 38       
 39       /* Register this component */
 40       Alfresco.util.ComponentManager.register(this);
 41 
 42       /* Load YUI Components */
 43       Alfresco.util.YUILoaderHelper.require(["button", "container", "datasource", "datatable", "json", "history"], this.onComponentsLoaded, this);
 44 
 45       /* Decoupled event listeners */
 46       YAHOO.Bubbling.on("viewNodeClick", this.onViewNodeClick, this);
 47 
 48       /* Define panel handlers */
 49       var parent = this;
 50       
 51       // NOTE: the panel registered first is considered the "default" view and is displayed first
 52       
 53       /* Search Panel Handler */
 54       SearchPanelHandler = function ConsoleNodeBrowser_SearchPanelHandler_constructor()
 55       {
 56          SearchPanelHandler.superclass.constructor.call(this, "search");
 57       };
 58       
 59       YAHOO.extend(SearchPanelHandler, Alfresco.ConsolePanelHandler,
 60       {
 61 
 62          /**
 63           * INSTANCE VARIABLES
 64           */
 65 
 66          /**
 67           * Keeps track if this panel is searching or not
 68           *
 69           * @property isSearching
 70           * @type Boolean
 71           */
 72          isSearching: false,
 73 
 74          /**
 75           * PANEL LIFECYCLE CALLBACKS
 76           */
 77          
 78          /**
 79           * Called by the ConsolePanelHandler when this panel shall be loaded
 80           *
 81           * @method onLoad
 82           */
 83          onLoad: function ConsoleNodeBrowser_SearchPanelHandler_onLoad()
 84          {
 85              // selectedMenuItemChange handler to make menu buttons act like select lists
 86              // shared by store menu and language menu
 87              var onSelectedMenuItemChange = function (event) {
 88                  var oMenuItem = event.newValue;
 89                  this.set("label", (oMenuItem.cfg.getProperty("text")));
 90                  this.set("value", (oMenuItem.cfg.getProperty("text")));
 91                  return;
 92             };
 93             
 94             var onMenuClick = function (p_sType, p_aArgs) {
 95                 var oEvent = p_aArgs[0],    //  DOM event
 96                     oMenuItem = p_aArgs[1]; //  MenuItem instance that was the target of the event
 97                 
 98                 if (oMenuItem)
 99                 {
100                     this.set("label", (oMenuItem.cfg.getProperty("text")));
101                     this.set("value", (oMenuItem.cfg.getProperty("text")));
102                 }
103             };
104             
105             var onMenuRender = function (type, args, button) {
106                 button.set("selectedMenuItem", this.getItem(0));
107             };
108             
109             // Search button
110             parent.widgets.searchButton = Alfresco.util.createYUIButton(parent, "search-button", parent.onSearchClick);
111             
112             // Store menu button
113             Alfresco.util.Ajax.request({
114                 url: Alfresco.constants.PROXY_URI + "slingshot/node/stores",
115                 successCallback:
116                 {
117                    fn: function(p_obj) {
118                        var stores = p_obj.json.stores;
119                        parent.widgets.storeMenuButton = new YAHOO.widget.Button(parent.id + "-store-menu-button", { 
120                            type: "menu",
121                            menu: stores,
122                            lazyloadmenu: false
123                         });
124                        parent.widgets.storeMenuButton.set("value", parent.store);
125                        parent.widgets.storeMenuButton.set("label", parent.store);
126                        parent.widgets.storeMenuButton.on("selectedMenuItemChange", onSelectedMenuItemChange);
127                    },
128                    scope: this
129                 },
130                 failureMessage: parent._msg("message.getstores-failure")
131             });
132             
133             // Query language button
134             parent.widgets.langMenuButton = new YAHOO.widget.Button(parent.id + "-lang-menu-button", { 
135                 type: "menu",
136                 menu: parent.id + "-lang-menu-select"
137              });
138             parent.widgets.langMenuButton.on("selectedMenuItemChange", onSelectedMenuItemChange);
139             parent.widgets.langMenuButton.set("value", parent.searchLanguage);
140             parent.widgets.langMenuButton.set("label", parent.searchLanguage);
141             
142             // DataTable and DataSource setup
143             parent.widgets.dataSource = new YAHOO.util.DataSource(Alfresco.constants.PROXY_URI + "slingshot/node/search",
144             {
145                responseType: YAHOO.util.DataSource.TYPE_JSON,
146                responseSchema:
147                {
148                   resultsList: "results",
149                   metaFields:
150                   {
151                      recordOffset: "startIndex",
152                      totalRecords: "totalResults"
153                   }
154                }
155             });
156             
157             var me = this;
158             
159             // Work to be performed after data has been queried but before display by the DataTable
160             parent.widgets.dataSource.doBeforeParseData = function PeopleFinder_doBeforeParseData(oRequest, oFullResponse)
161             {
162                var updatedResponse = oFullResponse;
163                
164                if (oFullResponse)
165                {
166                   var items = oFullResponse.results;
167                   
168                   // initial sort by username field
169                   items.sort(function(a, b)
170                   {
171                      return (a.name > b.name);
172                   });
173                   
174                   // we need to wrap the array inside a JSON object so the DataTable gets the object it expects
175                   updatedResponse =
176                   {
177                      "results": items
178                   };
179                }
180                
181                // update Results Bar message with number of results found
182                if (items.length < parent.options.maxSearchResults)
183                {
184                   me._setResultsMessage("message.results", $html(parent.searchTerm), items.length);
185                }
186                else
187                {
188                   me._setResultsMessage("message.maxresults", parent.options.maxSearchResults);
189                }
190                
191                return updatedResponse;
192             };
193             
194             // Setup the main datatable
195             this._setupDataTable();
196             
197             // register the "enter" event on the search text field
198             var searchText = Dom.get(parent.id + "-search-text");
199             
200             new YAHOO.util.KeyListener(searchText,
201             {
202                keys: YAHOO.util.KeyListener.KEY.ENTER
203             },
204             {
205                fn: function() 
206                {
207                   parent.onSearchClick();
208                },
209                scope: parent,
210                correctScope: true
211             }, "keydown").enable();
212          },
213          
214          onShow: function ConsoleNodeBrowser_SearchPanelHandler_onShow()
215          {
216             Dom.get(parent.id + "-search-text").focus();
217          },
218          
219          onUpdate: function ConsoleNodeBrowser_SearchPanelHandler_onUpdate()
220          {
221             // update the text field - as this event could come from bookmark, navigation or a search button click
222             var searchTermElem = Dom.get(parent.id + "-search-text");
223             searchTermElem.value = parent.searchTerm;
224             
225             // Update language menu
226             parent.widgets.langMenuButton.set("value", parent.searchLanguage);
227             parent.widgets.langMenuButton.set("label", parent.searchLanguage);
228             
229             // Update language menu
230             if (parent.widgets.storeMenuButton)
231             {
232                 parent.widgets.storeMenuButton.set("value", parent.store);
233                 parent.widgets.storeMenuButton.set("label", parent.store);
234             }
235             
236             // check search length again as we may have got here via history navigation
237             if (!this.isSearching && parent.searchTerm !== undefined && parent.searchTerm.length >= parent.options.minSearchTermLength)
238             {
239                this.isSearching = true;
240 
241                var me = this;
242                
243                // Reset the custom error messages
244                me._setDefaultDataTableErrors(parent.widgets.dataTable);
245                
246                // Don't display any message
247                parent.widgets.dataTable.set("MSG_EMPTY", parent._msg("message.searching"));
248                
249                // Empty results table
250                parent.widgets.dataTable.deleteRows(0, parent.widgets.dataTable.getRecordSet().getLength());
251                
252                var successHandler = function ConsoleNodeBrowser_SearchPanelHandler_onUpdate_successHandler(sRequest, oResponse, oPayload)
253                {
254                   me._enableSearchUI();                  
255                   me._setDefaultDataTableErrors(parent.widgets.dataTable);
256                   parent.widgets.dataTable.onDataReturnInitializeTable.call(parent.widgets.dataTable, sRequest, oResponse, oPayload);
257                };
258                
259                var failureHandler = function ConsoleNodeBrowser_SearchPanelHandler_onUpdate_failureHandler(sRequest, oResponse)
260                {
261                   me._enableSearchUI();
262                   if (oResponse.status == 401)
263                   {
264                      // Our session has likely timed-out, so refresh to offer the login page
265                      window.location.reload();
266                   }
267                   else
268                   {
269                      try
270                      {
271                         var response = YAHOO.lang.JSON.parse(oResponse.responseText);
272                         parent.widgets.dataTable.set("MSG_ERROR", response.message);
273                         parent.widgets.dataTable.showTableMessage(response.message, YAHOO.widget.DataTable.CLASS_ERROR);
274                         me._setResultsMessage("message.noresults");
275                      }
276                      catch(e)
277                      {
278                         me._setDefaultDataTableErrors(parent.widgets.dataTable);
279                      }
280                   }
281                };
282 
283                // Send the query to the server
284                parent.widgets.dataSource.sendRequest(me._buildSearchParams(parent.searchTerm, parent.searchLanguage, parent.store),
285                {
286                   success: successHandler,
287                   failure: failureHandler,
288                   scope: parent
289                });
290                me._setResultsMessage("message.searchingFor", $html(parent.searchTerm));
291 
292                // Disable search button and display a wait feedback message if the users hasn't been found yet
293                parent.widgets.searchButton.set("disabled", true);
294                YAHOO.lang.later(2000, me, function(){
295                   if (me.isSearching)
296                   {
297                      if (!me.widgets.feedbackMessage)
298                      {
299                       me.widgets.feedbackMessage = Alfresco.util.PopupManager.displayMessage(
300                       {
301                          text: Alfresco.util.message("message.searching", parent.name),
302                          spanClass: "wait",
303                          displayTime: 0
304                       });
305                      }
306                      else if (!me.widgets.feedbackMessage.cfg.getProperty("visible"))
307                      {
308                       me.widgets.feedbackMessage.show();
309                      }
310                   }
311                }, []);
312             }
313          },
314 
315          /**
316           * Enable search button, hide the pending wait message and set the panel as not searching.
317           *
318           * @method _enableSearchUI
319           * @private
320           */
321          _enableSearchUI: function ConsoleNodeBrowser_SearchPanelHandler_enableSearchUI()
322          {
323             // Enable search button and close the wait feedback message if present
324             if (this.widgets.feedbackMessage && this.widgets.feedbackMessage.cfg.getProperty("visible"))
325             {
326                this.widgets.feedbackMessage.hide();
327             }
328             parent.widgets.searchButton.set("disabled", false);
329             this.isSearching = false;
330          },
331 
332          /**
333           * Setup the YUI DataTable with custom renderers.
334           *
335           * @method _setupDataTable
336           * @private
337           */
338          _setupDataTable: function ConsoleNodeBrowser_SearchPanelHandler_setupDataTable()
339          {
340             /**
341              * DataTable Cell Renderers
342              *
343              * Each cell has a custom renderer defined as a custom function. See YUI documentation for details.
344              * These MUST be inline in order to have access to the parent instance (via the "parent" variable).
345              */
346             
347             /**
348              * Generic HTML-safe custom datacell formatter
349              */
350             var renderCellSafeHTML = function renderCellSafeHTML(elCell, oRecord, oColumn, oData)
351             {
352                elCell.innerHTML = $html(oData);
353             };
354 
355             /**
356              * Qname renderer
357              *
358              * @method renderQName
359              */
360             var renderQName = function renderQName(elCell, oRecord, oColumn, oData)
361             {
362                 elCell.innerHTML = $html(oData[parent._qnamePropertyName()]);
363             }
364 
365             /**
366              * Parent Qname path renderer
367              *
368              * @method renderParentPath
369              */
370             var renderParentPath = function renderParentPath(elCell, oRecord, oColumn, oData)
371             {
372                 if (oData != "")
373                 {
374                     var path = oData[parent._qnamePropertyName()];
375                     // Remove last part
376                     var index = parent.options.shortQNames ? path.lastIndexOf("/") : path.lastIndexOf("/{");
377                     path = index != -1 ? path.substring(0, index) : path;
378                     elCell.innerHTML = $html(path);
379                 }
380             }
381 
382             /**
383              * Node name renderer
384              *
385              * @method renderNodeName
386              */
387             var renderNodeName = function renderNodeName(elCell, oRecord, oColumn, oData)
388             {
389                 if (oData != "")
390                 {
391                     renderNodeLink(elCell, oRecord, oColumn, $html(oData[parent._qnamePropertyName()]));
392                 }
393             }
394             
395             /**
396              * Node name custom datacell formatter
397              *
398              * @method renderName
399              */
400             var renderNodeLink = function renderNodeLink(elCell, oRecord, oColumn, oData)
401             {
402                // Create view userlink
403                var viewNodeLink = document.createElement("a");
404                Dom.setAttribute(viewNodeLink, "href", "#");
405                viewNodeLink.innerHTML = $html(oData);
406 
407                // fire the 'viewUserClick' event when the selected user in the list has changed
408                YAHOO.util.Event.addListener(viewNodeLink, "click", function(e)
409                {
410                   YAHOO.util.Event.preventDefault(e);
411                   YAHOO.Bubbling.fire('viewNodeClick',
412                   {
413                      nodeRef: oRecord.getData("nodeRef")
414                   });
415                }, null, parent);
416                elCell.appendChild(viewNodeLink);
417             };
418             
419             // DataTable column defintions
420             var columnDefinitions =
421             [
422                { key: "name", label: parent._msg("label.name"), sortable: true, formatter: renderNodeName },
423                { key: "qnamePath", label: parent._msg("label.parent_path"), sortable: true, formatter: renderParentPath },
424                { key: "nodeRef", label: parent._msg("label.node-ref"), sortable: true, formatter: renderNodeLink }
425             ];
426             
427             // DataTable definition
428             parent.widgets.dataTable = new YAHOO.widget.DataTable(parent.id + "-datatable", columnDefinitions, parent.widgets.dataSource,
429             {
430                initialLoad: false,
431                renderLoopSize: 32,
432                sortedBy:
433                {
434                   key: "name",
435                   dir: "asc"
436                },
437                MSG_EMPTY: parent._msg("message.empty")
438             });
439          },
440          
441          /**
442           * Resets the YUI DataTable errors to our custom messages
443           * NOTE: Scope could be YAHOO.widget.DataTable, so can't use "this"
444           *
445           * @method _setDefaultDataTableErrors
446           * @param dataTable {object} Instance of the DataTable
447           * @private
448           */
449          _setDefaultDataTableErrors: function ConsoleNodeBrowser_SearchPanelHandler_setDefaultDataTableErrors(dataTable)
450          {
451             dataTable.set("MSG_EMPTY", parent._msg("message.datatable.empty"));
452             dataTable.set("MSG_ERROR", parent._msg("message.datatable.error"));
453          },
454          
455          /**
456           * Build URI parameters for People List JSON data webscript
457           *
458           * @method _buildSearchParams
459           * @param searchTerm {string} User search term
460           * @param store {string} Store name
461           * @private
462           */
463          _buildSearchParams: function ConsoleNodeBrowser_SearchPanelHandler_buildSearchParams(searchTerm, searchLanguage, store)
464          {
465             return "?q=" + encodeURIComponent(searchTerm) + 
466                "&lang=" + encodeURIComponent(searchLanguage) + 
467                "&store=" + encodeURIComponent(store) + 
468                "&maxResults=" + parent.options.maxSearchResults;
469          },
470          
471          /**
472           * Set the message in the Results Bar area
473           * 
474           * @method _setResultsMessage
475           * @param messageId {string} The messageId to display
476           * @private
477           */
478          _setResultsMessage: function ConsoleNodeBrowser_SearchPanelHandler_setResultsMessage(messageId, arg1, arg2)
479          {
480             var resultsDiv = Dom.get(parent.id + "-search-bar");
481             resultsDiv.innerHTML = parent._msg(messageId, arg1, arg2);
482          },
483          
484          /**
485           * Successfully applied options event handler
486           *
487           * @method onSuccess
488           * @param response {object} Server response object
489           */
490          onSuccess: function ConsoleNodeBrowser_SearchPanelHandler_onSuccess(response)
491          {
492             if (response && response.json)
493             {
494                if (response.json.success)
495                {
496                   // refresh the browser to force the themed components to reload
497                   window.location.reload(true);
498                }
499                else if (response.json.message)
500                {
501                   Alfresco.util.PopupManager.displayPrompt(
502                   {
503                      text: response.json.message
504                   });
505                }
506             }
507             else
508             {
509                Alfresco.util.PopupManager.displayPrompt(
510                {
511                   text: Alfresco.util.message("message.failure")
512                });
513             }
514          }
515       });
516       new SearchPanelHandler();
517       
518       /* View Panel Handler */
519       ViewPanelHandler = function ConsoleNodeBrowser_ViewPanelHandler_constructor()
520       {
521          ViewPanelHandler.superclass.constructor.call(this, "view");
522       };
523       
524       YAHOO.extend(ViewPanelHandler, Alfresco.ConsolePanelHandler,
525       {
526          onLoad: function ConsoleNodeBrowser_ViewPanelHandler_onLoad()
527          {
528             // Buttons
529             parent.widgets.gobackButton = Alfresco.util.createYUIButton(parent, "goback-button", parent.onGoBackClick);
530             parent.widgets.deleteuserButton = Alfresco.util.createYUIButton(parent, "deleteuser-button", parent.onDeleteUserClick);
531             parent.widgets.edituserButton = Alfresco.util.createYUIButton(parent, "edituser-button", parent.onEditUserClick);
532          },
533          
534          onBeforeShow: function ConsoleNodeBrowser_ViewPanelHandler_onBeforeShow()
535          {
536             // Hide the main panel area before it is displayed - so we don't show
537             // old data to the user before the Update() method paints the results
538             Dom.get(parent.id + "-view-title").innerHTML = "";
539             Dom.setStyle(parent.id + "-view-main", "visibility", "hidden");
540          },
541          
542          onShow: function ConsoleNodeBrowser_ViewPanelHandler_onShow()
543          {
544             window.scrollTo(0, 0);
545          },
546          
547          onUpdate: function ConsoleNodeBrowser_ViewPanelHandler_onUpdate()
548          {
549             window.scrollTo(0, 0);
550             
551             // Use a XHR call to get node data
552             Alfresco.util.Ajax.request(
553             {
554                url: Alfresco.constants.PROXY_URI + "slingshot/node/" + parent.currentNodeRef.replace("://", "/"),
555                method: Alfresco.util.Ajax.GET,
556                successCallback:
557                {
558                   fn: this.onDataLoad,
559                   scope: parent
560                },
561                failureMessage: parent._msg("message.getnode-failure", $html(parent.currentUserId))   
562             });
563          },
564 
565          /**
566           * Node data loaded successfully. Sets up YUI DataTable instances and other UI elements.
567           *
568           * @method onDataLoad
569           * @param p_obj {object} Result object, defining serverResponse and json objects
570           */
571          onDataLoad: function ConsoleNodeBrowser_ViewPanelHandler_onDataLoad(p_obj)
572          {
573              var me = this, node = p_obj.json, nodeRef = node.nodeRef;
574              
575              var fnSetter = function(id, val)
576              {
577                 Dom.get(parent.id + id).innerHTML = val ? $html(val) : "";
578              };
579 
580              /**
581               * Node link custom datacell formatter
582               *
583               * @method renderName
584               */
585              var renderNodeLink = function renderNodeLink(elCell, oRecord, oColumn, oData, oParams)
586              {
587                 oParams = oParams || {};
588                 var viewNodeLink = document.createElement("a");
589                 YAHOO.util.Dom.setAttribute(viewNodeLink, "href", "#");
590                 viewNodeLink.innerHTML = $html(oData);
591 
592                 // fire the 'viewNodeClick' event when the selected node in the list has changed
593                 YAHOO.util.Event.addListener(viewNodeLink, "click", function(e)
594                 {
595                    YAHOO.util.Event.preventDefault(e);
596                    YAHOO.Bubbling.fire('viewNodeClick',
597                    {
598                       nodeRef: oParams.nodeRef || oRecord.getData("nodeRef")
599                    });
600                 }, null, parent);
601                 elCell.appendChild(viewNodeLink);
602              };
603 
604              /**
605               * QName custom formatter
606               *
607               * @method renderQName
608               */
609              var renderQName = function renderQName(elCell, oRecord, oColumn, oData)
610              {
611                 elCell.innerHTML = $html(oData[parent._qnamePropertyName()]);
612              };
613 
614              /**
615               * Child name formatter
616               *
617               * @method renderChildName
618               */
619              var renderChildName = function renderChildName(elCell, oRecord, oColumn, oData)
620              {
621                  renderNodeLink(elCell, oRecord, oColumn, oData[parent._qnamePropertyName()]);
622              };
623 
624              /**
625               * Assoc nodeRef formatter
626               *
627               * @method renderSourceNodeRef
628               */
629              var renderAssocNodeRef = function renderChildName(elCell, oRecord, oColumn, oData)
630              {
631                  renderNodeLink(elCell, oRecord, oColumn, oData, { nodeRef: oData });
632              };
633              
634              /**
635               * Property value custom datacell formatter
636               *
637               * @method renderPropertyValue
638               */
639              var renderPropertyValue = function renderPropertyValue(elCell, oRecord, oColumn, oData)
640              {
641                 var renderValue = function(val, el)
642                 {
643                     if (val.isNullValue)
644                     {
645                         Dom.addClass(el, "node-value-label");
646                         el.innerHTML = parent.msg("label.node-value-null");
647                     }
648                     else
649                     {
650                         if (val.dataType == "{http://www.alfresco.org/model/dictionary/1.0}content")
651                         {
652                            // Create new link
653                            var html = "<a ";
654                            html += "href=\"" + Alfresco.constants.PROXY_URI + "api/node/" + nodeRef.replace("://", "/") + "/content" + "\">";
655                            html += $html(val.value);
656                            html += "</a>";
657                            // Create new link
658                            var contentLink = document.createElement("a");
659                            contentLink.innerHTML = $html(val.value);
660                            YAHOO.util.Dom.setAttribute(contentLink, "href", Alfresco.constants.PROXY_URI + "api/node/" + nodeRef.replace("://", "/") + "/content;" + oRecord.getData("name").prefixedName);
661                            el.appendChild(contentLink);
662                         }
663                         else if (val.dataType == "{http://www.alfresco.org/model/dictionary/1.0}noderef")
664                         {
665                             renderNodeLink(el, oRecord, oColumn, val.value, { nodeRef: val.value });
666                         }
667                         else
668                         {
669                             el.innerHTML = $html(val.value);
670                         }
671                     }
672                 };
673 
674                 if (oRecord.getData("multiple") == false)
675                 {
676                     renderValue(oData[0], elCell.appendChild(document.createElement("div"), elCell));
677                 }
678                 else
679                 {
680                     var labelEl = elCell.appendChild(document.createElement("div"), elCell);
681                     Dom.addClass(labelEl, "node-value-label");
682                     labelEl.innerHTML = parent.msg("label.node-value-collection");
683                     for (var i = 0; i < oData.length; i++)
684                     {
685                         renderValue(oData[i], elCell.appendChild(document.createElement("div"), elCell));
686                     }
687                 }
688                 
689              };
690 
691              Dom.get(parent.id + "-view-title").innerHTML = node.name[parent._qnamePropertyName()];
692              
693              // About section fields
694              fnSetter("-view-node-ref", node.nodeRef);
695              fnSetter("-view-node-path", node.qnamePath[parent._qnamePropertyName()]);
696              fnSetter("-view-node-type", node.type[parent._qnamePropertyName()]);
697 
698              Dom.get(parent.id + "-view-node-parent").innerHTML = "";
699              // Add parent noderef link
700              if (node.parent !== null)
701              {
702                 var nodeLink = document.createElement("a");
703                 Dom.setAttribute(nodeLink, "href", "#");
704                 nodeLink.innerHTML = $html(node.parentNodeRef);
705                 YAHOO.util.Event.addListener(nodeLink, "click", function(e)
706                 {
707                    YAHOO.util.Event.preventDefault(e);
708                    YAHOO.Bubbling.fire('viewNodeClick',
709                    {
710                       nodeRef: node.parentNodeRef
711                    });
712                 }, null, parent);
713                 Dom.get(parent.id + "-view-node-parent").appendChild(nodeLink);
714              }
715 
716              var dtConfig = {
717                      MSG_EMPTY: parent._msg("message.datatable.empty"),
718                      MSG_ERROR: parent._msg("message.datatable.error")
719              };
720 
721              var propsDT = new YAHOO.widget.DataTable(parent.id + "-view-node-properties", 
722                 [
723                    { key: "name", label: parent.msg("label.properties-name"), formatter: renderQName },
724                    { key: "type", label: parent.msg("label.properties-type"), formatter: renderQName },
725                    { key: "values", label: parent.msg("label.properties-value"), formatter: renderPropertyValue },
726                    { key: "residual", label: parent.msg("label.properties-residual") }
727                 ], 
728                 new YAHOO.util.LocalDataSource(node.properties),
729                 dtConfig
730              );
731              
732              var aspects = "";
733              for ( var i = 0; i < node.aspects.length; i++)
734              {
735                 aspects += (i != 0 ? "<br />" : "") + $html(node.aspects[i][parent._qnamePropertyName()]);
736              }
737              Dom.get(parent.id + "-view-node-aspects").innerHTML = aspects;
738              
739              var childrenDT = new YAHOO.widget.DataTable(parent.id + "-view-node-children", 
740                 [
741                    { key: "name", label: parent.msg("label.children-name"), formatter: renderChildName },
742                    { key: "type", label: parent.msg("label.children-type"), formatter: renderQName },
743                    { key: "nodeRef", label: parent.msg("label.children-node-ref"), formatter: renderNodeLink },
744                    { key: "primary", label: parent.msg("label.children-primary") },
745                    { key: "assocType", label: parent.msg("label.children-assoc-type"), formatter: renderQName },
746                    { key: "index", label: parent.msg("label.children-index") }
747                 ], 
748                 new YAHOO.util.LocalDataSource(node.children),
749                 dtConfig
750              );
751              
752              var parentsDT = new YAHOO.widget.DataTable(parent.id + "-view-node-parents", 
753                  [
754                    { key: "name", label: parent.msg("label.parents-name"), formatter: renderChildName },
755                    { key: "type", label: parent.msg("label.parents-type"), formatter: renderQName },
756                    { key: "nodeRef", label: parent.msg("label.parents-node-ref"), formatter: renderNodeLink },
757                    { key: "primary", label: parent.msg("label.parents-primary") },
758                    { key: "assocType", label: parent.msg("label.parents-assoc-type"), formatter: renderQName }
759                    
760                 ], 
761                 new YAHOO.util.LocalDataSource(node.parents),
762                 dtConfig
763              );
764 
765              var assocsDT = new YAHOO.widget.DataTable(parent.id + "-view-node-assocs", 
766                 [
767                    { key: "assocType", label: parent.msg("label.assocs-assoc-type"), formatter: renderQName },
768                    { key: "targetRef", label: parent.msg("label.assocs-node-ref"), formatter: renderAssocNodeRef },
769                    { key: "type", label: parent.msg("label.assocs-type"), formatter: renderQName }
770                 ], 
771                 new YAHOO.util.LocalDataSource(node.assocs),
772                 dtConfig
773              );
774              
775              var sourceAssocsDT = new YAHOO.widget.DataTable(parent.id + "-view-node-source-assocs", 
776                 [
777                    { key: "assocType", label: parent.msg("label.source-assocs-assoc-type"), formatter: renderQName },
778                    { key: "sourceRef", label: parent.msg("label.source-assocs-node-ref"), formatter: renderAssocNodeRef },
779                    { key: "type", label: parent.msg("label.source-assocs-type"), formatter: renderQName }
780                 ], 
781                 new YAHOO.util.LocalDataSource(node.sourceAssocs),
782                 dtConfig
783              );
784 
785              var permissionsDT = new YAHOO.widget.DataTable(parent.id + "-view-node-permissions", 
786                 [
787                    { key: "permission", label: parent.msg("label.permissions-permission") },
788                    { key: "authority", label: parent.msg("label.permissions-authority") },
789                    { key: "rel", label: parent.msg("label.permissions-access") }
790                 ], 
791                 new YAHOO.util.LocalDataSource(node.permissions.entries),
792                 dtConfig
793              );
794              
795              var storePermissionsDT = new YAHOO.widget.DataTable(parent.id + "-view-node-store-permissions", 
796                 [
797                    { key: "permission", label: parent.msg("label.permissions-store-permission") },
798                    { key: "authority", label: parent.msg("label.permissions-authority") },
799                    { key: "rel", label: parent.msg("label.permissions-access") }
800                 ], 
801                 new YAHOO.util.LocalDataSource(node.permissions.masks),
802                 dtConfig
803              );
804              
805              fnSetter("-view-node-inherits-permissions", "" + node.permissions.inherit);
806              fnSetter("-view-node-owner", node.permissions.owner);
807              
808              // Make main panel area visible
809              Dom.setStyle(parent.id + "-view-main", "visibility", "visible");
810          },
811          
812          /**
813           * View Node event handler
814           *
815           * @method onNodeClick
816           * @param e {object} DomEvent
817           * @param args {array} Event parameters (depends on event type)
818           */
819          onNodeClick: function ConsoleNodeBrowser_ViewPanelHandler_onNodeClick(e, args)
820          {
821             var nodeRef = args[1].nodeRef;
822             this.refreshUIState({"panel": "view", "nodeRef": nodeRef});
823          }
824          
825       });
826       new ViewPanelHandler();
827       
828       return this;
829    };
830    
831    YAHOO.extend(Alfresco.ConsoleNodeBrowser, Alfresco.ConsoleTool,
832    {
833       /**
834        * Object container for initialization options
835        *
836        * @property options
837        * @type object
838        */
839       options:
840       {
841          /**
842           * Number of characters required for a search.
843           * 
844           * @property minSearchTermLength
845           * @type int
846           * @default 1
847           */
848          minSearchTermLength: 1,
849          
850          /**
851           * Maximum number of items to display in the results list
852           * 
853           * @property maxSearchResults
854           * @type int
855           * @default 100
856           */
857          maxSearchResults: 100,
858 
859          /**
860           * Whether to use short QNames when displaying node information
861           * 
862           * @property shortQNames
863           * @type boolean
864           * @default true
865           */
866          shortQNames: true
867       },
868       
869       /**
870        * Current node ref if viewing a node.
871        * 
872        * @property currentNodeRef
873        * @type string
874        */
875       currentNodeRef: "",
876       
877       /**
878        * Name of the store to search against
879        * 
880        * @property store
881        * @type string
882        */
883       store: "workspace://SpacesStore",
884       
885       /**
886        * Current search term, obtained from form input field.
887        * 
888        * @property searchTerm
889        * @type string
890        */
891       searchTerm: "PATH:\"/\"",
892       
893       /**
894        * Current search language, obtained from drop-down.
895        * 
896        * @property searchLanguage
897        * @type string
898        */
899       searchLanguage: "fts-alfresco",
900       
901       /**
902        * Fired by YUILoaderHelper when required component script files have
903        * been loaded into the browser.
904        *
905        * @method onComponentsLoaded
906        */
907       onComponentsLoaded: function ConsoleNodeBrowser_onComponentsLoaded()
908       {
909          Event.onContentReady(this.id, this.onReady, this, true);
910       },
911       
912       /**
913        * Fired by YUI when parent element is available for scripting.
914        * Component initialisation, including instantiation of YUI widgets and event listener binding.
915        *
916        * @method onReady
917        */
918       onReady: function ConsoleNodeBrowser_onReady()
919       {
920          // Call super-class onReady() method
921          Alfresco.ConsoleNodeBrowser.superclass.onReady.call(this);
922       },
923       
924       /**
925        * YUI WIDGET EVENT HANDLERS
926        * Handlers for standard events fired from YUI widgets, e.g. "click"
927        */
928       
929       /**
930        * History manager state change event handler (override base class)
931        *
932        * @method onStateChanged
933        * @param e {object} DomEvent
934        * @param args {array} Event parameters (depends on event type)
935        */
936       onStateChanged: function ConsoleNodeBrowser_onStateChanged(e, args)
937       {
938          var state = this.decodeHistoryState(args[1].state);
939          
940          // test if panel has actually changed?
941          if (state.panel)
942          {
943             this.showPanel(state.panel);
944          }
945          
946          if (state.search !== undefined && this.currentPanelId === "search")
947          {
948             // keep track of the last search performed
949             var searchTerm = state.search;
950             this.searchTerm = searchTerm;
951             
952             // keep track of search language
953             var searchLanguage = state.lang;
954             this.searchLanguage = searchLanguage;
955             
956             // keep track of store name
957             var store = state.store;
958             this.store = store;
959             
960             this.updateCurrentPanel();
961          }
962          
963          if (state.nodeRef &&
964              (this.currentPanelId === "view"))
965          {
966             this.currentNodeRef = state.nodeRef;
967             
968             this.updateCurrentPanel();
969          }
970       },
971       
972       /**
973        * Search button click event handler
974        *
975        * @method onSearchClick
976        * @param e {object} DomEvent
977        * @param args {array} Event parameters (depends on event type)
978        */
979       onSearchClick: function ConsoleNodeBrowser_onSearchClick(e, args)
980       {
981          var searchTermElem = Dom.get(this.id + "-search-text");
982          var searchTerm = YAHOO.lang.trim(searchTermElem.value);
983          
984          // Search language
985          var searchLanguage = this.widgets.langMenuButton.get("value");
986          
987          // Search language
988          var store = this.widgets.storeMenuButton.get("value");
989          
990          // inform the user if the search term entered is too small
991          if (searchTerm.length < this.options.minSearchTermLength)
992          {
993             Alfresco.util.PopupManager.displayMessage(
994             {
995                text: this._msg("message.minimum-length", this.options.minSearchTermLength)
996             });
997             return;
998          }
999          
1000          this.refreshUIState({"search": searchTerm, "lang": searchLanguage, "store": store});
1001       },
1002       
1003       /**
1004        * View Node event handler
1005        *
1006        * @method onViewNodeClick
1007        * @param e {object} DomEvent
1008        * @param args {array} Event parameters (depends on event type)
1009        */
1010       onViewNodeClick: function ConsoleNodeBrowser_onViewNodeClick(e, args)
1011       {
1012          var nodeRef = args[1].nodeRef;
1013          this.refreshUIState({"panel": "view", "nodeRef": nodeRef});
1014       },
1015 
1016       /**
1017        * Go back button click event handler
1018        *
1019        * @method onGoBackClick
1020        * @param e {object} DomEvent
1021        * @param args {array} Event parameters (depends on event type)
1022        */
1023       onGoBackClick: function ConsoleNodeBrowser_onGoBackClick(e, args)
1024       {
1025          this.refreshUIState({"panel": "search"});
1026       },
1027       
1028       /**
1029        * Encode state object into a packed string for use as url history value.
1030        * Override base class.
1031        * 
1032        * @method encodeHistoryState
1033        * @param obj {object} state object
1034        * @private
1035        */
1036       encodeHistoryState: function ConsoleNodeBrowser_encodeHistoryState(obj)
1037       {
1038          // wrap up current state values
1039          var stateObj = {};
1040          if (this.currentPanelId !== "")
1041          {
1042             stateObj.panel = this.currentPanelId;
1043          }
1044          if (this.currentNodeRef !== "")
1045          {
1046             stateObj.nodeRef = this.currentNodeRef;
1047          }
1048          if (this.searchTerm !== undefined)
1049          {
1050             stateObj.search = this.searchTerm;
1051          }
1052          if (this.searchLanguage !== undefined)
1053          {
1054             stateObj.lang = this.searchLanguage;
1055          }
1056          if (this.store !== undefined)
1057          {
1058             stateObj.store = this.store;
1059          }
1060          
1061          // convert to encoded url history state - overwriting with any supplied values
1062          var state = "";
1063          if (obj.panel || stateObj.panel)
1064          {
1065             state += "panel=" + encodeURIComponent(obj.panel ? obj.panel : stateObj.panel);
1066          }
1067          if (obj.nodeRef || stateObj.nodeRef)
1068          {
1069             if (state.length !== 0)
1070             {
1071                state += "&";
1072             }
1073             state += "nodeRef=" + encodeURIComponent(obj.nodeRef ? obj.nodeRef : stateObj.nodeRef);
1074          }
1075          if (obj.search !== undefined || stateObj.search !== undefined)
1076          {
1077             if (state.length !== 0)
1078             {
1079                state += "&";
1080             }
1081             state += "search=" + encodeURIComponent(obj.search !== undefined ? obj.search : stateObj.search);
1082          }
1083          if (obj.lang !== undefined || stateObj.lang !== undefined)
1084          {
1085             if (state.length !== 0)
1086             {
1087                state += "&";
1088             }
1089             state += "lang=" + encodeURIComponent(obj.lang !== undefined ? obj.lang : stateObj.lang);
1090          }
1091          if (obj.store !== undefined || stateObj.store !== undefined)
1092          {
1093             if (state.length !== 0)
1094             {
1095                state += "&";
1096             }
1097             state += "store=" + encodeURIComponent(obj.store !== undefined ? obj.store : stateObj.store);
1098          }
1099          return state;
1100       },
1101       
1102       /**
1103        * Gets a custom message
1104        *
1105        * @method _msg
1106        * @param messageId {string} The messageId to retrieve
1107        * @return {string} The custom message
1108        * @private
1109        */
1110       _msg: function ConsoleNodeBrowser__msg(messageId)
1111       {
1112          return Alfresco.util.message.call(this, messageId, "Alfresco.ConsoleNodeBrowser", Array.prototype.slice.call(arguments).slice(1));
1113       },
1114       
1115       /**
1116        * Returns the qname property name to use for display purposes, based on shortQNames setting
1117        *
1118        * @method _qnamePropertyName
1119        * @return {string} The name of the property to display from QName objects
1120        * @private
1121        */
1122       _qnamePropertyName: function ConsoleNodeBrowser__qnamePropertyName()
1123       {
1124          return this.options.shortQNames == true ? "prefixedName": "name";
1125       }
1126    });
1127 })();