JavaScript Extension Development API for Oracle Visual Builder Cloud Service - Classic Applications

Source: operation/js/api/OperationData.js

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;

});