cancel
Showing results for 
Search instead for 
Did you mean: 

Possible to filter by multiple tags?

zbennett
Champ in-the-making
Champ in-the-making
Hi all,

I'm trying to figure out if it's possible to filter my Document Library by multiple tags at once.  In other words, I want to filter by one tag and then narrow my filter down with another tag.

In an ideal world, I would be able to use the tag list on the left side of the page:
[img]http://zandadev.com/zach/my_images/alfresco_forums/filter_by_multiple_tags/tag_list.jpg[/img]

Clicking on a tag in the list filters my view of the Document Library so it only displays items with that tag, as expected.  Clicking on another tag then displays all of the items in the Library with that tag.  I want to change the functionality so that clicking on a second tag displays only the items with both the first tags and the second tag.

I know that I can use "Search This Site" to search for my first tag, and then I can narrow my search results by searching for my second tag.  I have a couple problems with this method, though; I don't want to make users have to type their tags and, even more importantly, search results work different from filter results.

Search results vs filter results:
[img]http://zandadev.com/zach/my_images/alfresco_forums/filter_by_multiple_tags/search_vs_filter.jpg[/img]

I've only been using Alfresco for a few weeks and I'm still learning how most of the features work, so this following idea might be a bit absurd…bear with me. Smiley Wink

I noticed that a standard Document Library URL looks like this:
http://192.168.100.62:8080/share/page/site/gallery/documentlibrary
And a filtered Document Library URL has a filter parameter:
http://192.168.100.62:8080/share/page/site/gallery/documentlibrary#filter=tag|anthony&page=1

My first thought was that I could figure out how to add multiple tag filters to the URL and then change the tag links so they modify the URL according to the way I want them to work. I haven't figured out how to manually add multiple tag filters to the URL yet though - is that even possible?

I'm going to be digging around for documentation on the way the tag list works… I would be extremely grateful if anyone could push me in the right direction to get started!

Thanks,
Zach
35 REPLIES 35

