/**
* 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 module was written with the express purpose of working with the [ComboBox]{@link module:alfresco/forms/controls/ComboBox}
* form control. It extends the Dojo JsonRest module to support queries over the Aikau publication/subscription
* communication layer (rather than by direct XHR request).
*
* @module alfresco/forms/controls/utilities/ServiceStore
* @extends external:dojo/store/JsonRest
* @author Dave Draper
*/
define(["dojo/_base/declare",
"dojo/store/JsonRest",
"alfresco/core/Core",
"dojo/Deferred",
"dojo/_base/lang",
"dojo/_base/array",
"dojo/store/util/QueryResults",
"dojo/store/util/SimpleQueryEngine",
"dojo/regexp"],
function(declare, JsonRest, AlfCore, Deferred, lang, array, QueryResults, SimpleQueryEngine, regexp) {
return declare([JsonRest, AlfCore], {
/**
* This is the topic to publish to get the options for.
*
* @instance
* @type {string}
* @default
*/
publishTopic: null,
/**
* The payload to publish on the [publishTopic]{@link module:alfresco/forms/controls/utilities/ServiceStore#publishTopic}
* to assist with retrieving data.
*
* @instance
* @type {object}
* @default
*/
publishPayload: null,
/**
* This is the attribute to use when querying the result data for matching items. This is set to
* "name" by default but can be overridden. When used by a [form control]{@link module:alfresco/forms/controls/BaseFormControl}
* it would be expected that this would be set to be the [name attribute]{@link module:alfresco/forms/controls/BaseFormControl#name}
* of that form control.
*
* @instance
* @type {string}
* @default
*/
queryAttribute: "name",
/**
* Should the results all start with the search query string.
* If set to false, results that contain the string anywhere will match
*
* @instance
* @type {string}
* @default
*/
searchStartsWith: false,
/**
* If this is configured to be an array of fixed options then the query will be run against
* those options without constantly making XHR requests for fresh data.
*
* @instance
* @type {array}
* @default
*/
fixed: null,
/**
* This function is called to retrieve an item from the store. If the store uses fixed options
* then these are checked and if an XHR request is required then a deferred item will be
* returned pending a callback to the
* [onGetOptions function]{@link module:alfresco/forms/controls/utilities/ServiceStore#onGetOptions}.
*
* @instance
* @param {string} id The id of the item to retrieve from the store
* @param {object} options Options for finding the item
* @returns Either the item or a promise of the item
*/
get: function alfresco_forms_controls_utilities_ServiceStore__get(id, /*jshint unused:false*/options){
var response = null;
if (this.publishTopic)
{
// If a publishTopic has been specified then publish on it to request the options
// to search through for the item...
response = new Deferred();
var responseTopic = this.generateUuid();
var payload = lang.clone(this.publishPayload);
if (!payload)
{
payload = {};
}
payload.alfResponseTopic = responseTopic;
var resultsProperty = this.publishPayload.resultsProperty || "response";
this._getOptionsHandle = [];
this._getOptionsHandle.push(this.alfSubscribe(responseTopic + "_SUCCESS", lang.hitch(this, this.onGetOptions, response, resultsProperty, id)));
this._getOptionsHandle.push(this.alfSubscribe(responseTopic, lang.hitch(this, this.onGetOptions, response, resultsProperty, id)));
this.alfPublish(this.publishTopic, payload, this.publishGlobal);
}
else if (this.fixed)
{
// ...otherwise search any fixed options that have been supplied...
response = this.getOption(lang.clone(this.fixed), id);
}
else
{
this.alfLog("warn", "A ServiceStore was set up without 'publishTopic' or 'fixed' attributes to use to retrieve options", this);
response = "";
}
return response;
},
/**
* This is the callback function that is hitched to the request for
*
* @instance
* @param {obejct} dfd The deferred object to resolve.
* @param {string} resultsProperty A dot-notation address in the payload that should contain the list of options.
* @param {string} id The id of the item to retrieve
* @param {object} payload The options to use
*/
onGetOptions: function alfresco_forms_controls_utilities_ServiceStore__onGetOptions(dfd, resultsProperty, id, payload) {
this.alfUnsubscribeSaveHandles([this._getOptionsHandle]);
var results = lang.getObject(resultsProperty, false, payload);
if (results !== null && typeof results !== "undefined")
{
var target = this.getOption(results, id);
dfd.resolve(target);
}
else
{
this.alfLog("warn", "No '" + resultsProperty + "' attribute published in payload for the query options", payload, this);
dfd.resolve("");
}
},
/**
* Iterates over the supplied results array to try and find an item where it's
* valueAttribute matches the supplied id.
*
* @instance
* @param {array} results The results to iterate over
* @param {string} id The id of the item to find
* @returns {object} The found item (or the empty string if the item cannot be found)
*/
getOption: function alfresco_forms_controls_utilities_ServiceStore__getOption(results, id) {
var target = "";
array.forEach(results, function(item) {
if (item[this.valueAttribute] === id)
{
target = item;
}
}, this);
return target;
},
/**
* This function is used to actually query the results (either from a pub/sub request or
* defined in a fixed list of options).
*
* @instance
* @param {array} results The results to query.
*/
queryResults: function alfresco_forms_controls_utilities_ServiceStore__queryResults(results, query) {
/*jshint newcap:false*/
// Clone the original fixed set of options to ensure that we're not
// removing any of the original data...
var queryAttribute = this.queryAttribute || "name";
var labelAttribute = this.labelAttribute || "label";
var valueAttribute = this.valueAttribute || "value";
// Check that all the data is valid, this is done to ensure any data sets that don't contain all the data...
// This is a workaround for an issue with the Dojo query engine that will break when an item doesn't contain
// the query attribute...
array.forEach(results, lang.hitch(this, this.processResult, queryAttribute, labelAttribute, valueAttribute));
// Create an updated query with a sanitised query/regex in it
var updatedQuery = {};
updatedQuery[this.queryAttribute] = this.createSearchRegex(query[this.queryAttribute].toString());
// NOTE: Ignore JSHint warnings on the following 2 lines...
var queryEngine = SimpleQueryEngine(updatedQuery);
var queriedResults = QueryResults(queryEngine(results));
return queriedResults;
},
/**
* Create the regex used for querying
*
* @instance
* @param {string} queryString The supplied query string
* @param {boolean} ignorePostMatch Whether to append ".*$" to the string (defaults to including this)
* @returns {object} The regular expression to use in the query engine
*/
createSearchRegex: function alfresco_forms_controls_utilities_ServiceStore__createSearchRegex(queryString, ignorePostMatch) {
var safeQueryString = regexp.escapeString(queryString),
rePrefix = this.searchStartsWith ? "^" : "",
reSuffix = ignorePostMatch ? "" : ".*$",
reValue = rePrefix + safeQueryString + reSuffix,
reModifiers = "i";
return new RegExp(reValue, reModifiers);
},
/**
* Processes the results to check that all the data is valid, this is done to ensure any
* data sets that don't contain all the data are corrected.This is a workaround for an
* issue with the Dojo query engine that will break when an item doesn't contain
* the query attribute. This function also adds label and value attributes to the item
* if they're not present.
*
* @instance
* @param {array} options The array to add the processed item to
* @param {object} config The configuration to use for processing the option
* @param {object} item The current item to process as an option
* @param {number} index The index of the item in the items list
*/
processResult: function alfresco_forms_controls_utilities_ServiceStore__processResult(queryAttribute, labelAttribute, valueAttribute, item, /*jshint unused:false*/ index) {
// Small helper func to remove JSHint errors but absolutely preserve existing logic
var isValid = function(o) {
return o !== null && typeof o !== "undefined";
};
if (!isValid(item[queryAttribute]))
{
item[queryAttribute] = "";
}
if (!isValid(item.label) && isValid(item[labelAttribute]))
{
item.label = item[labelAttribute];
}
if (!isValid(item.value) && isValid(item[valueAttribute]))
{
item.value = item[valueAttribute];
}
},
/**
* Queries a fixed set of options.
*
* @instance
* @param {object} query The query to use for retrieving objects from the store.
* @param {object} options The optional arguments to apply to the resultset.
* @returns {object} The results of the query, extended with iterative methods.
*/
queryFixedOptions: function alfresco_forms_controls_utilities_ServiceStore__queryFixedOptions(query, /*jshint unused:false*/ options) {
var queriedResults = this.queryResults(lang.clone(this.fixed), query);
return queriedResults;
},
/**
* Makes a request for data by publishing a request on a specific topic. This returns a
* Deferred object which is resolved by the
* [onQueryOptions]{@link module:alfresco/forms/controls/utilities/ServiceStore#onQueryOptions}
* function.
*
* @instance
* @param {object} query The query to use for retrieving objects from the store.
* @param {object} options The optional arguments to apply to the resultset.
* @returns {object} The results of the query, extended with iterative methods.
*/
queryXhrOptions: function alfresco_forms_controls_utilities_ServiceStore__queryXhrOptions(query, /*jshint unused:false*/ options) {
var response = new Deferred();
var responseTopic = this.generateUuid();
var payload = lang.clone(this.publishPayload);
if (!payload)
{
payload = {};
}
payload.alfResponseTopic = responseTopic;
// Set up a dot-notation address to retrieve the results from, this will be set to response if not included
// in the payload...
var resultsProperty = payload.resultsProperty || "response";
// Add in an additional query attribute. Some services (e.g. the TagService) will use this as an additional
// search term request parameter...
payload.query = query[this.queryAttribute || "name"];
var optionsHandle = [];
optionsHandle.push(this.alfSubscribe(responseTopic + "_SUCCESS", lang.hitch(this, this.onQueryOptions, response, query, resultsProperty, optionsHandle)));
optionsHandle.push(this.alfSubscribe(responseTopic, lang.hitch(this, this.onQueryOptions, response, query, resultsProperty, optionsHandle)));
this.alfPublish(this.publishTopic, payload, this.publishGlobal);
return response;
},
/**
* Overrides the inherited function from the JsonRest store to call either the
* [queryXhrOptions]{@link module:alfresco/forms/controls/utilities/ServiceStore#queryXhrOptions}
* or [queryFixedOptions]{@link module:alfresco/forms/controls/utilities/ServiceStore#queryFixedOptions}
* depending upon how this module has been configured.
*
* @instance
* @param {object} query The query to use for retrieving objects from the store.
* @param {object} options The optional arguments to apply to the resultset.
* @returns {object} The r{@link module:alfresco/forms/controls/utilities/ServiceStore#onQueryOptions} esults of the query, extended with iterative methods.
*/
query: function alfresco_forms_controls_utilities_ServiceStore__query(query, options){
var response = null;
if (this.publishTopic)
{
response = this.queryXhrOptions(query, options);
}
else if (this.fixed)
{
response = this.queryFixedOptions(query, options);
}
else
{
this.alfLog("warn", "A ServiceStore was set up without 'publishTopic' or 'fixed' attributes to use to retrieve options", this);
response = {};
}
return response;
},
/**
* This is hitched to a generated topic subscription that is published when the target service has retrieved
* the requested data. It performs a query on the data provided to generate the result set.
*
* @instance
* @param {obejct} dfd The deferred object to resolve.
* @param {object} query The requested query data.
* @param {string} resultsProperty A dot-notation address in the payload that should contain the list of options.
* @param {object} payload The options to use
*/
onQueryOptions: function alfresco_forms_controls_utilities_ServiceStore__onQueryOptions(dfd, query, resultsProperty, optionsHandle, payload) {
this.alfUnsubscribeSaveHandles([optionsHandle]);
var results = lang.getObject(resultsProperty, false, payload);
if (results)
{
var queriedResults = this.queryResults(results, query);
dfd.resolve(queriedResults);
}
else
{
this.alfLog("warn", "No '" + resultsProperty + "' attribute published in payload for the query options", payload, this);
dfd.resolve([]);
}
}
});
});