Source: services/PreferenceService.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/>.
 */

/**
 * This service provides access to user preferences as a whole as well as subscribing to specific topics
 * for setting Document Library preferences (views, sidebar visibility/width, breadcrumb visibility, etc) and
 * adding and removing documents or folders from a the users favourites list.
 * 
 * @module alfresco/services/PreferenceService
 * @extends module:alfresco/services/BaseService
 * @mixes module:alfresco/core/CoreXhr
 * @mixes module:alfresco/services/_PreferenceServiceTopicMixin
 * @mixes module:alfresco/services/_AlfDocumentListTopicMixin
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "alfresco/services/BaseService",
        "alfresco/core/CoreXhr",
        "alfresco/services/_PreferenceServiceTopicMixin",
        "alfresco/documentlibrary/_AlfDocumentListTopicMixin",
        "alfresco/core/topics",
        "dojo/_base/lang",
        "dojo/_base/array",
        "service/constants/Default",
        "alfresco/core/ArrayUtils",
        "dojo/Deferred",
        "dojo/when",
        "jquery"],
        function(declare, BaseService, CoreXhr, _PreferenceServiceTopicMixin, _AlfDocumentListTopicMixin, topics, lang, 
                 array, AlfConstants, ArrayUtils, Deferred, when, $) {
   
   return declare([BaseService, CoreXhr, _PreferenceServiceTopicMixin, _AlfDocumentListTopicMixin], {
      
      /**
       * This is the dot-notation preferences property address for favourite documents
       *
       * @instance
       * @type {string}
       * @default
       */
      FAVOURITE_DOCUMENTS: "org.alfresco.share.documents.favourites",

      /**
       * This is the dot-notation preferences property address for favourite folders
       *
       * @instance
       * @type {string}
       * @default
       */
      FAVOURITE_FOLDERS: "org.alfresco.share.folders.favourites",
      
      /**
       * Sets up the subscriptions for the PreferenceService
       * 
       * @instance
       * @since 1.0.32
       * @listens module:alfresco/core/topics#GET_PREFERENCE
       * @listens module:alfresco/core/topics#SET_PREFERENCE
       * @listens module:alfresco/core/topics#ADD_FAVOURITE_NODE
       * @listens module:alfresco/core/topics#REMOVE_FAVOURITE_NODE
       */
      registerSubscriptions: function alfresco_services_PreferenceService__registerSubscriptions() {
         this.alfSubscribe(topics.GET_PREFERENCE, lang.hitch(this, this.getPreference));
         this.alfSubscribe(topics.SET_PREFERENCE, lang.hitch(this, this.setPreference));
         this.alfSubscribe(this.showFoldersTopic, lang.hitch(this, this.onShowFolders));
         this.alfSubscribe(this.showPathTopic, lang.hitch(this, this.onShowPath));
         this.alfSubscribe(this.viewSelectionTopic, lang.hitch(this, this.onViewSelection));
         this.alfSubscribe(topics.ADD_FAVOURITE_NODE, lang.hitch(this, this.onAddFavouriteDocument));
         this.alfSubscribe(topics.REMOVE_FAVOURITE_NODE, lang.hitch(this, this.onRemoveFavouriteDocument));

         // Load the user preferences...
         this._preferencesLoaded = new Deferred();
         // early-resolve when localPreferences have been provided via config
         if (this.localPreferences !== undefined && this.localPreferences !== null)
         {
             this._preferencesLoaded.resolve(this.localPreferences);
         }
         else
         {
             var url = AlfConstants.PROXY_URI + "api/people/" + encodeURIComponent(AlfConstants.USERNAME) + "/preferences";
             this.serviceXhr({url : url,
                          successCallback: function(response) {
                              this._preferencesLoaded.resolve(response);
                          },
                          callbackScope: this,
                          method: "GET"});
         }
      },
      
      /**
       * The preferences provided at services instantiation.
       * 
       * @instance
       * @type {object} 
       * @default
       */
      localPreferences: null,
      
      /**
       * Retrieves a preference from the [local copy]{@link module:alfresco/services/PreferenceService#localPreferences} rather than 
       * getting them remotely.
       * 
       * @instance
       * @param {object} payload
       */
      getPreference: function alfresco_services_PreferenceService__getPreference(payload) {
         if (!lang.exists("callback", payload) || 
             typeof payload.callback !== "function" || 
             !lang.exists("callbackScope", payload))
         {
            this.alfLog("warn", "A request was made to get a preference, but the callback information was missing", payload);
         }
         else if (!lang.exists("preference", payload))
         {
            this.alfLog("warn", "A request was made to get a preference, but no 'preference' attribute was provided", payload);
         }
         else
         {
            when(this._preferencesLoaded, lang.hitch(this, this.setLocalPreferences, payload));
         }
      },

      /**
       * Sets the local copy of the preferences onces they've been retrieved from the Repository.
       * 
       * @param {object} payload The payload from the original request to get preferences
       * @param {object} preferences The retrieved preferences object
       */
      setLocalPreferences: function alfresco_services_PreferenceService__setLocalPreferences(payload, preferences) {
         this.localPreferences = preferences;
         payload.callback.apply(payload.callbackScope, [lang.getObject(payload.preference, false, this.localPreferences)]);
      },
      
      /**
       * Sets a preference remotely.
       * 
       * @instance
       * @param {object} payload
       */
      setPreference: function alfresco_services_PreferenceService__setPreference(payload) {
         if (!lang.exists("preference", payload))
         {
            this.alfLog("warn", "A request was made to set a preference, but no 'preference' attribute was provided", payload);
         }
         else if (!lang.exists("value", payload))
         {
            this.alfLog("warn", "A request was made to set a preference, but no 'value' attribute was provided", payload);
         }
         else if (lang.getObject(payload.preference, false, this.localPreferences) === payload.value)
         {
            // Don't save a preference if it hasn't changed...
            this.alfLog("log", "Intentionally not saving a preference that hasn't changed");
         }
         else
         {
            // Set users preference url...
            var url = AlfConstants.PROXY_URI + "api/people/" + encodeURIComponent(AlfConstants.USERNAME) + "/preferences";
            
            // Set the remote preference...
            var preferenceObj = {};
            lang.setObject(payload.preference, payload.value, preferenceObj);
            var responseTopic = payload.alfTopic;
            if (payload.alfResponseTopic)
            {
               responseTopic = payload.alfResponseTopic;
            }
            this.serviceXhr({url : url,
                             alfTopic: responseTopic,
                             preference: payload.preference,
                             updatedValue: payload.updatedValue,
                             value: payload.value,
                             data: preferenceObj,
                             method: "POST"});

            // Set the local preference...
            when(this._preferencesLoaded, function(preferences) {
               this.localPreferences = preferences;
               lang.setObject(payload.preference, payload.value, this.localPreferences);
            });
         }
      },

      /**
       * This is the success callback for retrieving preferences. If a name and value are included then
       * the preferences will be updated.
       *
       * @instance
       * @param {object} response The object returned from the successful XHR request
       * @param {object} requestConfig The original configuration passed when the request was made
       */
      onPreferenceRetrieved: function alfresco_services_PreferenceService__onPreferenceRetrieved(response, requestConfig) {
         if (response && requestConfig.data && requestConfig.data.name && requestConfig.data.value)
         {
            var name = requestConfig.data.name;
            var value = requestConfig.data.value;
            var preferences = {};
            lang.setObject(name, value, preferences);
            $.extend(true, preferences, response);
            var values = lang.getObject(name, false, preferences);
            
            // Parse string to array, add the value and convert to string again
            if (typeof values === "string" || values === null)
            {
               var arrValues = values ? values.split(",") : [];

               if (requestConfig.data.add === true)
               {
                  arrValues.push(value);
               }
               else
               {
                  var valueArr = value.split(",");
                  array.forEach(valueArr, lang.hitch(ArrayUtils, ArrayUtils.arrayRemove, arrValues));
               }
               
               // Save preference with the new value
               // We include the updatedValue so that subscribers can identify the requested changes
               // because the stored value will not included data that has been removed...
               this.setPreference({
                  alfTopic: requestConfig.data.alfTopic,
                  preference: name,
                  updatedValue: value, 
                  value: arrValues.join(",")
               });
            }
         }
      },

      /**
       * This is the failure callback for retrieving preferences
       *
       * @instance
       * @param {object} response The object returned from the successful XHR request
       * @param {object} requestConfig The original configuration passed when the request was made
       */
      onPreferenceRetrievalFail: function alfresco_services_PreferenceService__onPreferenceRetrieved(response, requestConfig) {
         if (requestConfig.data && requestConfig.data.alfTopic)
         {
            this.alfPublish(requestConfig.data.alfTopic + "_FAILURE", {});
         }
      },

      /**
       * This function updates and existing preference where that preference is intended to be an array
       * of items. In order to perform the update the existing preferences must be retrieved and then the
       * preference can be added or removed depending on the supplied mode.
       * 
       * @instance
       * @param {object} payload The details of the preference to add
       * @param {boolean} add Indicates whether or not the update is to add or remove a preference
       */
      update: function alfresco_services_PreferenceService__update(payload, add) {
         if (payload.name && payload.value)
         {
            var url = AlfConstants.PROXY_URI + "api/people/" + encodeURIComponent(AlfConstants.USERNAME) + "/preferences" + (payload.name ? "?pf=" + payload.name : "");
            this.serviceXhr({
               url: url,
               method: "GET",
               data: {
                  name: payload.name,
                  value: payload.value,
                  alfTopic: payload.alfTopic,
                  add: add
               },
               successCallback: this.onPreferenceRetrieved,
               failureCallback: this.onPreferenceRetrievalFail,
               callbackScope: this
            });
         }
         else
         {
            this.alfLog("warn", "A request was made to add a preference but either the preference name or value was missing from the payload", payload, this);
         }
      },
      
      /**
       * Updates the current users preference for seeing folders in the Document List
       * @instance
       * @param {object} payload 
       */
      onShowFolders: function alfresco_services_PreferenceService__onShowFolders(payload) {
         if (payload && (payload.selected || payload.selected === false))
         {
            this.setPreference({
               preference: "org.alfresco.share.documentList.showFolders",
               value: payload.selected
            });
         } 
      },
      
      /**
       * Updates the current users preference for seeing the current path in the Document List
       * 
       * @instance
       * @param {object} payload 
       */
      onShowPath: function alfresco_services_PreferenceService__onShowPath(payload) {
         if (payload && (payload.selected || payload.selected === false))
         {
            this.setPreference({
               preference: "org.alfresco.share.documentList.hideNavBar",
               value: !payload.selected
            });
         } 
      },
      
      /**
       * Updates the current users preferred view for Document Lists. However, this will NOT set
       * a view preference if the view provided is "Abstract".
       * 
       * @instance
       * @param {object} payload
       */
      onViewSelection: function alfresco_services_PreferenceService__onViewSelection(payload) {
         if (payload && payload.value && payload.value !== "Abstract")
         {
            var preference = payload.preference || "org.alfresco.share.documentList.viewRendererName";
            this.setPreference({
               preference: preference,
               value: payload.value
            });
         }
      },
      
      /**
       * Iterates over the supplied array of Nodes and puts the NodeRefs from them into the supplied
       * arrays of folders and documents based on whether or not the Node has an "isContainer" attribute
       * set to true.
       * 
       * @instance
       * @param {object[]} nodes     An array of nodes to iterate over
       * @param {string[]} folders   A string array to populate with folder NodeRefs
       * @param {string[]} documents A string array to populate with document NodeRefs
       * @since 1.0.38
       */
      separateFoldersAndDocuments: function alfresco_services_PreferenceService__separateFoldersAndDocuments(nodes, folders, documents) {
         array.forEach(nodes, function(item) {
            if (item.node)
            {
               if (item.node.isContainer)
               {
                  item.node.nodeRef && folders.push(item.node.nodeRef);
               }
               else
               {
                  item.node.nodeRef && documents.push(item.node.nodeRef);
               }
            } 
         });
      },

      /**
       * Adds nodes to or removes nodes from the documents and folders favourites in the current user preferences.
       *
       * @instance
       * @param  {object} payload  The original payload for the add or remove favourites request.
       * @param  {string} alfTopic The topic to publish on the callback
       * @param  {boolean} add     Indicates whether or not this is an add request
       * @since 1.0.38
       */
      processFavourites: function alfresco_services_PreferenceService__processFavourites(payload, alfTopic, add) {
         // Default to favouriting documents since that's more likely...
         if (payload.nodes && payload.nodes.length)
         {
            var folders = [];
            var documents = [];
            this.separateFoldersAndDocuments(payload.nodes, folders, documents);
            if (folders.length)
            {
               this.update({
                  name: this.FAVOURITE_FOLDERS,
                  value: folders.join(","),
                  alfTopic: alfTopic
               }, add);
            }
            if (documents.length)
            {
               this.update({
                  name: this.FAVOURITE_DOCUMENTS,
                  value: documents.join(","),
                  alfTopic: alfTopic
               }, add);
            }
         }
         else if (payload.node && payload.node.node && payload.node.node.nodeRef)
         {
            var prefKey = payload.node.node.isContainer ? this.FAVOURITE_FOLDERS : this.FAVOURITE_DOCUMENTS;
            this.update({
               name: prefKey,
               value: payload.node.node.nodeRef,
               alfTopic: alfTopic
            }, add);
         }
      },

      /**
       * Makes the supplied node or nodes favourites of the current user
       * 
       * @instance
       * @param {object} payload
       */
      onAddFavouriteDocument: function alfresco_services_PreferenceService__onAddFavouriteDocument(payload) {
         var alfTopic = payload.alfResponseTopic || topics.ADD_FAVOURITE_NODE;
         this.processFavourites(payload, alfTopic, true);
      },
      
      /**
       * Removes the supplied node or nodes from being a favourite.
       * 
       * @instance
       * @param {object} payload
       */
      onRemoveFavouriteDocument: function alfresco_services_PreferenceService__onRemoveFavouriteDocument(payload) {
         var alfTopic = payload.alfResponseTopic || topics.REMOVE_FAVOURITE_NODE;
         this.processFavourites(payload, alfTopic, false);
      }
   });
});