define([
'entity/js/api/Property',
'operation/js/api/Conditions',
'operation/js/api/FieldPath'
], function(
Property,
Conditions,
FieldPath
) {
'use strict';
/**
* Describes the input data (record or records) and options (filtering, sorting, pagination etc.) which the client selected to perform an {@link operation/js/api/Operation Operation} instance.
*
* <p>
* {@link operation/js/api/Operation Operation} author is expected to process {@link operation/js/api/OperationData OperationData} values and perform corresponding function so that the expected
* data (filtered, sorted and paginated) are always fetched.
* </p>
*
* <p>
* {@link operation/js/api/Operation Operation} contains all relevant information (e.g. {@link operation/js/api/Condition Condition}) to allow build up URL correctly or generally process input values any
* way it need. The {@link operation/js/api/OperationData OperationData} instances are created automatically by Application Builder when Business User is tweaking application UI. For example while Business
* User is changing "Default Query" in the UI, Application Builder automatically creates different {@link operation/js/api/OperationData OperationData} instance based on the UI selection which is then passed
* into the {@link operation/js/api/Operation#perform Operation.perform(..)} method so that the author can take them into account and fetch only corresponding records.
* </p>
*
* <p>
* {@link operation/js/api/OperationData OperationData} can be set by one of these options:
* <ul>
* <li>
* By Application Builder UI automatically when client is using the Table/List and selects another value in sorting combo-box.
* </li>
* <li>
* By Business User creating and passing its instance directly from the custom code. See {@link module:api/js/Operations.read Abcs.Operations().read(..)} to check
* how to perform such custom code.
* </li>
* </ul>
* </p>
*
* @AbcsExtension stable
* @version 17.1.1
* @exports operation/js/api/OperationData
*
* @constructor
* @private
*
* @param {Object} params - Object literal with all possible parameters.
* @param {String} params.etag
* @param {String} params.record - Record values
* @param {Object} params.customData
* @param {operation/js/query/Query} params.query
* @param {operation/js/api/Sorting} params.sorting
* @param {operation/js/api/PaginationRequest} params.paginationRequest
* @param {operation/js/api/ExpandableReference[]} params.expandableReferences
* @param {entity/js/api/Property[] | operation/js/api/FieldPath[]} params.fields
*/
var OperationData = function(params) {
AbcsLib.checkThis(this);
if (!params) {
params = {};
}
this._query = params.query;
this._paginationRequest = params.paginationRequest;
this._data = params.record;
this._sorting = params.sorting;
this._etag = params.etag;
this._contentType = 'application/json; charset=UTF-8';
this._headers = {};
this._fieldPaths = params.fields && params.fields.map(function(field){
if (field instanceof Property) {
return new FieldPath(field);
} else if (field instanceof FieldPath) {
return field;
}
});
this._customData = params.customData;
this._expandableReferences = params.expandableReferences;
this._originalData = undefined; // set by setOriginalResult
};
OperationData._SORTING = 'sorting';
OperationData._CONDITIONS = 'conditions';
OperationData.prototype.getQuery = function() {
return this._query;
};
/**
* Gets the {@link operation/js/api/Condition Condition} requesting to fetch only records matching the given criteria.
*
* @AbcsExtension stable
* @version 17.1.1
*
* @returns {operation/js/api/Condition}
*
* @see {@link operation/js/api/Condition Condition}
* @see {@link bop/js/spi/operation/ConditionVisitor ConditionVisitor}
*
* @example
* <caption>
* Using an input {@link operation/js/api/Condition Condition} and processing it with custom {@link bop/js/spi/operation/ConditionVisitor ConditionVisitor} to get the proper URL.
* </caption>
*
* var findEmployee = function(operationData) {
* var results = [];
* var baseURL = 'http://example.com/employee';
* var condition = operationData.getCondition();
*
* // The visitor is there to turn Condition abstraction into
* // whatever URL shape data provider expects on it's input
* var resultURL = condition.visit(new MyConditionVisitor(baseURL));
*
* $.ajax({
* url: resultURL,
* type: 'GET'
* }).done(function(response) {
* // Because the URL was change by the visitor, we should have filtered results here
* fulfil(OperationResult.success(response));
* });
* };
*/
OperationData.prototype.getCondition = function() {
return this._query ? this._query.getCondition() : Conditions.EMPTY;
};
/**
* Gets the {@link operation/js/api/PaginationRequest PaginationRequest} value requesting to fetch only certain subset of records.
*
* @AbcsExtension stable
* @version 17.1.1
*
* @returns {operation/js/api/PaginationRequest}
*/
OperationData.prototype.getPaginationRequest = function() {
return this._paginationRequest;
};
/**
* Gets custom data assigned to this {@link operation/js/api/OperationData OperationData} instance.
*
* <p>
* Custom data Object has no formal structure pre-defined. It's up to the {@link operation/js/api/Operation Operation} author whether s(he) can handle some custom
* values as part of the {@link operation/js/api/Operation#perform Operation.perform()} method implementation. Because of that, the format and contract of the
* custom data object is completely up to the author. It can be expected as complex Object literal, plain String value or any other valid JS construct.
* </p>
*
* @AbcsExtension stable
* @version 17.1.1
*
* @returns {Object}
*/
OperationData.prototype.getCustomData = function() {
return this._customData;
};
OperationData.prototype.getData = function() {
return this._data;
};
/**
* Tests whether getData() would return any actual data.
* @returns {Boolean} false if there is no data, or data is an empty Object.
*/
OperationData.prototype.hasData = function() {
if (this._data) {
for (var key in this._data) {
if (this._data.hasOwnProperty(key)) {
return true;
}
}
}
return false;
};
/**
* Gets what this OperationData considers to be the modified parts of the
* data record. If setOriginalResult() has been called this diff's getData()
* against getOriginalData(). Otherwise this returns the same as getData().
* @returns {Object<String, Object>} data record
*/
OperationData.prototype.getModifiedData = function() {
var res = this.getData();
var origData = this.getOriginalData();
if (origData) {
res = AbcsLib.clone(res);
// If something is in origData but not data we do not assume it has
// been removed. It has to be in data with a value of null or undefined
// for it to be considered as 'removed'.
// remove anything from res that has not been modified
for (var key in res) {
if (res.hasOwnProperty(key) && origData.hasOwnProperty(key)) {
// BUFP-12175 this will fail for REFERENCE data because one
// side will be a string (changed by the viewmodel) and one
// an integer (correctly).
if (res[key] === origData[key]) {
delete res[key];
}
}
}
}
return res;
};
/**
* Gets any stored original data record.
*/
OperationData.prototype.getOriginalData = function() {
return this._originalData;
};
/**
* Sets the original OperationResult that last retrieved/created/updated
* the record this OperationData pertains to.
* @param {operation/js/api/OperationResult} operationResult
*/
OperationData.prototype.setOriginalResult = function(operationResult) {
this._originalData = operationResult && operationResult.getData();
this._etag = operationResult && operationResult.getETag();
};
OperationData.prototype.getETag = function() {
return this._etag;
};
/**
* Gets the {@link operation/js/api/Sorting Sorting} value requesting to fetch records in certain order.
*
* @AbcsExtension stable
* @version 17.1.1
*
* @returns {operation/js/api/Sorting}
*
* @see {@link operation/js/api/Sorting Sorting}
*
* @example
* <caption>
* Using an input {@link operation/js/api/Sorting Sorting} and processing it to build proper URL.
* </caption>
*
* var findEmployee = function(operationData) {
* var url = 'http://example.com/employee';
* var sorting = operationData.getSorting();
* var criteria = sorting.getSortingCriteria();
*
* if (criteria && criteria.length > 0) {
* // For simplicity lets assume only one criterion is always available
* var criterion = criteria[0];
* var property = criterion.getProperty();
* var propertyID = property.getId();
*
* // Assume our server supports URL suffix in form of '?sort=firstname&direction=asc'
* url += '?sort=' + propertyID;
* if (criterion.isAscending()) {
* url += '&direction=asc';
* } else {
* url += '&direction=desc';
* }
* }
*
* $.ajax({
* url: url,
* type: 'GET'
* }).done(function(response) {
* // Because the URL contain sorting parameter, we should have sorted records here
* fulfil(OperationResult.success(response));
* });
* };
*/
OperationData.prototype.getSorting = function() {
return this._sorting;
};
/**
* Gets all {@link entity/js/api/FieldPath FieldPaths} requested by the client to return.
*
* @returns {entity/js/api/FieldPath[]}
*/
OperationData.prototype.getFieldPathSelection = function() {
return this._fieldPaths;
};
OperationData.prototype.getExpandableReferences = function() {
return this._expandableReferences;
};
OperationData.prototype.getContentType = function() {
return this._contentType;
};
OperationData.prototype.setContentType = function(contentType) {
this._contentType = contentType;
};
OperationData.prototype.getDefinition = function() {
var self = this;
var query = self.getQuery();
var sorting = self.getSorting();
var result = {};
result[OperationData._CONDITIONS] = query ? query.getDefinition().condition : undefined;
result[OperationData._SORTING] = sorting ? sorting.getDefinition() : [];
return ko.toJSON(result);
};
/**
* Returns all custom headers for this request.
*
* @private
* @returns {Object}
*/
OperationData.prototype.getHeaders = function() {
return this._headers;
};
/**
* Creates a new custom header
*
* @param {String} name The name of the header
* @param {String} value The value of the header
* @private
*/
OperationData.prototype.addHeader = function(name, value) {
this._headers[name] = value;
};
/**
* Overwrites all headers with the ones provided
*
* @param {Object} headers
* @private
*/
OperationData.prototype.setHeaders = function(headers) {
// Make defensive copy
this._headers = {};
$.extend(true, this._headers, headers);
};
/**
* Clones this OperationData instance and possibly override data set.
*
* @param {Object<String, Object>} [data]
* @param {Object<String, Object>} [originalData]
* @returns {OperationData}
*/
OperationData.prototype.clone = function(data, originalData) {
var od = new OperationData({
query: this.getQuery(),
paginationRequest: this.getPaginationRequest(),
record: data || this.getData(),
sorting: this.getSorting(),
etag: this.getETag(),
fields: this.getFieldPathSelection(),
customData: this.getCustomData(),
expandableReferences: this.getExpandableReferences()
});
// Copy non constructor properties
od.setContentType(this.getContentType());
od.setHeaders(this.getHeaders());
// presence of data means we consider originalData to be the new value
// for _originalData regardless of whether it is undefined itself.
od._originalData = data ? originalData : this._originalData;
return od;
};
/**
* Clones this OperationData instance to create a version whose data is only
* the modified data in this OperationData. This is useful to any Operation
* implementation that wants to cut the input down for PATCH rather than PUT.
*
* @returns {OperationData} a clone with data containing just the modified
* data from this OperationData.
*/
OperationData.prototype.cloneModifiedOnly = function() {
return this.clone(this.getModifiedData());
};
return OperationData;
});