sirround
Champ in-the-making
Champ in-the-making
Lines 1484 through 2857


     
      /**
       * Fired by YUI when History Manager is initialised and available for scripting.
       * Component initialisation, including instantiation of YUI widgets and event listener binding.
       *
       * @method onHistoryManagerReady
       */
      onHistoryManagerReady: function DL_onHistoryManagerReady()
      {
         // Fire changeFilter event for first-time population
         Alfresco.logger.debug("DL_onHistoryManagerReady", "changeFilter =>", this.options.initialFilter);
         YAHOO.Bubbling.fire("changeFilter", YAHOO.lang.merge(
         {
            doclistFirstTimeNav: true
         }, this.options.initialFilter));
        
         // Finally show the component body here to prevent UI artifacts on YUI button decoration
         Dom.setStyle(this.id + "-body", "visibility", "visible");
      },
     
      /**
       * Public functions
       *
       * Functions designed to be called form external sources
       */

      /**
       * Public function to get array of selected files
       *
       * @method getSelectedFiles
       * @return {Array} Currently selected files
       */
      getSelectedFiles: function DL_getSelectedFiles()
      {
         var files = [],
            recordSet = this.widgets.dataTable.getRecordSet(),
            record;
        
         for (var i = 0, j = recordSet.getLength(); i < j; i++)
         {
            record = recordSet.getRecord(i);
            if (this.selectedFiles[record.getData("nodeRef")])
            {
               files.push(record.getData());
            }
         }
        
         return files;
      },
     
      /**
       * Public function to select files by specified groups
       *
       * @method selectFiles
       * @param p_selectType {string} Can be one of the following:
       * <pre>
       * selectAll - all documents and folders
       * selectNone - deselect all
       * selectInvert - invert selection
       * selectDocuments - select all documents
       * selectFolders - select all folders
       * </pre>
       */
      selectFiles: function DL_selectFiles(p_selectType)
      {
         var recordSet = this.widgets.dataTable.getRecordSet(),
            checks = YAHOO.util.Selector.query('input[type="checkbox"]', this.widgets.dataTable.getTbodyEl()),
            len = checks.length,
            record, i, fnCheck, typeMap;

         var typeMapping =
         {
            selectDocuments:
            {
               "document": true
            },
            selectFolders:
            {
               "folder": true
            }
         };

         switch (p_selectType)
         {
            case "selectAll":
               fnCheck = function(assetType, isChecked)
               {
                  return true;
               };
               break;
           
            case "selectNone":
               fnCheck = function(assetType, isChecked)
               {
                  return false;
               };
               break;

            case "selectInvert":
               fnCheck = function(assetType, isChecked)
               {
                  return !isChecked;
               };
               break;

            case "selectDocuments":
            case "selectFolders":
               typeMap = typeMapping[p_selectType];
               fnCheck = function(assetType, isChecked)
               {
                  if (typeof typeMap === "object")
                  {
                     return typeMap[assetType];
                  }
                  return assetType == typeMap;
               };
               break;

            default:
               fnCheck = function(assetType, isChecked)
               {
                  return isChecked;
               };
         }

         for (i = 0; i < len; i++)
         {
            record = recordSet.getRecord(i);
            this.selectedFiles[record.getData("nodeRef")] = checks[i].checked = fnCheck(record.getData("type"), checks[i].checked);
         }
        
         YAHOO.Bubbling.fire("selectedFilesChanged");
      },


      /**
       * YUI WIDGET EVENT HANDLERS
       * Handlers for standard events fired from YUI widgets, e.g. "click"
       */

      /**
       * Show/Hide folders button click handler
       *
       * @method onShowFolders
       * @param e {object} DomEvent
       * @param p_obj {object} Object passed back from addListener method
       */
      onShowFolders: function DL_onShowFolders(e, p_obj)
      {
         this.options.showFolders = !this.options.showFolders;
         this.widgets.showFolders.set("label", this.msg(this.options.showFolders ? "button.folders.hide" : "button.folders.show"));
         this.services.preferences.set(PREF_SHOW_FOLDERS, this.options.showFolders);
         YAHOO.Bubbling.fire("metadataRefresh");
         if (e)
         {
            Event.preventDefault(e);
         }
      },
     
      /**
       * Show/Hide detailed list button click handler
       *
       * @method onSimpleView
       * @param e {object} DomEvent
       * @param p_obj {object} Object passed back from addListener method
       */
      onSimpleView: function DL_onSimpleView(e, p_obj)
      {
         this.options.simpleView = !this.options.simpleView;
         p_obj.set("label", this.msg(this.options.simpleView ? "button.view.detailed" : "button.view.simple"));

         this.services.preferences.set(PREF_SIMPLE_VIEW, this.options.simpleView);

         YAHOO.Bubbling.fire("metadataRefresh");
         Event.preventDefault(e);
      },

      /**
       * Show/Hide detailed list buttongroup click handler
       *
       * @method onSimpleDetailed
       * @param e {object} DomEvent
       * @param p_obj {object} Object passed back from addListener method
       */
      onSimpleDetailed: function DL_onSimpleDetailed(e, p_obj)
      {
         this.options.simpleView = e.newValue.index === 0;
         this.services.preferences.set(PREF_SIMPLE_VIEW, this.options.simpleView);
         YAHOO.Bubbling.fire("metadataRefresh");

         Event.preventDefault(e);
      },
     
      /**
       * Multi-file select button click handler
       *
       * @method onFileSelect
       * @param sType {string} Event type, e.g. "click"
       * @param aArgs {array} Arguments array, [0] = DomEvent, [1] = EventTarget
       * @param p_obj {object} Object passed back from subscribe method
       */
      onFileSelect: function DL_onFileSelect(sType, aArgs, p_obj)
      {
         var domEvent = aArgs[0],
            eventTarget = aArgs[1];

         // Select based upon the className of the clicked item
         this.selectFiles(Alfresco.util.findEventClass(eventTarget));
         Event.preventDefault(domEvent);
      },

      /**
       * Custom event handler to highlight row.
       *
       * @method onEventHighlightRow
       * @param oArgs.event {HTMLEvent} Event object.
       * @param oArgs.target {HTMLElement} Target element.
       */
      onEventHighlightRow: function DL_onEventHighlightRow(oArgs)
      {
         // Call through to get the row highlighted by YUI
         this.widgets.dataTable.onEventHighlightRow.call(this.widgets.dataTable, oArgs);

         // elActions is the element id of the active table cell where we'll inject the actions
         var elActions = Dom.get(this.id + "-actions-" + oArgs.target.id);

         // Inject the correct action elements into the actionsId element
         if (elActions && elActions.firstChild === null)
         {
            // Retrieve the actionSet for this asset
            var record = this.widgets.dataTable.getRecord(oArgs.target.id),
               actionSet = record.getData("actionSet");
           
            // Clone the actionSet template node from the DOM
            var clone = Dom.get(this.id + "-actionSet-" + actionSet).cloneNode(true);
           
            // Token replacement
            clone.innerHTML = YAHOO.lang.substitute(window.unescape(clone.innerHTML), this.getActionUrls(record.getData()));

            // Generate an id
            clone.id = elActions.id + "_a";
           
            // Simple or detailed view
            Dom.addClass(clone, this.options.simpleView ? "simple" : "detailed");
           
            // Trim the items in the clone depending on the user's access
            var userAccess = record.getData("permissions").userAccess,
               actionLabels = record.getData("actionLabels") || {};
           
            // Inject special-case permissions
            if (record.getData("mimetype") in this.options.inlineEditMimetypes)
            {
               userAccess["inline-edit"] = true;
            }
            if (record.getData("onlineEditUrl"))
            {
               userAccess["online-edit"] = true;
            }
            if (this.options.repositoryUrl)
            {
               userAccess.repository = true;
            }
            userAccess.portlet = Alfresco.constants.PORTLET;
           
            // Inject the current filterId to allow filter-scoped actions
            userAccess["filter-" + this.currentFilter.filterId] = true;
           
            // Remove any actions the user doesn't have permission for
            var actions = YAHOO.util.Selector.query("div", clone),
               action, aTag, spanTag, actionPermissions, aP, i, ii, j, jj;
            for (i = 0, ii = actions.length; i < ii; i++)
            {
               action = actions[i];
               aTag = action.firstChild;
               spanTag = aTag.firstChild;
               if (spanTag && actionLabels[action.className])
               {
                  spanTag.innerHTML = $html(actionLabels[action.className]);
               }
              
               if (aTag.rel !== "")
               {
                  actionPermissions = aTag.rel.split(",");
                  for (j = 0, jj = actionPermissions.length; j < jj; j++)
                  {
                     aP = actionPermissions[j];
                     // Support "negative" permissions
                     if ((aP.charAt(0) == "~") ? !!userAccess[aP.substring(1)] : !userAccess[aP])
                     {
                        clone.removeChild(action);
                        break;
                     }
                  }
               }
            }
           
            // Need the "More >" container?
            var splitAt = record.getData("type") == "folder" ? 2 : 3;
            actions = YAHOO.util.Selector.query("div", clone);
            if (actions.length > splitAt + (this.options.simpleView ? 0 : 1))
            {
               var moreContainer = Dom.get(this.id + "-moreActions").cloneNode(true);
               var containerDivs = YAHOO.util.Selector.query("div", moreContainer);
               // Insert the two necessary DIVs before the third action item
               Dom.insertBefore(containerDivs[0], actions[splitAt]);
               Dom.insertBefore(containerDivs[1], actions[splitAt]);
               // Now make action items three onwards children of the 2nd DIV
               var index, moreActions = actions.slice(splitAt);
               for (index in moreActions)
               {
                  if (moreActions.hasOwnProperty(index))
                  {
                     containerDivs[1].appendChild(moreActions[index]);
                  }
               }
            }
           
            elActions.appendChild(clone);
         }
        
         if (this.showingMoreActions)
         {
            this.deferredActionsMenu = elActions;
         }
         else if (!Dom.hasClass(document.body, "masked"))
         {
            this.currentActionsMenu = elActions;
            // Show the actions
            Dom.removeClass(elActions, "hidden");
            this.deferredActionsMenu = null;
         }
      },

      /**
       * The urls to be used when creating links in the action cell
       *
       * @method getActionUrls
       * @param recordData {object} Object literal representing the node
       * @param siteId {string} Optional siteId override for site-based locations
       * @return {object} Object literal containing URLs to be substituted in action placeholders
       */
      getActionUrls: function DL_getActionUrls(recordData, siteId)
      {
         var nodeRef = recordData.isLink ? recordData.linkedNodeRef : recordData.nodeRef,
            nodeRefUri = new Alfresco.util.NodeRef(nodeRef).uri,
            contentUrl = recordData.contentUrl,
            custom = recordData.custom,
            siteObj = YAHOO.lang.isString(siteId) ? { site: siteId } : null,
            fnPageURL = Alfresco.util.bind(function(page)
            {
               return Alfresco.util.siteURL(page, siteObj);
            }, this);

         return (
         {
            downloadUrl: Alfresco.constants.PROXY_URI + contentUrl + "?a=true",
            viewUrl:  Alfresco.constants.PROXY_URI + contentUrl + "\" target=\"_blank",
            documentDetailsUrl: fnPageURL("document-details?nodeRef=" + nodeRef),
            folderDetailsUrl: fnPageURL("folder-details?nodeRef=" + nodeRef),
            folderRulesUrl: fnPageURL("folder-rules?nodeRef=" + nodeRef),
            editMetadataUrl: fnPageURL("edit-metadata?nodeRef=" + nodeRef),
            inlineEditUrl: fnPageURL("inline-edit?nodeRef=" + nodeRef),
            managePermissionsUrl: fnPageURL("manage-permissions?nodeRef=" + nodeRef),
            workingCopyUrl: fnPageURL("document-details?nodeRef=" + (custom.workingCopyNode || nodeRef)),
            viewGoogleDocUrl: custom.googleDocUrl + "\" target=\"_blank",
            originalUrl: fnPageURL("document-details?nodeRef=" + (custom.workingCopyOriginal || nodeRef)),
            explorerViewUrl: $combine(this.options.repositoryUrl, "/n/showSpaceDetails/", nodeRefUri) + "\" target=\"_blank",
            sourceRepositoryUrl: this.viewInSourceRepositoryURL(recordData) + "\" target=\"_blank"
         });
      },

      /**
       * Custom event handler to unhighlight row.
       *
       * @method onEventUnhighlightRow
       * @param oArgs.event {HTMLEvent} Event object.
       * @param oArgs.target {HTMLElement} Target element.
       */
      onEventUnhighlightRow: function DL_onEventUnhighlightRow(oArgs)
      {
         // Call through to get the row unhighlighted by YUI
         this.widgets.dataTable.onEventUnhighlightRow.call(this.widgets.dataTable, oArgs);

         var elActions = Dom.get(this.id + "-actions-" + (oArgs.target.id));

         // Don't hide unless the More Actions drop-down is showing, or a dialog mask is present
         if (elActions && !this.showingMoreActions || Dom.hasClass(document.body, "masked"))
         {
            // Just hide the action links, rather than removing them from the DOM
            Dom.addClass(elActions, "hidden");
            this.deferredActionsMenu = null;
         }
      },


      /**
       * BUBBLING LIBRARY EVENT HANDLERS FOR ACTIONS
       * Disconnected event handlers for action event notification
       */

      /**
       * Show more actions pop-up.
       *
       * @method onActionShowMore
       * @param asset {object} Unused
       * @param elMore {element} DOM Element of "More Actions" link
       */
      onActionShowMore: function DL_onActionShowMore(asset, elMore)
      {
         var me = this;
        
         // Fix "More Actions" hover style
         Dom.addClass(elMore.firstChild, "highlighted");
        
         // Get the pop-up div, sibling of the "More Actions" link
         var elMoreActions = Dom.getNextSibling(elMore);
         Dom.removeClass(elMoreActions, "hidden");
         me.showingMoreActions = true;
        
         // Hide pop-up timer function
         var fnHidePopup = function DL_oASM_fnHidePopup()
         {
            // Need to rely on the "elMoreActions" enclosed variable, as MSIE doesn't support
            // parameter passing for timer functions.
            Event.removeListener(elMoreActions, "mouseover");
            Event.removeListener(elMoreActions, "mouseout");
            Dom.removeClass(elMore.firstChild, "highlighted");
            Dom.addClass(elMoreActions, "hidden");
            me.showingMoreActions = false;
            if (me.deferredActionsMenu !== null)
            {
               Dom.addClass(me.currentActionsMenu, "hidden");
               me.currentActionsMenu = me.deferredActionsMenu;
               me.deferredActionsMenu = null;
               Dom.removeClass(me.currentActionsMenu, "hidden");
            }
         };

         // Initial after-click hide timer - 5x the mouseOut timer delay
         if (elMoreActions.hideTimerId)
         {
            window.clearTimeout(elMoreActions.hideTimerId);
         }
         elMoreActions.hideTimerId = window.setTimeout(fnHidePopup, me.options.actionsPopupTimeout * 5);
        
         // Mouse over handler
         var onMouseOver = function DLSM_onMouseOver(e, obj)
         {
            // Clear any existing hide timer
            if (obj.hideTimerId)
            {
               window.clearTimeout(obj.hideTimerId);
               obj.hideTimerId = null;
            }
         };
        
         // Mouse out handler
         var onMouseOut = function DLSM_onMouseOut(e, obj)
         {
            var elTarget = Event.getTarget(e);
            var related = elTarget.relatedTarget;

            // In some cases we should ignore this mouseout event
            if ((related != obj) && (!Dom.isAncestor(obj, related)))
            {
               if (obj.hideTimerId)
               {
                  window.clearTimeout(obj.hideTimerId);
               }
               obj.hideTimerId = window.setTimeout(fnHidePopup, me.options.actionsPopupTimeout);
            }
         };
        
         Event.on(elMoreActions, "mouseover", onMouseOver, elMoreActions);
         Event.on(elMoreActions, "mouseout", onMouseOut, elMoreActions);
      },
     
      /**
       * Edit Offline.
       *
       * @override
       * @method onActionEditOffline
       * @param asset {object} Object literal representing file or folder to be actioned
       */
      onActionEditOffline: function DL_onActionEditOffline(asset)
      {
         if (!this.state.actionEditOfflineActive)
         {
            // Make sure we don't call edit offline twice
            this.state.actionEditOfflineActive = true;

            var nodeRef = new Alfresco.util.NodeRef(asset.nodeRef),
               displayName = asset.displayName;

            this.modules.actions.genericAction(
            {
               success:
               {
                  event:
                  {
                     name: "changeFilter",
                     obj:
                     {
                        filterId: "editingMe"
                     }
                  },
                  callback:
                  {
                     fn: function DL_oAEO_success(data)
                     {
                        this.state.actionEditOfflineActive = false;
                        this.options.highlightFile = displayName;

                        // The filterChanged event causes the DocList to update, so we need to run these functions afterwards
                        var fnAfterUpdate = function DL_oAEO_success_afterUpdate()
                        {
                           var downloadUrl = Alfresco.constants.PROXY_URI + data.json.results[0].downloadUrl;
                           if (YAHOO.env.ua.ie > 6)
                           {
                              // MSIE7 blocks the download and gets the wrong URL in the "manual download bar"
                              Alfresco.util.PopupManager.displayPrompt(
                              {
                                 title: this.msg("message.edit-offline.success", displayName),
                                 text: this.msg("message.edit-offline.success.ie7"),
                                 buttons: [
                                 {
                                    text: this.msg("button.download"),
                                    handler: function DL_oAEO_success_download()
                                    {
                                       window.location = downloadUrl;
                                       this.destroy();
                                    },
                                    isDefault: true
                                 },
                                 {
                                    text: this.msg("button.close"),
                                    handler: function DL_oAEO_success_close()
                                    {
                                       this.destroy();
                                    }
                                 }]
                              });
                           }
                           else
                           {
                              Alfresco.util.PopupManager.displayMessage(
                              {
                                 text: this.msg("message.edit-offline.success", displayName)
                              });
                              // Kick off the download 3 seconds after the confirmation message
                              YAHOO.lang.later(3000, this, function()
                              {
                                 window.location = downloadUrl;
                              });
                           }
                        };
                        this.afterDocListUpdate.push(fnAfterUpdate);
                     },
                     scope: this
                  }
               },
               failure:
               {
                  callback:
                  {
                     fn: function DL_oAEO_failure()
                     {
                        this.state.actionEditOfflineActive = false;
                     },
                     scope: this
                  },
                  message: this.msg("message.edit-offline.failure", displayName)
               },
               webscript:
               {
                  method: Alfresco.util.Ajax.POST,
                  name: "checkout/node/{nodeRef}",
                  params:
                  {
                     nodeRef: nodeRef.uri
                  }
               }
            });
         }
      },
     
      /**
       * Checkout to Google Docs.
       *
       * @override
       * @method onActionCheckoutToGoogleDocs
       * @param asset {object} Object literal representing file or folder to be actioned
       */
      onActionCheckoutToGoogleDocs: function DL_onActionCheckoutToGoogleDocs(asset)
      {
         var displayName = asset.displayName,
            nodeRef = new Alfresco.util.NodeRef(asset.nodeRef),
            path = asset.location.path,
            fileName = asset.fileName;
           
         var progressPopup = Alfresco.util.PopupManager.displayMessage(
         {
            displayTime: 0,
            effect: null,
            text: this.msg("message.checkout-google.inprogress", displayName)
         });

         this.modules.actions.genericAction(
         {
            success:
            {
               event:
               {
                  name: "changeFilter",
                  obj:
                  {
                     filterId: "editingMe"
                  }
               },
               callback:
               {
                  fn: function DL_oACTGD_success(data)
                  {
                     progressPopup.destroy();
                     this.options.highlightFile = displayName;
                    
                     // The filterChanged event causes the DocList to update, so we need to run these functions afterwards
                     var fnAfterUpdate = function DL_oACTGD_success_afterUpdate()
                     {
                        Alfresco.util.PopupManager.displayMessage(
                        {
                           text: this.msg("message.checkout-google.success", displayName)
                        });
                     };
                     this.afterDocListUpdate.push(fnAfterUpdate);
                  },
                  scope: this
               },
               activity:
               {
                  siteId: this.options.siteId,
                  activityType: "google-docs-checkout",
                  page: "document-details",
                  activityData:
                  {
                     fileName: fileName,
                     path: path,
                     nodeRef: nodeRef.toString()
                  }
               }
            },
            failure:
            {
               callback:
               {
                  fn: function DocumentActions_oAEO_failure(data)
                  {
                     progressPopup.destroy();
                     Alfresco.util.PopupManager.displayMessage(
                     {
                        text: this.msg("message.checkout-google.failure", displayName)
                     });
                  },
                  scope: this
               }
            },
            webscript:
            {
               method: Alfresco.util.Ajax.POST,
               name: "checkout/node/{nodeRef}",
               params:
               {
                  nodeRef: nodeRef.uri
               }
            }
         });
      },

      /**
       * Check in a new version from Google Docs.
       *
       * @override
       * @method onActionCheckinFromGoogleDocs
       * @param asset {object} Object literal representing the file to be actioned upon
       */
      onActionCheckinFromGoogleDocs: function DL_onActionCheckinFromGoogleDocs(asset)
      {
         var displayName = asset.displayName,
            nodeRef = new Alfresco.util.NodeRef(asset.nodeRef),
            originalNodeRef = new Alfresco.util.NodeRef(asset.custom.workingCopyOriginal),
            path = asset.location.path;

         var progressPopup = Alfresco.util.PopupManager.displayMessage(
         {
            displayTime: 0,
            effect: null,
            text: this.msg("message.checkin-google.inprogress", displayName)
         });

         this.modules.actions.genericAction(
         {
            success:
            {
               event:
               {
                  name: "metadataRefresh"
               },
               callback:
               {
                  fn: function DL_oACFGD_success(data)
                  {
                     progressPopup.destroy();
                    
                     // The filterChanged event causes the DocList to update, so we need to run these functions afterwards

                     var fnAfterUpdate = function DL_oACTGD_success_afterUpdate()
                     {
                        Alfresco.util.PopupManager.displayMessage(
                        {
                           text: this.msg("message.checkin-google.success", displayName)
                        });
                     };
                     this.afterDocListUpdate.push(fnAfterUpdate);
                  },
                  scope: this
               },
               activity:
               {
                  siteId: this.options.siteId,
                  activityType: "google-docs-checkin",
                  page: "document-details",
                  activityData:
                  {
                     fileName: displayName,
                     path: path,
                     nodeRef: originalNodeRef.toString()
                  }
               }
            },
            failure:
            {
               message: this.msg("message.checkin-google.failure", displayName)
            },
            webscript:
            {
               method: Alfresco.util.Ajax.POST,
               name: "checkin/node/{nodeRef}",
               params:
               {
                  nodeRef: nodeRef.uri
               }
            }
         });
      },

      /**
       * BUBBLING LIBRARY EVENT HANDLERS FOR PAGE EVENTS
       * Disconnected event handlers for inter-component event notification
       */
     
      /**
       * Generic file action event handler
       *
       * @method onFileAction
       * @param layer {object} Event fired
       * @param args {array} Event parameters (depends on event type)
       */
      onFileAction: function DL_onFileAction(layer, args)
      {
         var obj = args[1];
         if (obj)
         {
            if (!obj.multiple)
            {
               this._updateDocList.call(this);
            }
         }
      },

      /**
       * File or folder renamed event handler
       *
       * @method onFileRenamed
       * @param layer {object} Event fired
       * @param args {array} Event parameters (depends on event type)
       */
      onFileRenamed: function DL_onFileRenamed(layer, args)
      {
         var obj = args[1];
         if (obj && (obj.file !== null))
         {
            var recordFound = this._findRecordByParameter(obj.file.nodeRef, "nodeRef");
            if (recordFound !== null)
            {
               this.widgets.dataTable.updateRow(recordFound, obj.file);
               var el = this.widgets.dataTable.getTrEl(recordFound);
               Alfresco.util.Anim.pulse(el);
            }
         }
      },

      /**
       * DocList Refresh Required event handler
       *
       * @method onDocListRefresh
       * @param layer {object} Event fired (unused)
       * @param args {array} Event parameters (unused)
       */
      onDocListRefresh: function DL_onDocListRefresh(layer, args)
      {
         var obj = args[1];
         if (obj && (obj.highlightFile !== null))
         {
            this.options.highlightFile = obj.highlightFile;
         }
         this._updateDocList.call(this);
      },

      /**
       * DocList View change filter request event handler
       *
       * @method onChangeFilter
       * @param layer {object} Event fired (unused)
       * @param args {array} Event parameters (new filterId)
       */
      onChangeFilter: function DL_onChangeFilter(layer, args)
      {
         var obj = args[1];

         if ((obj !== null) && (obj.filterId !== null))
         {
            // Should be a filter in the arguments
            var filter = Alfresco.util.cleanBubblingObject(obj),
               strFilter = window.escape(obj.filterId) + (typeof obj.filterData !== "undefined" ? "|" + window.escape(obj.filterData) : "");
           
            Alfresco.logger.debug("DL_onChangeFilter: ", filter);

            var objNav =
            {
               filter: strFilter
            };
           
            // Initial navigation won't fire the History event
            if (obj.doclistFirstTimeNav)
            {
               this._updateDocList.call(this,
               {
                  filter: filter,
                  page: this.currentPage
               });
            }
            else
            {
               if (this.options.usePagination)
               {
                  this.currentPage = 1;
                  objNav.page = "1";
               }

               Alfresco.logger.debug("DL_onChangeFilter: objNav = ", objNav);

               // Do we think the history state will change?
               if (this.options.highlightFile && objNav.filter == YAHOO.util.History.getCurrentState("filter"))
               {
                  YAHOO.Bubbling.fire("highlightFile",
                  {
                     fileName: this.options.highlightFile
                  });
               }
              
               YAHOO.util.History.multiNavigate(objNav);
            }
         }
      },

      /**
       * DocList View Filter changed event handler
       *
       * @method onFilterChanged
       * @param layer {object} Event fired (unused)
       * @param args {array} Event parameters (new filterId)
       */
      onFilterChanged: function DL_onFilterChanged(layer, args)
      {
         var obj = args[1];
         if ((obj !== null) && (obj.filterId !== null))
         {
            obj.filterOwner = obj.filterOwner || Alfresco.util.FilterManager.getOwner(obj.filterId);

            // Should be a filterId in the arguments
            this.currentFilter = Alfresco.util.cleanBubblingObject(obj);
            Alfresco.logger.debug("DL_onFilterChanged: ", this.currentFilter);
         }
      },

      /**
       * Highlight file event handler
       * Used when a component (including the DocList itself on loading) wants to scroll to and highlight a file
       *
       * @method onHighlightFile
       * @param layer {object} Event fired (unused)
       * @param args {array} Event parameters (filename to be highlighted)
       */
      onHighlightFile: function DL_onHighlightFile(layer, args)
      {
         var obj = args[1];
         if ((obj !== null) && (obj.fileName !== null))
         {
            Alfresco.logger.debug("DL_onHighlightFile: ", obj.fileName);
            var recordFound = this._findRecordByParameter(obj.fileName, "displayName");
            if (recordFound !== null)
            {
               // Scroll the record into view and highlight it
               var el = this.widgets.dataTable.getTrEl(recordFound);
               var yPos = Dom.getY(el);
               if (YAHOO.env.ua.ie > 0)
               {
                  yPos = yPos - (document.body.clientHeight / 3);
               }
               else
               {
                  yPos = yPos - (window.innerHeight / 3);
               }
               window.scrollTo(0, yPos);
               Alfresco.util.Anim.pulse(el);
               this.options.highlightFile = null;

               // Select the file
               Dom.get("checkbox-" + recordFound.getId()).checked = true;
               this.selectedFiles[recordFound.getData("nodeRef")] = true;
               YAHOO.Bubbling.fire("selectedFilesChanged");
            }
         }
      },

      /**
       * Deactivate All Controls event handler
       *
       * @method onDeactivateAllControls
       * @param layer {object} Event fired
       * @param args {array} Event parameters (depends on event type)
       */
      onDeactivateAllControls: function DL_onDeactivateAllControls(layer, args)
      {
         var index, fnDisable = Alfresco.util.disableYUIButton;
         for (index in this.widgets)
         {
            if (this.widgets.hasOwnProperty(index))
            {
               fnDisable(this.widgets[index]);
            }
         }
      },
     
      /**
       * Deactivate Dynamic Controls event handler
       * Only deactivates specifically defined controls.
       *
       * @method onDeactivateDynamicControls
       * @param layer {object} Event fired
       * @param args {array} Event parameters (depends on event type)
       */
      onDeactivateDynamicControls: function DL_onDeactivateDynamicControls(layer, args)
      {
         var index, fnDisable = Alfresco.util.disableYUIButton;
         for (index in this.dynamicControls)
         {
            if (this.dynamicControls.hasOwnProperty(index))
            {
               fnDisable(this.dynamicControls[index]);
            }
         }
      },
     
      /**
       * Activate Dynamic Controls event handler
       * (Re-)Activates controls taking part in dynamic deactivation
       *
       * @method onActivateDynamicControls
       * @param layer {object} Event fired
       * @param args {array} Event parameters (depends on event type)
       */
      onActivateDynamicControls: function DL_onActivateDynamicControls(layer, args)
      {
         var index, fnEnable = Alfresco.util.enableYUIButton;
         for (index in this.dynamicControls)
         {
            if (this.dynamicControls.hasOwnProperty(index))
            {
               fnEnable(this.dynamicControls[index]);
            }
         }
      },
     
      /**
       * Favourite document event handler
       *
       * @method onFavouriteDocument
       * @param row {HTMLElement} DOM reference to a TR element (or child thereof)
       */
      onFavouriteDocument: function DL_onFavouriteDocument(row)
      {
         this._favouriteHandler(row, Alfresco.service.Preferences.FAVOURITE_DOCUMENTS);
      },

      /**
       * Favourite folder event handler
       *
       * @method onFavouriteFolder
       * @param row {HTMLElement} DOM reference to a TR element (or child thereof)
       */
      onFavouriteFolder: function DL_onFavouriteFolder(row)
      {
         this._favouriteHandler(row, Alfresco.service.Preferences.FAVOURITE_FOLDERS);
      },
     

      /**
       * PRIVATE FUNCTIONS
       */

      /**
       * Handler to set/reset favourite for document or folder
       *
       * @method _favouriteHandler
       * @private
       * @param row {HTMLElement} DOM reference to a TR element (or child thereof)
       * @param prefKey {String} The preferences key
       */
      _favouriteHandler: function DL__favouriteHandler(row, prefKey)
      {
         var record = this.widgets.dataTable.getRecord(row),
            file = record.getData(),
            nodeRef = file.nodeRef;
        
         file.isFavourite = !file.isFavourite;
         this.widgets.dataTable.updateRow(record, file);
              
         var responseConfig =
         {
            failureCallback:
            {
               fn: function DL_oFD_failure(event, p_oRecord)
               {
                  // Reset the flag to it's previous state
                  var file = p_oRecord.getData();
                  file.isFavourite = !file.isFavourite;
                  this.widgets.dataTable.updateRow(p_oRecord, file);
                  Alfresco.util.PopupManager.displayPrompt(
                  {
                     text: this.msg("message.favourite.failure", file.displayName)
                  });
               },
               scope: this,
               obj: record
            }
         };

         var fnPref = file.isFavourite ? "add" : "remove";
         this.services.preferences[fnPref].call(this.services.preferences, prefKey, nodeRef, responseConfig);
      },

      /**
       * Resets the YUI DataTable errors to our custom messages
       * NOTE: Scope could be YAHOO.widget.DataTable, so can't use "this"
       *
       * @method _setDefaultDataTableErrors
       * @param dataTable {object} Instance of the DataTable
       */
      _setDefaultDataTableErrors: function DL__setDefaultDataTableErrors(dataTable)
      {
         var msg = Alfresco.util.message;
         dataTable.set("MSG_EMPTY", msg("message.empty", "Alfresco.DocumentList"));
         dataTable.set("MSG_ERROR", msg("message.error", "Alfresco.DocumentList"));
      },
     
      /**
       * Updates document list by calling data webscript with current site and path
       *
       * @method _updateDocList
       * @param p_obj.filter {object} Optional filter to navigate with
       * @param p_obj.page {string} Optional page to navigate to (defaults to this.currentPage)
       */
      _updateDocList: function DL__updateDocList(p_obj)
      {
         p_obj = p_obj || {};
         Alfresco.logger.debug("DL__updateDocList: ", p_obj.filter, p_obj.page);
         var successFilter = YAHOO.lang.merge({}, p_obj.filter !== undefined ? p_obj.filter : this.currentFilter),
            successPage = p_obj.page !== undefined ? p_obj.page : this.currentPage,
            loadingMessage = null,
            timerShowLoadingMessage = null,
            me = this,
            params =
            {
               filter: successFilter,
               page: successPage
            };
         successFilter.doclistFirstTimeNav = false;
        
         // Clear the current document list if the data webscript is taking too long
         var fnShowLoadingMessage = function DL_fnShowLoadingMessage()
         {
            Alfresco.logger.debug("DL__uDL_fnShowLoadingMessage: slow data webscript detected.");
            // Check the timer still exists. This is to prevent IE firing the event after we cancelled it. Which is "useful".
            if (timerShowLoadingMessage)
            {
               loadingMessage = Alfresco.util.PopupManager.displayMessage(
               {
                  displayTime: 0,
                  text: '<span class="wait">' + $html(this.msg("message.loading")) + '</span>',
                  noEscape: true
               });
              
               if (YAHOO.env.ua.ie > 0)
               {
                  this.loadingMessageShowing = true;
               }
               else
               {
                  loadingMessage.showEvent.subscribe(function()
                  {
                     this.loadingMessageShowing = true;
                  }, this, true);
               }
            }
         };
        
         // Reset the custom error messages
         this._setDefaultDataTableErrors(this.widgets.dataTable);
        
         // Reset preview tooltips array
         this.previewTooltips = [];
        
         // More Actions menu no longer relevant
         this.showingMoreActions = false;
        
         // Slow data webscript message
         this.loadingMessageShowing = false;
         timerShowLoadingMessage = YAHOO.lang.later(this.options.loadingMessageDelay, this, fnShowLoadingMessage);
        
         var destroyLoaderMessage = function DL__uDL_destroyLoaderMessage()
         {
            if (timerShowLoadingMessage)
            {
               // Stop the "slow loading" timed function
               timerShowLoadingMessage.cancel();
               timerShowLoadingMessage = null;
            }

            if (loadingMessage)
            {
               if (this.loadingMessageShowing)
               {
                  // Safe to destroy
                  loadingMessage.destroy();
                  loadingMessage = null;
               }
               else
               {
                  // Wait and try again later. Scope doesn't get set correctly with "this"
                  YAHOO.lang.later(100, me, destroyLoaderMessage);
               }
            }
         };
        
         var successHandler = function DL__uDL_successHandler(sRequest, oResponse, oPayload)
         {
            destroyLoaderMessage();
            // Updating the Doclist may change the file selection
            var fnAfterUpdate = function DL__uDL_sH_fnAfterUpdate()
            {
               YAHOO.Bubbling.fire("activateDynamicControls");
               YAHOO.Bubbling.fire("selectedFilesChanged");
            };
            this.afterDocListUpdate.push(fnAfterUpdate);
           
            Alfresco.logger.debug("currentFilter was:", this.currentFilter, "now:", successFilter);
            Alfresco.logger.debug("currentPage was [" + this.currentPage + "] now [" + successPage + "]");

            this.currentFilter = successFilter;
            this.currentPage = successPage;
            if (successFilter.filterId == "path")
            {
               Alfresco.logger.debug("currentPath was [" + this.currentPath + "] now [" + successFilter.filterData + "]");
               this.currentPath = successFilter.filterData;
            }
            YAHOO.Bubbling.fire("filterChanged", successFilter);
            this.widgets.dataTable.onDataReturnInitializeTable.call(this.widgets.dataTable, sRequest, oResponse, oPayload);
         };
        
         var failureHandler = function DL__uDL_failureHandler(sRequest, oResponse)
         {
            destroyLoaderMessage();
            // Clear out deferred functions
            this.afterDocListUpdate = [];

            if (oResponse.status == 401)
            {
               // Our session has likely timed-out, so refresh to offer the login page
               window.location.reload(true);
            }
            else
            {
               try
               {
                  if (oResponse.status == 404)
                  {
                     // Folder not found (via the HTTP "404 Not Found" response) - deactivate dynamic controls only
                     YAHOO.Bubbling.fire("deactivateDynamicControls");
                  }
                  else
                  {
                     // Site or container not found (e.g. via the HTTP "410 Gone" response) or more serious - deactivate all controls
                     YAHOO.Bubbling.fire("deactivateAllControls");
                  }

                  var fnAfterFailedUpdate = function DL__uDL_failureHandler_fnAfterUpdate(responseMsg)
                  {
                     return function DL__uDL_failureHandler_afterUpdate()
                     {
                        this.widgets.paginator.setState(
                        {
                           totalRecords: 0
                        });
                        this.widgets.paginator.render();
                        this.widgets.dataTable.set("MSG_ERROR", responseMsg);
                        this.widgets.dataTable.showTableMessage(responseMsg, YAHOO.widget.DataTable.CLASS_ERROR);
                     };
                  };

                  this.afterDocListUpdate.push(fnAfterFailedUpdate(YAHOO.lang.JSON.parse(oResponse.responseText).message));
                  this.widgets.dataTable.initializeTable();
                  this.widgets.dataTable.render();
               }
               catch(e)
               {
                  Alfresco.logger.error(e);
                  this._setDefaultDataTableErrors(this.widgets.dataTable);
               }
            }
         };
        
         // Update the DataSource
         if (params.filter && params.filter.filterId == "path")
         {
            params.path = params.filter.filterData;
         }
         //list multiple tags if appropriate
         // Change by Nathan 03-09-2011
         // Code from alfresco forum thread "possible to filter by multiple tags?"
         // Also changed below (~Line 2761)
         if (this.currentFilter.filterId == "tag" && successFilter.filterId == "tag") {
           
            //allow user to remove tags by selecting them a second time
            if (this.currentFilter.filterData.indexOf(successFilter.filterData) != -1 || successFilter.filterData.length == 0) {
               var filtersArray = this.currentFilter.filterData.split(", ");
               var newFilter = "";
               for (var lcv = 0; lcv < filtersArray.length; lcv++) {
                  if (filtersArray[lcv] != successFilter.filterData) {
                     newFilter += filtersArray[lcv];
                     if(lcv < filtersArray.length - 1 && filtersArray[lcv + 1] != successFilter.filterData || lcv < filtersArray.length - 2) {
                        newFilter += ", ";
                     }
                  }
               }
               successFilter.filterData = newFilter;
            }
            else {
               if (this.currentFilter.filterData != "") {
                  successFilter.filterData += ", " + this.currentFilter.filterData;
               }
            }
         }
         params.filter.filterData = successFilter.filterData
         //this.currentFilter.filterData = successFilter.filterData
        
         var requestParams = this._buildDocListParams(params);
         Alfresco.logger.debug("DataSource requestParams: ", requestParams);
         this.widgets.dataSource.sendRequest(requestParams,
         {
            success: successHandler,
            failure: failureHandler,
            scope: this
         });
      },

      /**
       * Build URI parameter string for doclist JSON data webscript
       *
       * @method _buildDocListParams
       * @param p_obj.page {string} Page number
       * @param p_obj.pageSize {string} Number of items per page
       * @param p_obj.path {string} Path to query
       * @param p_obj.type {string} Filetype to filter: "all", "documents", "folders"
       * @param p_obj.site {string} Current site
       * @param p_obj.container {string} Current container
       * @param p_obj.filter {string} Current filter
       */
      _buildDocListParams: function DL__buildDocListParams(p_obj)
      {
         // Essential defaults
         var obj =
         {
            path: this.currentPath,
            type: this.options.showFolders ? "all" : "documents",
            site: this.options.siteId,
            container: this.options.containerId,
            filter: this.currentFilter
         };
        
         // Pagination in use?
         if (this.options.usePagination)
         {
            obj.page = this.widgets.paginator.getCurrentPage() || this.currentPage;
            obj.pageSize = this.widgets.paginator.getRowsPerPage();
         }

         // Passed-in overrides
         if (typeof p_obj == "object")
         {
            obj = YAHOO.lang.merge(obj, p_obj);
         }

         // Build the URI stem
         var params = YAHOO.lang.substitute("{type}/site/{site}/{container}" + (obj.filter.filterId == "path" ? "{path}" : ""),
         {
            type: encodeURIComponent(obj.type),
            site: encodeURIComponent(obj.site),
            container: encodeURIComponent(obj.container),
            path: $combine("/", Alfresco.util.encodeURIPath(obj.path))
         });

         // Filter parameters
         params += "?filter=" + encodeURIComponent(obj.filter.filterId);
         if (obj.filter.filterData != "" && obj.filter.filterId !== "path")
         {
            params += "&filterData=" + encodeURIComponent(obj.filter.filterData);            
         }
        
        
         // Paging parameters
         if (this.options.usePagination)
         {
            params += "&size=" + obj.pageSize  + "&pos=" + obj.page;
         }

         // No-cache
         params += "&noCache=" + new Date().getTime();

         return params;
      },
      
      /**
       * Searches the current recordSet for a record with the given parameter value
       *
       * @private
       * @method _findRecordByParameter
       * @param p_value {string} Value to find
       * @param p_parameter {string} Parameter to look for the value in
       */
      _findRecordByParameter: function DL__findRecordByParameter(p_value, p_parameter)
      {
        var recordSet = this.widgets.dataTable.getRecordSet();
        for (var i = 0, j = recordSet.getLength(); i < j; i++)
        {
           if (recordSet.getRecord(i).getData(p_parameter) == p_value)
           {
              return recordSet.getRecord(i);
           }
        }
        return null;
      }
   }, true);
})();

