1 /**
  2  * Copyright (C) 2005-2010 Alfresco Software Limited.
  3  *
  4  * This file is part of Alfresco
  5  *
  6  * Alfresco is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU Lesser General Public License as published by
  8  * the Free Software Foundation, either version 3 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * Alfresco is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU Lesser General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU Lesser General Public License
 17  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 18  */
 19  
 20 /**
 21  * Search component.
 22  * 
 23  * @namespace Alfresco
 24  * @class Alfresco.Search
 25  */
 26 (function()
 27 {
 28    /**
 29     * YUI Library aliases
 30     */
 31    var Dom = YAHOO.util.Dom,
 32        Event = YAHOO.util.Event;
 33 
 34    /**
 35     * Alfresco Slingshot aliases
 36     */
 37    var $html = Alfresco.util.encodeHTML;
 38 
 39    /**
 40     * Search constructor.
 41     * 
 42     * @param {String} htmlId The HTML id of the parent element
 43     * @return {Alfresco.Search} The new Search instance
 44     * @constructor
 45     */
 46    Alfresco.Search = function(htmlId)
 47    {
 48       Alfresco.Search.superclass.constructor.call(this, "Alfresco.Search", htmlId, ["button", "container", "datasource", "datatable", "paginator", "json"]);
 49       
 50       // Decoupled event listeners
 51       YAHOO.Bubbling.on("onSearch", this.onSearch, this);
 52       
 53       return this;
 54    };
 55    
 56    YAHOO.extend(Alfresco.Search, Alfresco.component.Base,
 57    {
 58       /**
 59        * Object container for initialization options
 60        *
 61        * @property options
 62        * @type object
 63        */
 64       options:
 65       {
 66          /**
 67           * Current siteId
 68           * 
 69           * @property siteId
 70           * @type string
 71           */
 72          siteId: "",
 73          
 74          /**
 75           * Current site title
 76           * 
 77           * @property siteTitle
 78           * @type string
 79           */
 80          siteTitle: "",
 81          
 82          /**
 83           * Maximum number of results displayed.
 84           * 
 85           * @property maxSearchResults
 86           * @type int
 87           * @default 250
 88           */
 89          maxSearchResults: 250,
 90          
 91          /**
 92           * Results page size.
 93           * 
 94           * @property pageSize
 95           * @type int
 96           * @default 50
 97           */
 98          pageSize: 50,
 99          
100          /**
101           * Search term to use for the initial search
102           * @property initialSearchTerm
103           * @type string
104           * @default ""
105           */
106          initialSearchTerm: "",
107          
108          /**
109           * Search tag to use for the initial search
110           * @property initialSearchTag
111           * @type string
112           * @default ""
113           */
114          initialSearchTag: "",
115          
116          /**
117           * States whether all sites should be searched.
118           * 
119           * @property initialSearchAllSites
120           * @type boolean
121           */
122          initialSearchAllSites: true,
123          
124          /**
125           * States whether repository should be searched.
126           * This is in preference to current or all sites.
127           * 
128           * @property initialSearchRepository
129           * @type boolean
130           */
131          initialSearchRepository: false,
132          
133          /**
134           * Sort property to use for the initial search.
135           * Empty default value will use score relevance default.
136           * @property initialSort
137           * @type string
138           * @default ""
139           */
140          initialSort: "",
141          
142          /**
143           * Advanced Search query - forms data json format based search.
144           * @property searchQuery
145           * @type string
146           * @default ""
147           */
148          searchQuery: "",
149          
150          /**
151           * Number of characters required for a search.
152           *
153           * @property minSearchTermLength
154           * @type int
155           * @default 1
156           */
157          minSearchTermLength: 1
158       },
159       
160       /**
161        * Search term used for the last search.
162        */
163       searchTerm: "",
164       
165       /**
166        * Search tag used for the last search.
167        */
168       searchTag: "",
169       
170       /**
171        * Whether the search was over all sites or just the current one
172        */
173       searchAllSites: true,
174       
175       /**
176        * Whether the search is over the entire repository - in preference to site or all sites
177        */
178       searchRepository: false,
179       
180       /**
181        * Search sort used for the last search.
182        */
183       searchSort: "",
184       
185       /**
186        * Number of search results.
187        */
188       resultsCount: 0,
189       
190       /**
191        * Current visible page index - counts from 1
192        */
193       currentPage: 1,
194       
195       /**
196        * True if there are more results than the ones listed in the table.
197        */
198       hasMoreResults: false,
199       
200       /**
201        * Fired by YUI when parent element is available for scripting.
202        * Component initialisation, including instantiation of YUI widgets and event listener binding.
203        *
204        * @method onReady
205        */
206       onReady: function Search_onReady()
207       {
208          var me = this;
209          
210          // DataSource definition
211          var uriSearchResults = Alfresco.constants.PROXY_URI_RELATIVE + "slingshot/search?";
212          this.widgets.dataSource = new YAHOO.util.DataSource(uriSearchResults,
213          {
214             responseType: YAHOO.util.DataSource.TYPE_JSON,
215             connXhrMode: "queueRequests",
216             responseSchema:
217             {
218                 resultsList: "items"
219             }
220          });
221          
222          // YUI Paginator definition
223          var handlePagination = function Search_handlePagination(state, me)
224          {
225             me.currentPage = state.page;
226             me.widgets.paginator.setState(state);
227          };
228          this.widgets.paginator = new YAHOO.widget.Paginator(
229          {
230             containers: [this.id + "-paginator-top", this.id + "-paginator-bottom"],
231             rowsPerPage: this.options.pageSize,
232             initialPage: 1,
233             template: this.msg("pagination.template"),
234             pageReportTemplate: this.msg("pagination.template.page-report"),
235             previousPageLinkLabel: this.msg("pagination.previousPageLinkLabel"),
236             nextPageLinkLabel: this.msg("pagination.nextPageLinkLabel")
237          });
238          this.widgets.paginator.subscribe("changeRequest", handlePagination, this);
239          
240          // setup of the datatable.
241          this._setupDataTable();
242          
243          // set initial value and register the "enter" event on the search text field
244          var queryInput = Dom.get(this.id + "-search-text");
245          queryInput.value = this.options.initialSearchTerm;
246          
247          this.widgets.enterListener = new YAHOO.util.KeyListener(queryInput, 
248          {
249             keys: YAHOO.util.KeyListener.KEY.ENTER
250          }, 
251          {
252             fn: me._searchEnterHandler,
253             scope: this,
254             correctScope: true
255          }, "keydown").enable();
256          
257          // trigger the initial search
258          YAHOO.Bubbling.fire("onSearch",
259          {
260             searchTerm: this.options.initialSearchTerm,
261             searchTag: this.options.initialSearchTag,
262             searchSort: this.options.initialSort,
263             searchAllSites: this.options.initialSearchAllSites,
264             searchRepository: this.options.initialSearchRepository
265          });
266          
267          // toggle site scope links
268          var toggleLink = Dom.get(this.id + "-site-link");
269          Event.addListener(toggleLink, "click", this.onSiteSearch, this, true);
270          toggleLink = Dom.get(this.id + "-all-sites-link");
271          Event.addListener(toggleLink, "click", this.onAllSiteSearch, this, true);
272          toggleLink = Dom.get(this.id + "-repo-link");
273          Event.addListener(toggleLink, "click", this.onRepositorySearch, this, true);
274          
275          // search YUI button
276          this.widgets.searchButton = Alfresco.util.createYUIButton(this, "search-button", this.onSearchClick);
277          
278          // menu button for sort options
279          this.widgets.sortButton = new YAHOO.widget.Button(this.id + "-sort-menubutton",
280          {
281             type: "menu", 
282             menu: this.id + "-sort-menu",
283             menualignment: ["tr", "br"],
284             lazyloadmenu: false
285          });
286          // set initially selected sort button label
287          var menuItems = this.widgets.sortButton.getMenu().getItems();
288          for (var m in menuItems)
289          {
290             if (menuItems[m].value === this.options.initialSort)
291             {
292                this.widgets.sortButton.set("label", this.msg("label.sortby", menuItems[m].cfg.getProperty("text")));
293                break;
294             }
295          }
296          // event handler for sort menu
297          this.widgets.sortButton.getMenu().subscribe("click", function(p_sType, p_aArgs)
298          {
299             var menuItem = p_aArgs[1];
300             if (menuItem)
301             {
302                me.refreshSearch(
303                {
304                   searchSort: menuItem.value
305                });
306             }
307          });
308          
309          // Hook action events
310          var fnActionHandler = function Search_fnActionHandler(layer, args)
311          {
312             var owner = YAHOO.Bubbling.getOwnerByTagName(args[1].anchor, "span");
313             if (owner !== null)
314             {
315                if (typeof me[owner.className] == "function")
316                {
317                   args[1].stop = true;
318                   var tagId = owner.id.substring(me.id.length + 1);
319                   me[owner.className].call(me, tagId);
320                }
321             }
322             return true;
323          };
324          YAHOO.Bubbling.addDefaultAction("search-tag", fnActionHandler);
325          
326          // Finally show the component body here to prevent UI artifacts on YUI button decoration
327          Dom.setStyle(this.id + "-body", "visibility", "visible");
328       },
329       
330       _setupDataTable: function Search_setupDataTable()
331       {
332          /**
333           * DataTable Cell Renderers
334           *
335           * Each cell has a custom renderer defined as a custom function. See YUI documentation for details.
336           * These MUST be inline in order to have access to the Alfresco.Search class (via the "me" variable).
337           */
338          var me = this;
339          
340          /**
341           * Thumbnail custom datacell formatter
342           *
343           * @method renderCellThumbnail
344           * @param elCell {object}
345           * @param oRecord {object}
346           * @param oColumn {object}
347           * @param oData {object|string}
348           */
349          renderCellThumbnail = function Search_renderCellThumbnail(elCell, oRecord, oColumn, oData)
350          {
351             oColumn.width = 100;
352             oColumn.height = 100;
353             Dom.setStyle(elCell.parentNode, "width", oColumn.width + "px");
354             Dom.setStyle(elCell, "height", oColumn.height + "px");
355             Dom.addClass(elCell, "thumbnail-cell");
356             
357             var url = me._getBrowseUrlForRecord(oRecord);
358             var imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/generic-result.png';
359             
360             // use the preview image for a document type
361             var dataType = oRecord.getData("type");
362             switch (dataType)
363             {
364                case "document":
365                   imageUrl = Alfresco.constants.PROXY_URI_RELATIVE + "api/node/" + oRecord.getData("nodeRef").replace(":/", "");
366                   imageUrl += "/content/thumbnails/doclib?c=queue&ph=true";
367                   break;
368                
369                case "folder":
370                   imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/folder.png';
371                   break;
372                
373                case "blogpost":
374                   imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/blog-post.png';
375                   break;
376                
377                case "forumpost":
378                   imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/topic-post.png';
379                   break;
380                
381                case "calendarevent":
382                   imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/calendar-event.png';
383                   break;
384                
385                case "wikipage":
386                   imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/wiki-page.png';
387                   break;
388                   
389                case "link":
390                   imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/link.png';
391                   break;
392                
393                case "datalist":
394                   imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/datalist.png';
395                   break;
396                
397                case "datalistitem":
398                   imageUrl = Alfresco.constants.URL_RESCONTEXT + 'components/search/images/datalistitem.png';
399                   break;
400             }
401             
402             // Render the cell
403             var name = oRecord.getData("displayName");
404             var htmlName = $html(name);
405             var html = '<span><a href="' + url + '"><img src="' + imageUrl + '" alt="' + htmlName + '" title="' + htmlName + '" /></a></span>';
406             if (dataType === "document")
407             {
408                var viewUrl = Alfresco.constants.PROXY_URI_RELATIVE + "api/node/content/" + oRecord.getData("nodeRef").replace(":/", "") + "/" + oRecord.getData("name");
409                html = '<div class="action-overlay">' + 
410                       '<a href="' + encodeURI(viewUrl) + '" target="_blank"><img title="' + $html(me.msg("label.viewinbrowser")) +
411                       '" src="' + Alfresco.constants.URL_RESCONTEXT + 'components/search/images/view-in-browser-16.png" width="16" height="16"/></a>' +
412                       '<a href="' + encodeURI(viewUrl + "?a=true") + '" style="padding-left:4px" target="_blank"><img title="' + $html(me.msg("label.download")) +
413                       '" src="' + Alfresco.constants.URL_RESCONTEXT + 'components/search/images/download-16.png" width="16" height="16"/></a>' + 
414                       '</div>' + html;
415             }
416             elCell.innerHTML = html;
417          };
418 
419          /**
420           * Description/detail custom cell formatter
421           *
422           * @method renderCellDescription
423           * @param elCell {object}
424           * @param oRecord {object}
425           * @param oColumn {object}
426           * @param oData {object|string}
427           */
428          renderCellDescription = function Search_renderCellDescription(elCell, oRecord, oColumn, oData)
429          {
430             // apply styles
431             Dom.setStyle(elCell.parentNode, "line-height", "1.5em");
432             
433             // site and repository items render with different information available
434             var site = oRecord.getData("site");
435             var url = me._getBrowseUrlForRecord(oRecord);
436             
437             // displayname and link to details page
438             var displayName = oRecord.getData("displayName");
439             var desc = '<h3 class="itemname"><a href="' + url + '" class="theme-color-1">' + $html(displayName) + '</a>';
440             // add title (if any) to displayname area
441             var title = oRecord.getData("title");
442             if (title && title !== displayName)
443             {
444                desc += '<span class="title">(' + $html(title) + ')</span>';
445             }
446             desc += '</h3>';
447             
448             // description (if any)
449             var txt = oRecord.getData("description");
450             if (txt)
451             {
452                desc += '<div class="details meta">' + $html(txt) + '</div>';
453             }
454             
455             // detailed information, includes site etc. type specific
456             desc += '<div class="details">';
457             var type = oRecord.getData("type");
458             switch (type)
459             {
460                case "document":
461                case "folder":
462                case "blogpost":
463                case "forumpost":
464                case "calendarevent":
465                case "wikipage":
466                case "datalist":
467                case "datalistitem":
468                case "link":
469                   desc += me.msg("label." + type);
470                   break;
471                
472                default:
473                   desc += me.msg("label.unknown");
474                   break;
475             }
476             
477             // link to the site and other meta-data details
478             if (site)
479             {
480                desc += ' ' + me.msg("message.insite");
481                desc += ' <a href="' + Alfresco.constants.URL_PAGECONTEXT + 'site/' + $html(site.shortName) + '/dashboard">' + $html(site.title) + '</a>';
482             }
483             if (oRecord.getData("size") !== -1)
484             {
485                desc += ' ' + me.msg("message.ofsize");
486                desc += ' <span class="meta">' + Alfresco.util.formatFileSize(oRecord.getData("size")) + '</span>';
487             }
488             if (oRecord.getData("modifiedBy"))
489             {
490                desc += ' ' + me.msg("message.modifiedby");
491                desc += ' <a href="' + Alfresco.constants.URL_PAGECONTEXT + 'user/' + encodeURI(oRecord.getData("modifiedByUser")) + '/profile">' + $html(oRecord.getData("modifiedBy")) + '</a>';
492             }
493             desc += ' ' + me.msg("message.modifiedon") + ' <span class="meta">' + Alfresco.util.formatDate(oRecord.getData("modifiedOn")) + '</span>';
494             desc += '</div>';
495             
496             // folder path (if any)
497             if (type === "document" || type === "folder")
498             {
499                var path = oRecord.getData("path");
500                if (site)
501                {
502                   if (path === null || path === undefined)
503                   {
504                      path = "";
505                   }
506                   desc += '<div class="details">' + me.msg("message.infolderpath") +
507                           ': <a href="' + me._getBrowseUrlForFolderPath(path, site) + '">' + $html('/' + path) + '</a></div>';
508                }
509                else
510                {
511                   if (path)
512                   {
513                      desc += '<div class="details">' + me.msg("message.infolderpath") +
514                           ': <a href="' + me._getBrowseUrlForFolderPath(path) + '">' + $html(path) + '</a></div>';
515                   }
516                }
517             }
518             
519             // tags (if any)
520             var tags = oRecord.getData("tags");
521             if (tags.length !== 0)
522             {
523                var i, j;
524                desc += '<div class="details"><span class="tags">' + me.msg("label.tags") + ': ';
525                for (i = 0, j = tags.length; i < j; i++)
526                {
527                    desc += '<span id="' + me.id + '-' + $html(tags[i]) + '" class="searchByTag"><a class="search-tag" href="#">' + $html(tags[i]) + '</a> </span>';
528                }
529                desc += '</span></div>';
530             }
531             
532             elCell.innerHTML = desc;
533          };
534 
535          // DataTable column defintions
536          var columnDefinitions = [
537          {
538             key: "image", label: me.msg("message.preview"), sortable: false, formatter: renderCellThumbnail, width: 100
539          },
540          {
541             key: "summary", label: me.msg("label.description"), sortable: false, formatter: renderCellDescription
542          }];
543 
544          // DataTable definition
545          this.widgets.dataTable = new YAHOO.widget.DataTable(this.id + "-results", columnDefinitions, this.widgets.dataSource,
546          {
547             renderLoopSize: Alfresco.util.RENDERLOOPSIZE,
548             initialLoad: false,
549             paginator: this.widgets.paginator,
550             MSG_LOADING: ""
551          });
552 
553          // show initial message
554          this._setDefaultDataTableErrors(this.widgets.dataTable);
555          if (this.options.initialSearchTerm.length === 0 && this.options.initialSearchTag.length === 0)
556          {
557             this.widgets.dataTable.set("MSG_EMPTY", "");
558          }
559          
560          // Override abstract function within DataTable to set custom error message
561          this.widgets.dataTable.doBeforeLoadData = function Search_doBeforeLoadData(sRequest, oResponse, oPayload)
562          {
563             if (oResponse.error)
564             {
565                try
566                {
567                   var response = YAHOO.lang.JSON.parse(oResponse.responseText);
568                   me.widgets.dataTable.set("MSG_ERROR", response.message);
569                }
570                catch(e)
571                {
572                   me._setDefaultDataTableErrors(me.widgets.dataTable);
573                }
574             }
575             else if (oResponse.results)
576             {
577                // clear the empty error message
578                me.widgets.dataTable.set("MSG_EMPTY", "");
579                
580                // update the results count, update hasMoreResults.
581                me.hasMoreResults = (oResponse.results.length > me.options.maxSearchResults);
582                if (me.hasMoreResults)
583                {
584                   oResponse.results = oResponse.results.slice(0, me.options.maxSearchResults);
585                   me.resultsCount = me.options.maxSearchResults;
586                }
587                else
588                {
589                   me.resultsCount = oResponse.results.length;
590                }
591                
592                if (me.resultsCount > me.options.pageSize)
593                {
594                   Dom.removeClass(me.id + "-paginator-top", "hidden");
595                   Dom.removeClass(me.id + "-search-bar-bottom", "hidden");
596                }
597             }
598             // Must return true to have the "Loading..." message replaced by the error message
599             return true;
600          };
601          
602          // Rendering complete event handler
603          me.widgets.dataTable.subscribe("renderEvent", function()
604          {
605             // Update the paginator
606             me.widgets.paginator.setState(
607             {
608                page: me.currentPage,
609                totalRecords: me.resultsCount
610             });
611             me.widgets.paginator.render();
612          });
613       },
614 
615       /**
616        * Constructs the completed browse url for a record.
617        * @param record {string} the record
618        */
619       _getBrowseUrlForRecord: function Search__getBrowseUrlForRecord(record)
620       {
621          var url = null;
622          
623          var name = record.getData("name"),
624              type = record.getData("type"),
625              site = record.getData("site"),
626              path = record.getData("path");
627          
628          switch (type)
629          {
630             case "document":
631             {
632                url = "document-details?nodeRef=" + record.getData("nodeRef");
633                break;
634             }
635             
636             case "folder":
637             {
638                if (path !== null)
639                {
640                   if (site)
641                   {
642                      url = "documentlibrary?path=" + encodeURIComponent(this._buildSpaceNamePath(path.split("/"), name));
643                   }
644                   else
645                   {
646                      url = "repository?path=" + encodeURIComponent(this._buildSpaceNamePath(path.split("/").slice(2), name));
647                   }
648                }
649                break;
650             }
651             
652             case "blogpost":
653             {
654                url = "blog-postview?postId=" + name;
655                break;
656             }
657             
658             case "forumpost":
659             {
660                url = "discussions-topicview?topicId=" + name;
661                break;
662             }
663             
664             case "calendarevent":
665             {
666                url = record.getData("container") + "?date=" + Alfresco.util.formatDate(record.getData("modifiedOn"), "yyyy-mm-dd");
667                break;
668             }
669             
670             case "wikipage":
671             {
672                url = "wiki-page?title=" + name;
673                break;
674             }
675             
676             case "link":
677             {
678                url = "links-view?linkId=" + name;
679                break;
680             }
681             
682             case "datalist":
683             case "datalistitem":
684             {
685                url = "data-lists?list=" + name;
686                break;
687             }
688          }
689          
690          if (url !== null)
691          {
692             // browse urls always go to a page. We assume that the url contains the page name and all
693             // parameters. Add the absolute path and the optional site param
694             if (site)
695             {
696                url = Alfresco.constants.URL_PAGECONTEXT + "site/" + site.shortName + "/" + url;
697             }
698             else
699             {
700                url = Alfresco.constants.URL_PAGECONTEXT + url;
701             }
702          }
703          
704          return (url !== null ? url : '#');
705       },
706       
707       /**
708        * Constructs the folder url for a record.
709        * @param path {string} folder path
710        *        For a site relative item this can be empty (root of doclib) or any path - without a leading slash
711        *        For a repository item, this can never be empty - but will contain leading slash and Company Home root
712        */
713       _getBrowseUrlForFolderPath: function Search__getBrowseUrlForFolderPath(path, site)
714       {
715          var url = null;
716          if (site)
717          {
718             url = Alfresco.constants.URL_PAGECONTEXT + "site/" + site.shortName + "/documentlibrary?path=" + encodeURIComponent('/' + path);
719          }
720          else
721          {
722             url = Alfresco.constants.URL_PAGECONTEXT + "repository?path=" + encodeURIComponent('/' + path.split('/').slice(2).join('/'));
723          }
724          return url;
725       },
726       
727       _buildSpaceNamePath: function Search__buildSpaceNamePath(pathParts, name)
728       {
729          return (pathParts.length !== 0 ? ("/" + pathParts.join("/")) : "") + "/" + name;
730       },
731 
732       /**
733        * DEFAULT ACTION EVENT HANDLERS
734        * Handlers for standard events fired from YUI widgets, e.g. "click"
735        */
736 
737       /**
738        * Perform a search for a given tag
739        * The tag is simply handled as search term
740        */
741       searchByTag: function Search_searchTag(param)
742       {
743          this.refreshSearch(
744          {
745             searchTag: param,
746             searchTerm: "",
747             searchQuery: ""
748          });
749       },
750       
751       /**
752        * Refresh the search page by full URL refresh
753        *
754        * @method refreshSearch
755        * @param args {object} search args
756        */
757       refreshSearch: function Search_refreshSearch(args)
758       {
759          var searchTerm = this.searchTerm;
760          if (args.searchTerm !== undefined)
761          {
762             searchTerm = args.searchTerm;
763          }
764          var searchTag = this.searchTag;
765          if (args.searchTag !== undefined)
766          {
767             searchTag = args.searchTag;
768          }
769          var searchAllSites = this.searchAllSites;
770          if (args.searchAllSites !== undefined)
771          {
772             searchAllSites = args.searchAllSites;
773          }
774          var searchRepository = this.searchRepository;
775          if (args.searchRepository !== undefined)
776          {
777             searchRepository = args.searchRepository;
778          }
779          var searchSort = this.searchSort;
780          if (args.searchSort !== undefined)
781          {
782             searchSort = args.searchSort;
783          }
784          var searchQuery = this.options.searchQuery;
785          if (args.searchQuery !== undefined)
786          {
787             searchQuery = args.searchQuery;
788          }
789          
790          // redirect back to the search page - with appropriate site context
791          var url = Alfresco.constants.URL_PAGECONTEXT;
792          if (this.options.siteId.length !== 0)
793          {
794             url += "site/" + this.options.siteId + "/";
795          }
796          
797          // add search data webscript arguments
798          url += "search?t=" + encodeURIComponent(searchTerm);
799          if (searchSort.length !== 0)
800          {
801             url += "&s=" + searchSort;
802          }
803          if (searchQuery.length !== 0)
804          {
805             // if we have a query (already encoded), then apply it
806             // other options such as tag, terms, all sites, repo etc. are trumped
807             url += "&q=" + searchQuery;
808          }
809          else
810          {
811             if (searchTag.length !== 0)
812             {
813                url += "&tag=" + encodeURIComponent(searchTag);
814             }
815             url += "&a=" + searchAllSites + "&r=" + searchRepository;
816          }
817          window.location = url;
818       },
819 
820       /**
821        * BUBBLING LIBRARY EVENT HANDLERS FOR PAGE EVENTS
822        * Disconnected event handlers for inter-component event notification
823        */
824 
825       /**
826        * Execute Search event handler
827        *
828        * @method onSearch
829        * @param layer {object} Event fired
830        * @param args {array} Event parameters (depends on event type)
831        */
832       onSearch: function Search_onSearch(layer, args)
833       {
834          var obj = args[1];
835          if (obj !== null)
836          {
837             var searchTerm = this.searchTerm;
838             if (obj.searchTerm !== undefined)
839             {
840                searchTerm = obj.searchTerm;
841             }
842             var searchTag = this.searchTag;
843             if (obj.searchTag !== undefined)
844             {
845                searchTag = obj.searchTag;
846             }
847             var searchAllSites = this.searchAllSites;
848             if (obj.searchAllSites !== undefined)
849             {
850                searchAllSites = obj.searchAllSites;
851             }
852             var searchRepository = this.searchRepository;
853             if (obj.searchRepository !== undefined)
854             {
855                searchRepository = obj.searchRepository;
856             }
857             var searchSort = this.searchSort;
858             if (obj.searchSort !== undefined)
859             {
860                searchSort = obj.searchSort;
861             }
862             this._performSearch(
863             {
864                searchTerm: searchTerm,
865                searchTag: searchTag,
866                searchAllSites: searchAllSites,
867                searchRepository: searchRepository,
868                searchSort: searchSort
869             });
870          }
871       },
872       
873       /**
874        * Event handler that gets fired when user clicks the Search button.
875        *
876        * @method onSearchClick
877        * @param e {object} DomEvent
878        * @param obj {object} Object passed back from addListener method
879        */
880       onSearchClick: function Search_onSearchClick(e, obj)
881       {
882          this.refreshSearch(
883          {
884             searchTag: "",
885             searchTerm: YAHOO.lang.trim(Dom.get(this.id + "-search-text").value),
886             searchQuery: ""
887          });
888       },
889       
890       /**
891        * Click event for Current Site search link
892        * 
893        * @method onSiteSearch
894        */
895       onSiteSearch: function Search_onSiteSearch(e, args)
896       {
897          this.refreshSearch(
898          {
899             searchAllSites: false,
900             searchRepository: false
901          });
902       },
903       
904       /**
905        * Click event for All Sites search link
906        * 
907        * @method onAllSiteSearch
908        */
909       onAllSiteSearch: function Search_onAllSiteSearch(e, args)
910       {
911          this.refreshSearch(
912          {
913             searchAllSites: true,
914             searchRepository: false
915          });
916       },
917       
918       /**
919        * Click event for Repository search link
920        * 
921        * @method onRepositorySearch
922        */
923       onRepositorySearch: function Search_onRepositorySearch(e, args)
924       {
925          this.refreshSearch(
926          {
927             searchRepository: true
928          });
929       },
930 
931       /**
932        * Search text box ENTER key event handler
933        * 
934        * @method _searchEnterHandler
935        */
936       _searchEnterHandler: function Search__searchEnterHandler(e, args)
937       {
938          this.refreshSearch(
939          {
940             searchTag: "",
941             searchTerm: YAHOO.lang.trim(Dom.get(this.id + "-search-text").value),
942             searchQuery: ""
943          });
944       },
945       
946       /**
947        * Updates search results list by calling data webscript with current site and query term
948        *
949        * @method _performSearch
950        * @param args {object} search args
951        */
952       _performSearch: function Search__performSearch(args)
953       {
954          var searchTerm = YAHOO.lang.trim(args.searchTerm),
955              searchTag = YAHOO.lang.trim(args.searchTag),
956              searchAllSites = args.searchAllSites,
957              searchRepository = args.searchRepository,
958              searchSort = args.searchSort;
959          
960          if (this.options.searchQuery.length === 0 &&
961              searchTag.length === 0 &&
962              searchTerm.replace(/\*/g, "").length < this.options.minSearchTermLength)
963          {
964             Alfresco.util.PopupManager.displayMessage(
965             {
966                text: this.msg("message.minimum-length", this.options.minSearchTermLength)
967             });
968             return;
969          }
970          
971          // empty results table
972          this.widgets.dataTable.deleteRows(0, this.widgets.dataTable.getRecordSet().getLength());
973          
974          // update the ui to show that a search is on-going
975          this.widgets.dataTable.set("MSG_EMPTY", "");
976          this.widgets.dataTable.render();
977          
978          // Success handler
979          function successHandler(sRequest, oResponse, oPayload)
980          {
981             // update current state on success
982             this.searchTerm = searchTerm;
983             this.searchTag = searchTag;
984             this.searchAllSites = searchAllSites;
985             this.searchRepository = searchRepository;
986             this.searchSort = searchSort;
987             
988             this.widgets.dataTable.onDataReturnInitializeTable.call(this.widgets.dataTable, sRequest, oResponse, oPayload);
989             
990             // update the results info text
991             this._updateResultsInfo();
992             
993             // set focus to search input textbox
994             Dom.get(this.id + "-search-text").focus();
995          }
996          
997          // Failure handler
998          function failureHandler(sRequest, oResponse)
999          {
1000             if (oResponse.status == 401)
1001             {
1002                // Our session has likely timed-out, so refresh to offer the login page
1003                window.location.reload();
1004             }
1005             else
1006             {
1007                try
1008                {
1009                   var response = YAHOO.lang.JSON.parse(oResponse.responseText);
1010                   this.widgets.dataTable.set("MSG_ERROR", response.message);
1011                   this.widgets.dataTable.showTableMessage(response.message, YAHOO.widget.DataTable.CLASS_ERROR);
1012                }
1013                catch(e)
1014                {
1015                   this._setDefaultDataTableErrors(this.widgets.dataTable);
1016                   this.widgets.dataTable.render();
1017                }
1018             }
1019          }
1020          
1021          this.widgets.dataSource.sendRequest(this._buildSearchParams(searchRepository, searchAllSites, searchTerm, searchTag, searchSort),
1022          {
1023             success: successHandler,
1024             failure: failureHandler,
1025             scope: this
1026          });
1027       },
1028       
1029       /**
1030        * Updates the results info text.
1031        * 
1032        * @method _updateResultsInfo
1033        */
1034       _updateResultsInfo: function Search__updateResultsInfo()
1035       {
1036          // update the search results field
1037          var text;
1038          var resultsCount = '<b>' + this.resultsCount + '</b>';
1039          if (this.hasMoreResults)
1040          {
1041             text = this.msg("search.info.resultinfomore", resultsCount);
1042          }
1043          else
1044          {
1045             text = this.msg("search.info.resultinfo", resultsCount);
1046          }
1047          
1048          // apply the context
1049          if (this.searchRepository || this.options.searchQuery.length !== 0)
1050          {
1051             text += ' ' + this.msg("search.info.foundinrepository");
1052          }
1053          else if (this.searchAllSites)
1054          {
1055             text += ' ' + this.msg("search.info.foundinallsite");
1056          }
1057          else
1058          {
1059             text += ' ' + this.msg("search.info.foundinsite", $html(this.options.siteTitle));
1060          }
1061          
1062          // set the text
1063          Dom.get(this.id + '-search-info').innerHTML = text;
1064       },
1065 
1066       /**
1067        * Build URI parameter string for search JSON data webscript
1068        *
1069        * @method _buildSearchParams
1070        */
1071       _buildSearchParams: function Search__buildSearchParams(searchRepository, searchAllSites, searchTerm, searchTag, searchSort)
1072       {
1073          var site = searchAllSites ? "" : this.options.siteId;
1074          var params = YAHOO.lang.substitute("site={site}&term={term}&tag={tag}&maxResults={maxResults}&sort={sort}&query={query}&repo={repo}",
1075          {
1076             site: encodeURIComponent(site),
1077             repo: searchRepository.toString(),
1078             term: encodeURIComponent(searchTerm),
1079             tag: encodeURIComponent(searchTag),
1080             sort: encodeURIComponent(searchSort),
1081             query: encodeURIComponent(this.options.searchQuery),
1082             maxResults: this.options.maxSearchResults + 1 // to calculate whether more results were available
1083          });
1084          
1085          return params;
1086       },
1087       
1088       /**
1089        * Resets the YUI DataTable errors to our custom messages
1090        * NOTE: Scope could be YAHOO.widget.DataTable, so can't use "this"
1091        *
1092        * @method _setDefaultDataTableErrors
1093        * @param dataTable {object} Instance of the DataTable
1094        */
1095       _setDefaultDataTableErrors: function Search__setDefaultDataTableErrors(dataTable)
1096       {
1097          var msg = Alfresco.util.message;
1098          dataTable.set("MSG_EMPTY", msg("message.empty", "Alfresco.Search"));
1099          dataTable.set("MSG_ERROR", msg("message.error", "Alfresco.Search"));
1100       }
1101    });
1102 })();