Source: search/AlfSearchList.js

/**
 * Copyright (C) 2005-2016 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Extends the [sortable paginated list]{@link module:alfresco/lists/AlfSortablePaginatedList} to
 * handle search specific data.
 *
 * @module alfresco/search/AlfSearchList
 * @extends alfresco/lists/AlfSortablePaginatedList
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "alfresco/lists/AlfSortablePaginatedList",
        "alfresco/core/topics",
        "dojo/_base/array",
        "dojo/_base/lang",
        "dojo/dom-class",
        "alfresco/util/hashUtils",
        "dojo/io-query",
        "alfresco/core/ArrayUtils",
        "alfresco/core/ObjectTypeUtils",
        "alfresco/search/AlfSearchListView"],
        function(declare, AlfSortablePaginatedList, topics, array, lang, domClass, hashUtils, ioQuery, arrayUtils, ObjectTypeUtils) {

   return declare([AlfSortablePaginatedList], {

      /**
       * An array of the i18n files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/AlfSearchList.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/AlfSearchList.properties"}],

      /**
       * An array of the CSS files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{cssFile:"./css/AlfSearchList.css"}]
       */
      cssRequirements: [{cssFile:"./css/AlfSearchList.css"}],

      /**
       * An optional map of additional key/value pairs of query parameters to apply to all
       * searches.
       *
       * @instance
       * @type {object}
       * @default
       * @since 1.0.96
       */
      additionalQueryParameters: null,

      /**
       * The facet fields to include in searches. This is updated by the onIncludeFacetRequest function.
       *
       * @instance
       * @type {string}
       * @default
       */
      facetFields: "",

      /**
       * The filters of facets that should be applied to search queries. This can either be configured
       * when the widget is created or can be set via the browser hash fragment.
       *
       * @instance
       * @type {object}
       * @default
       */
      facetFilters: null,

      /**
       * This indicates whether or not to hide or display the included facets details when
       * results are loaded. This is initialised to true, but will be changed to false if
       * any facets are requested to be included in the page.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      hideFacets: true,

      /**
       * Overrides the [mixed in default]{@link module:alfresco/lists/SelectedItemStateMixin#itemKeyProperty}
       * to set a property more applicable to search APIs.
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.41
       */
      itemKeyProperty: "nodeRef",

      /**
       * The message key to use when displaying the number of results found.
       * 
       * @instance
       * @type {string}
       * @default
       * @since 1.0.70
       */
      resultsCountMessage: "faceted-search.results-menu.results-found-patch",

      /**
       * The current term to search on
       *
       * @instance
       * @type {string}
       * @default
       */
      searchTerm: "",

      /**
       * The initially selected scope. This should either be "repo", "all_sites" or the shortname of a
       * specific site.
       *
       * @instance
       * @type {string}
       * @default
       */
      selectedScope: "repo",

      /**
       * Include spell checking in search requests.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      spellcheck: true,

      /**
       * Overrides the [default value]{@link module:alfresco/lists/AlfList#totalResultsProperty} to use
       * the property expected when using the search API.
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.32
       */
      totalResultsProperty: "numberFound",

      /**
       * Overrides the [inherited default]{@link module:alfresco/lists/AlfHashList#updateInstanceValues}
       * to ensure that instance values should be updated from the hash. This only appliees when
       * [useHash]{@link module:alfresco/lists/AlfHashList#useHash} is configured to be true.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      updateInstanceValues: true,

      /**
       * The vars and terms showing on the url hash that should be reset for a new search.
       *
       * @instance
       * @type {string[]}
       * @default ["facetFilters", "query"]
       */
      _resetVars: ["facetFilters", "query"],

      /**
       * Extends the [inherited function]{@link module:alfresco/lists/AlfSortablePaginatedList#postMixInProperties}
       * 
       * @instance
       */
      postMixInProperties: function alfresco_search_AlfSearchList__postMixInProperties() {
         this.inherited(arguments);
         this._suspendSpellCheck = false;
         this._cleanResettableVars();

         if (this.useHash === true)
         {
            // Push the core hash update variables into the array configured by the extended AlfSortablePaginatedList
            this._coreHashVars.push("searchTerm","scope","facetFilters");
         }

         // NOTE: This is required to ensure that no search is performed when no search hash variables are
         //       initially provided. Added for backwards compatibility.
         this.currentFilter = {};
      },

      /**
       * Reloads the data by making a search using the currently set search attributes. Typically
       * called following actions
       *
       * @instance
       * @param {object} payload The payload of the publication requesting the reload
       */
      onReloadData: function alfresco_search_AlfSearchList__onReloadData() {
         this.resetResultsList();
         this.loadData();
      },

      /**
       * Extends the [inherited function]{@link module:alfresco/lists/AlfSortablePaginatedList#setupSubscriptions}
       * to subscribe to search specific topics.
       *
       * @instance
       */
      setupSubscriptions: function alfresco_search_AlfSearchList__setupSubscriptions() {
         this.inherited(arguments);
         this.alfSubscribe("ALF_SET_SEARCH_TERM", lang.hitch(this, this.onSearchTermRequest));
         this.alfSubscribe("ALF_INCLUDE_FACET", lang.hitch(this, this.onIncludeFacetRequest));
         this.alfSubscribe("ALF_APPLY_FACET_FILTER", lang.hitch(this, this.onApplyFacetFilter));
         this.alfSubscribe("ALF_REMOVE_FACET_FILTER", lang.hitch(this, this.onRemoveFacetFilter));
         this.alfSubscribe("ALF_SEARCHLIST_SCOPE_SELECTION", lang.hitch(this, this.onScopeSelection));
         this.alfSubscribe("ALF_ADVANCED_SEARCH", lang.hitch(this, this.onAdvancedSearch));
      },

      /**
       * Overrides the [inherited function]{@link module:alfresco/lists/AlfList#setDisplayMessages}
       * to set search specific messages.
       *
       * @instance
       */
      setDisplayMessages: function alfresco_search_AlfSearchList__setDisplayMessages() {
         this.noDataMessage = this.message("searchlist.no.data.message");
         this.fetchingDataMessage = this.message("searchlist.loading.data.message");
         this.renderingViewMessage = this.message("searchlist.rendering.data.message");
         this.fetchingMoreDataMessage = this.message("searchlist.loading.data.message");
         this.dataFailureMessage = this.message("searchlist.data.failure.message");
      },

      /**
       * Updates the current search term. Note that this is not currently sufficient for setting complete
       * search data (such as facets, filters, sort order, etc) so this will need to be iterated on as
       * needed.
       *
       * @instance
       * @param {object} payload The details of the search term to set
       */
      onSearchTermRequest: function alfresco_search_AlfSearchList__onSearchTermRequest(payload) {
         this.alfLog("log", "Setting search term", payload, this);

         var currHash;
         var searchTerm = lang.getObject("searchTerm", false, payload);
         if (searchTerm === null || searchTerm === undefined)
         {
            this.alfLog("warn", "No searchTerm provided on request", payload, this);
         }
         else if (searchTerm === this.searchTerm)
         {
            // The requested search term is the same as the previous one...
            // We want to allow duplicate searches to be made (to address eventual consistency issues)
            // but we want to prevent concurrent requests using the same data...
            if (this.requestInProgress === true)
            {
               // If a request is currently in progress, then we can just ignore this request.
            }
            else
            {
               if (payload.spellcheck === false)
               {
                  this._suspendSpellCheck = true;
               }

               if (this.useHash === true)
               {
                  // If a request is NOT in progress then we need to manually request a new search, because re-setting
                  // the hash will not trigger the changeFilter function....
                  // If the current hash includes a term from the resetHashTerms array, we need to clear those terms before
                  // setting a search term (even if it is the same), in this case updating the hash will trigger the search...
                  currHash = hashUtils.getHash();
                  if (this._cleanResettableHashTerms(currHash))
                  {
                     currHash.searchTerm = this.searchTerm;
                     this.alfPublish("ALF_NAVIGATE_TO_PAGE", {
                        url: ioQuery.objectToQuery(currHash),
                        type: "HASH"
                     }, true);
                  }
                  else
                  {
                     // The current hash has no resettable terms so we need to trigger a manual search...
                     this.resetResultsList();
                     this.loadData();
                  }
               }
               else
               {
                  // When not using URL hashes, just apply the new search...
                  this.resetResultsList();
                  this.loadData();
               }
            }
         }
         else
         {
            // The requested search term is new, so updating the hash will result in a new search...
            this.searchTerm = searchTerm;
            if (this.useHash === true)
            {
               currHash = hashUtils.getHash();
               this._cleanResettableHashTerms(currHash);
               currHash.searchTerm = this.searchTerm;
               this.alfPublish("ALF_NAVIGATE_TO_PAGE", {
                  url: ioQuery.objectToQuery(currHash),
                  type: "HASH"
               }, true);
            }
            else
            {
               this.resetResultsList();
               this.loadData();
            }
         }
      },

      /**
       * Handle advanced search requests.
       *
       * @instance
       * @param {object} payload The details of what to search for.
       */
      onAdvancedSearch: function alfresco_search_AlfSearchList__onAdvancedSearch(payload) {
         this.resetResultsList();
         this.alfCleanFrameworkAttributes(payload, true);
         this.searchTerm = payload.searchTerm;
         delete payload.searchTerm;
         this.query = payload;
         this.loadData();
      },

      /**
       *
       *
       * @instance
       * @param {object} payload The details of the scope selected.
       */
      onScopeSelection: function alfresco_search_AlfSearchList__onScopeSelection(payload) {
         this.alfLog("log", "Scope selection received", payload, this);
         var scope = lang.getObject("value", false, payload);
         if (scope === null || scope === undefined)
         {
            this.alfLog("warn", "No 'value' attribute provided in scope selection payload", payload, this);
         }
         else if (scope === this.selectedScope)
         {
            this.alfLog("log", "Scope requested is currently set", scope, this);
         }
         else
         {
            this.selectedScope = scope;
            if (scope === "repo" || scope === "all_sites")
            {
               this.siteId = "";
            }
            else
            {
               this.siteId = scope;
            }

            if (this.useHash === true)
            {
               var currHash = hashUtils.getHash();
               currHash.scope = scope;
            
               // Update the hash to trigger a search...
               this.alfPublish("ALF_NAVIGATE_TO_PAGE", {
                  url: ioQuery.objectToQuery(currHash),
                  type: "HASH"
               }, true);
            }
            else
            {
               this.resetResultsList();
               this.loadData();
            }
         }
      },

      /**
       *
       * @instance
       * @param {object} payload The details of the facet to include
       */
      onIncludeFacetRequest: function alfresco_search_AlfSearchList__onIncludeFacetRequest(payload) {
         this.alfLog("log", "Adding facet filter", payload, this);
         var qname = lang.getObject("qname", false, payload);
         var blockIncludeFacetRequest = lang.getObject("blockIncludeFacetRequest", false, payload);
         if (qname === null || qname === undefined)
         {
            this.alfLog("warn", "No qname provided when adding facet field", payload, this);
         }
         else if (blockIncludeFacetRequest !== null && blockIncludeFacetRequest === true)
         {
            // Don't include the facet in the facet fields, however indicate that facet
            this.hideFacets = false;
         }
         else
         {
            // Make sure each facet is only included once (the search API is not tolerant of duplicates)...
            // Even if multiple widgets want to include the same facet, they will all receive the same
            // publication on search results...
            this.hideFacets = false;
            var f = this.facetFields.split(",");
            var alreadyAdded = array.some(f, function(currQName) {
               return currQName === qname;
            });
            if (alreadyAdded)
            {
               // No action required - the request QName has already been included
            }
            else
            {
               this.facetFields = (this.facetFields !== "") ? this.facetFields + "," + qname : qname;
            }
         }
      },

      /**
       * This function is called as a result of publishing on the "ALF_APPLY_FACET_FILTER" topic. It will
       * update the current [filters]{@link module:alfresco/search/AlfSearchList#facetFilters}
       * object with a new entry for the request filter.
       *
       * @instance
       * @param {object} payload The details of the facet filter to apply
       */
      onApplyFacetFilter: function alfresco_search_AlfSearchList__onApplyFacetFilter(payload) {
         this.alfLog("log", "Filtering on facet", payload, this);
         var filter = lang.getObject("filter", false, payload);
         if (filter === null || filter === undefined)
         {
            this.alfLog("warn", "No filter provided when filtering by facet", payload, this);
         }
         else
         {
            this.facetFilters[filter] = true;
            if (this.useHash === true)
            {
               this.updateFilterHash(filter, "add");
            }
            else
            {
               this.resetResultsList();
               this.loadData();
            }
         }
      },

      /**
       * This function is called as a result of publishing on the "ALF_REMOVE_FACET_FILTER" topic. It will
       * update the current [filters]{@link module:alfresco/search/AlfSearchList#facetFilters}
       * object to delete the supplied filter
       *
       * @instance
       * @param {object} payload The details of the facet filter to apply
       */
      onRemoveFacetFilter: function alfresco_search_AlfSearchList__onRemoveFacetFilter(payload) {
         this.alfLog("log", "Removing facet filter", payload, this);
         delete this.facetFilters[payload.filter];
         if (this.useHash === true)
         {
            this.updateFilterHash(payload.filter, "remove");
         }
         else
         {
            this.resetResultsList();
            this.loadData();
         }
      },

      /**
       * Performs updates to the url hash as facets are selected and de-selected
       *
       * @instance
       *
       * @fires ALF_NAVIGATE_TO_PAGE
       */
      updateFilterHash: function alfresco_search_AlfSearchList__updateFilterHash(fullFilter, mode) {
         // Get the existing hash and extract the individual facetFilters into an array
         var aHash = hashUtils.getHash(),
             facetFilters = aHash.facetFilters ? aHash.facetFilters : "",
             facetFiltersArr = facetFilters === "" ? [] : facetFilters.split(",");

         // Add or remove the filter from the hash object
         if(mode === "add" && !arrayUtils.arrayContains(facetFiltersArr, fullFilter))
         {
            facetFiltersArr.push(fullFilter);
         }
         else if (mode === "remove" && arrayUtils.arrayContains(facetFiltersArr, fullFilter))
         {
            facetFiltersArr.splice(facetFiltersArr.indexOf(fullFilter), 1);
         }

         // Put the manipulated filters back into the hash object or remove the property if empty
         if(facetFiltersArr.length < 1)
         {
            delete aHash.facetFilters;
         }
         else
         {
            aHash.facetFilters = facetFiltersArr.join();
         }

         // Send the hash value back to navigation
         this.alfPublish("ALF_NAVIGATE_TO_PAGE", {
            url: ioQuery.objectToQuery(aHash),
            type: "HASH"
         }, true);
      },

      /**
       * If [useHash]{@link module:alfresco/lists/AlfHashList#useHash} has been set to true
       * then this function will be called whenever the browser hash fragment is modified. It will update
       * the attributes of this instance with the values provided in the fragment.
       *
       * @instance
       * @param {object} payload
       */
      onHashChanged: function alfresco_search_AlfSearchList__onHashChanged(payload) {
         this.alfLog("log", "Hash change detected", payload, this);

         // Only update if the payload contains one of the variables we care about
         if(this.doHashVarUpdate(payload, this.updateInstanceValues))
         {
            // If the search term has changed then we want to delete the facet filters as
            // they might not be applicable to the new search results...
            var newSearchTerm = lang.getObject("searchTerm", false, payload);
            if (newSearchTerm !== this.searchTerm)
            {
               this._cleanResettableVars();
            }

            var pageOrSizeHasChanged = !this.useInfiniteScroll && (this.currentPage !== payload.currentPage || this.currentPageSize !== payload.currentPageSize);

            // The facet filters need to be handled directly because they are NOT just passed as
            // a simple string. Create a new object for the filters and then break up the filters
            // based on comma delimition and assign each element as a new key in the filters object
            var filters = lang.getObject("facetFilters", false, payload);
            if (filters !== null && filters !== undefined)
            {
               var ff = payload.facetFilters = {};
               var fArr = filters.split(",");
               array.forEach(fArr, function(filter) {
                  ff[filter] = true;
               }, this);
            }
            else
            {
               this._cleanResettableVars();
            }

            lang.mixin(this, payload);
            this.resetResultsList(pageOrSizeHasChanged);
            this.loadData();
         }
      },

      /**
       * Processes all the current search arguments into a payload that is published to the [Search Service]{@link module:alfresco/services/SearchService}
       * to perform the actual search request
       *
       * @instance
       */
      loadData: function alfresco_search_AlfSearchList__loadData() {
         // jshint maxcomplexity:false,maxstatements:false
         
         // Ensure any no data node is hidden...
         domClass.add(this.noDataNode, "share-hidden");

         var key;
         if (this.requestInProgress)
         {
            // TODO: Inform user that request is in progress?
            this.alfLog("log", "Search request ignored because progress is already in progress");
            this._searchPending = true;
            if (this.currentRequestId)
            {
                this.alfPublish("ALF_STOP_SEARCH_REQUEST", {
                   requestId: this.currentRequestId
                }, true);
            }
         }
         else
         {
            // InfiniteScroll uses pagination under the covers.
            var startIndex = (this.currentPage - 1) * this.currentPageSize;
            if (!this.useInfiniteScroll ||
                !this.currentData ||
                this.currentData.numberFound === -1 || // Indicate no results found on previous search
                this.currentData.numberFound >= startIndex)
            {
               this.alfPublish(this.requestInProgressTopic, {});
               this.showLoadingMessage();

               // When loading a page containing filters to apply as a URL hash parameter, the facetFilters attribute
               // will be a string, but when applied after the page is loaded it will be an object. We need to treat
               // each case differently...
               var filters = "";
               if (ObjectTypeUtils.isString(this.facetFilters))
               {
                  // When facetFilters is a string it will be delimited by commas, the string can be split to get an array
                  // of each filter to apply...
                  var filtersArray = this.facetFilters.split(",");
                  array.forEach(filtersArray, function(filter) {
                     filters = filters + filter.replace(/\.__.u/g, "").replace(/\.__/g, "") + ",";
                  });
               }
               else
               {
                  // When facetFilters is an object, each key will be a filter to be applied...
                  for (key in this.facetFilters)
                  {
                     if (this.facetFilters.hasOwnProperty(key))
                     {
                        filters = filters + key.replace(/\.__.u/g, "").replace(/\.__/g, "") + ",";
                     }
                  }
               }
               // Trim any trailing comma...
               filters = filters.substring(0, filters.length - 1);

               // Make sure the repo param is set appropriately...
               // The repo instance variable trumps everything else...
               var siteId = this.siteId;
               var scope = this.selectedScope;
               if (this.useHash === true)
               {
                  // Unfortunately we made the error of using "scope" on the URL hash and
                  // "selectedScope" as the widget attribute. This means that we have to special
                  // case useHash being set to true to use the appropriate value for the mode being used...
                  siteId = (this.scope === "repo" || this.scope === "all_sites") ? "" : scope;
                  scope = this.scope || scope.toLowerCase();
               }
               
               this.currentRequestId = this.generateUuid();
               var searchPayload = {
                  term: this.searchTerm,
                  facetFields: this.facetFields,
                  filters: filters,
                  sortAscending: this.sortAscending,
                  sortField: this.sortField,
                  site: siteId,
                  rootNode: this.rootNode,
                  repo: scope === "repo",
                  requestId: this.currentRequestId,
                  pageSize: this.currentPageSize,
                  maxResults: 0,
                  startIndex: startIndex,
                  spellcheck: this.spellcheck && !this._suspendSpellCheck
               };

               if (this.query)
               {
                  this.alfCleanFrameworkAttributes(this.query, true);

                  if(typeof this.query === "string")
                  {
                     this.query = JSON.parse(this.query);
                  }

                  for (key in this.query)
                  {
                     if (this.query.hasOwnProperty(key))
                     {
                        searchPayload[key] = this.query[key];
                     }
                  }
               }

               // Add in any additional query parameters...
               if (this.additionalQueryParameters)
               {
                  for (key in this.additionalQueryParameters)
                  {
                     if (this.additionalQueryParameters.hasOwnProperty(key))
                     {
                        searchPayload[key] = this.additionalQueryParameters[key];
                     }
                  }
               }

               // Set a response topic that is scoped to this widget...
               searchPayload.alfResponseTopic = this.pubSubScope + "ALF_RETRIEVE_DOCUMENTS_REQUEST";
               this.alfPublish(topics.SEARCH_REQUEST, searchPayload, true);
            }
            else
            {
               this.alfLog("log", "No more data to to retrieve, cancelling search request", this);
            }
         }
      },

      /**
       * Handles successful calls to get data from the repository.
       *
       * @instance
       * @param {object} response The response object
       * @param {object} originalRequestConfig The configuration that was passed to the [serviceXhr]{@link module:alfresco/core/CoreXhr#serviceXhr} function
       */
      onDataLoadSuccess: function alfresco_search_AlfSearchList__onDataLoadSuccess(payload) {
         /* jshint maxcomplexity:false */
         this.alfLog("log", "Search Results Loaded", payload, this);

         var newData = payload.response;
         this.currentData = newData; // Some code below expects this even if the view is null.

         // Reset suspending the spell check...
         this._suspendSpellCheck = false;

         // Re-render the current view with the new data...
         var view = this.viewMap[this._currentlySelectedView];
         if (view !== null)
         {
            this.showRenderingMessage();
            this.processLoadedData(payload.response || this.currentData);
            if (this.useInfiniteScroll)
            {
               view.augmentData(newData);
               this.currentData = view.getData();
            }
            else
            {
               view.setData(newData);
            }

            view.renderView(this.useInfiniteScroll);
            this.showView(view);
         }

         // TODO: This should probably be in the SearchService... but will leave here for now...
         var facets = lang.getObject("response.facets", false, payload);
         var filters = lang.getObject("requestConfig.query.encodedFilters", false, payload);
         if (facets !== null && facets !== undefined)
         {
            for (var key in facets)
            {
               if (facets.hasOwnProperty(key))
               {
                  var facet = key;
                  if (key[0] === "@")
                  {
                     facet = key.substring(1);
                  }
                  this.alfPublish("ALF_FACET_RESULTS_" + facet, {
                     facetResults: facets[key],
                     activeFilters: filters
                  });
               }
            }
         }

         // Handle any spell checking data included in the results...
         this.handleSpellCheck(payload);

         var resultsCount = this.currentData.numberFound !== -1 ? this.currentData.numberFound : 0;
         if (resultsCount !== null)
         {
            // Publish the number of search results found...
            this.alfPublish("ALF_SEARCH_RESULTS_COUNT", {
               count: resultsCount,
               label: this.message(this.resultsCountMessage, {
                  0: resultsCount
               })
            });
         }

         this.alfPublish("ALF_HIDE_FACETS", {
            hide: this.hideFacets === true || resultsCount === 0
         });

         // This request has finished, allow another one to be triggered.
         this.alfPublish(this.requestFinishedTopic, {});

         // Force a resize of the sidebar container to take the new height of the view into account...
         this.alfPublish("ALF_RESIZE_SIDEBAR", {});
      },

      /**
       * This function can be called to handle spell check results. If a search term did not match to any
       * results or similar search terms yield better results then the search API may either have carried out
       * the alternative search or has provided some alternative suggestions to search for. This function
       * checks the search result payload and publishes on the "ALF_SPELL_CHECK_SEARCH_TERM" and
       * "ALF_SPELL_CHECK_SEARCH_SUGGESTIONS" topics respectively with that data.
       *
       * @instance
       * @param {object} payload The payload containing the search result data
       */
      handleSpellCheck: function alfresco_search_AlfSearchList__handleSpellCheck(payload) {
         // Check to see whether or not spell checking was applied...
         var spellcheck = lang.getObject("response.spellcheck", false, payload);
         if (spellcheck !== null && spellcheck !== undefined)
         {
            if (spellcheck.searchedFor !== null && spellcheck.searchedFor !== undefined)
            {
               // Update the local state to reflect what was actually searched for...
               // this.searchTerm = spellcheck.searchedFor;

               // This means that an alternative search term was used...
               this.alfPublish("ALF_SPELL_CHECK_SEARCH_TERM", {
                  searchRequest: spellcheck.searchRequest,
                  searchedFor: spellcheck.searchedFor
               });
            }
            else if (spellcheck.searchSuggestions !== null && spellcheck.searchSuggestions !== undefined)
            {
               // This means that an alternative search was not performed, but suggested searches
               // are available...
               var suggestions = [];
               array.forEach(spellcheck.searchSuggestions, function(suggestion) {
                  suggestions.push({
                     term: suggestion
                  });
               });
               this.alfPublish("ALF_SPELL_CHECK_SEARCH_SUGGESTIONS", {
                  searchRequest: spellcheck.searchRequest,
                  searchSuggestions: suggestions
               });
            }
            else
            {
               // This means that the requested search term was used. No action required.
            }
         }
      },

      /**
       * Clear Old results from list & reset counts.
       *
       * @instance
       */
      resetResultsList: function alfresco_search_AlfSearchList__resetResultsList(doNotChangePages) {
         if(!doNotChangePages) {
            this.startIndex = 0;
            this.currentPage = 1;
         }
         this.hideChildren(this.domNode);
         this.clearViews();
      },

      /**
       * Clean resettable variables based on resetVars array.
       *
       * @instance
       */
      _cleanResettableVars: function alfresco_search_AlfSearchList___cleanResettableVars() {
         for (var i = 0; i < this._resetVars.length; i++) {
            this[this._resetVars[i]] = {};
         }
      },

      /**
       * Clean resettable hash terms based on resetVars array.
       *
       * @instance
       * @param {object} currHash An object containing current hash values
       * @return boolean
       */
      _cleanResettableHashTerms: function alfresco_search_AlfSearchList___cleanResettableHashTerms(currHash) {
         var hasTerm = false;
         for (var term in currHash) {
            if(this._resetVars.indexOf(term) !== -1 && currHash[term] !== null && currHash[term] !== "")
            {
               hasTerm = true;
               delete currHash[term];
            }
         }
         return hasTerm;
      },

      /**
       * Extends the [inherited function]{@link module:alfresco/lists/AlfList#onRequestFinished} to reissue
       * a search request if additional search data was provided whilst a request was in-progress.
       *
       * @instance
       */
      onRequestFinished: function alfresco_search_AlfSearchList___onRequestFinished() {
         this.inherited(arguments);
         if (this._searchPending ===true)
         {
            this._searchPending = false;
            this.loadData();
         }
      }
   });
});