nicholasellis
Champ in-the-making
Champ in-the-making
Sirround,

Brilliant, that's great and extremely helpful. Also, thanks for clarifying the file name issue above. Cheers!

N

zbennett
Champ in-the-making
Champ in-the-making
Hi guys,

Has anyone had a chance to test this in 3.4d or 3.4e?  I'm starting to play around with my Alfresco machine again and I'm thinking about starting from a fresh slate…

Thanks!

zbennett
Champ in-the-making
Champ in-the-making
So I have a fresh copy of 3.4.d CE installed and I'm playing around with documentlist.js again!  I'm just refreshing my memory at this point, but I did implement a hybrid of the code that sirround and I have been kicking around in this thread.  So far, so good…  Some quick tests show me that the basics work, but it's time to call it a day for now.

I hope to get this production-ready in the near future (so keep your eyes on this thread for updates).  I'll be sure to post some sort of tutorial/summary when I'm happy with the way it works.

nicholasellis
Champ in-the-making
Champ in-the-making
Hey zbennet, came back months later to check on this. Any progress with the the multi-tagging since your last post?

zbennett
Champ in-the-making
Champ in-the-making
Hi nicholas,

I started making some headway earlier this year, but then the autumn came!  To be honest, the power went out here about 2 months ago and I never even had a chance to turn my Alfresco machine back on!  I expect I'll take another stab at it in the spring, but I'm going to be waaaaay too busy until then.

Sorry Smiley Sad