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

Source: bop/js/api/operation/OperationOutput.js

define([
    'data/js/api/OperationOutput',
    'entity/js/api/Property'
], function (
        OperationOutputImpl,
        Property
    ) {

    'use strict';

    /**
     * Builds the operation output.
     *
     * <p>
     * The operation output description drives some Application Builder UI features. For example if table is configured to use {@link operation/js/api/Operation Operation}
     * with only two output {@link entity/js/api/Property Properties} selected, these will be available for Business User to select in Table Creation Wizard - Data section.
     * </p>
     *
     * <p>
     * Most typical use case is when {@link operation/js/api/Operation Operation} of type {@link operation/js/api/Operation.Type.READ_ONE Operation.Type.READ_ONE} returns all
     * available {@link entity/js/api/Entity Entity} properties (resp. detailed information about {@link entity/js/api/Entity Entity}), but related {@link operation/js/api/Operation Operation}
     * of type {@link operation/js/api/Operation.Type.READ_MANY Operation.Type.READ_MANY} returns only certain sub-set of them (e.g. the most important ones which user want to see in
     * some Collection component). In such case, you need to create two {@link bop/js/api/operation/OperationOutput OperationOutput} instances and configure output
     * for each {@link operation/js/api/Operation Operation} separately.
     * </p>
     *
     * <p>
     * What's being build here needs to be passed into {@link bop/js/api/operation/OperationBuilder#returns OperationBuilder.returns(..)} method creating the final {@link operation/js/api/Operation Operation}
     * instance which is the base building block for creating custom BOP.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     * @exports bop/js/api/operation/OperationOutput
     * @constructor
     *
     * @param {Object} params - Object literal with all possible parameters.
     * @param {entity/js/api/Entity} params.entity - {@link entity/js/api/Entity Entity} whose {@link entity/js/api/Property Properties} the build {@link operation/js/api/Operation operation} returns on it's output.
     *
     * @see {@link entity/js/api/Entity Entity}
     * @see {@link entity/js/api/Property Property}
     * @see {@link operation/js/api/Operation Operation}
     * @see {@link bop/js/api/operation/OperationBuilder OperationBuilder}
     * @see {@link bop/js/api/operation/OperationBuilder#returns OperationBuilder.returns(..)}
     */
    var OperationOutput = function(params) {
        AbcsLib.checkThis(this);
        AbcsLib.checkDefined(params, 'params', 'You need to pass object literal with "entity" attribute set');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkObjectLiteral(params, ['entity'], ['entity']);

        this._entity = params.entity;
        this._properties = [];
        this._includeAll = false;
    };

    /**
     * Extends all {@link entity/js/api/Property Properties} from either single {@link bop/js/api/operation/OperationOutput OperationOutput}
     * instance or array of {@link bop/js/api/operation/OperationOutput OperationOutput}s.
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @param {bop/js/api/operation/OperationOutput | bop/js/api/operation/OperationOutput[]} outputs - {@link bop/js/api/operation/OperationOutput OperationOutput}(s)
     *          from which you want to extend all it's (resp. their's) {@link entity/js/api/Property Properties}.
     * @returns {bop/js/api/operation/OperationOutput} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationOutput OperationOutput} which is inheriting all defined {@link entity/js/api/Property Properties} from
     *  another {@link bop/js/api/operation/OperationOutput OperationOutput} plus including some others explicitly.
     * </caption>
     *
     * // Assuming Employee has properties: "Firstname", "Lastname", "Age", "Salary" and some others
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('firstname');
     * var lastname = employee.getProperty('lastname');
     * var age = employee.getProperty('age');
     * var salary = employee.getProperty('salary');
     *
     * // If some operation returns only basic Name information about Employee, this OperationOutput can be used
     * var nameOutput = new OperationOutput({
     *     entity: employee
     * }).property(firstname).
     *     property(lastname);
     *
     * // If on the other hand some other operation returns more information about Employee, full-OperationOutput can be used
     * var fullOutput = new OperationOutput({
     *     entity: employee
     * }).propertiesFrom(nameOutput).   // Inheriting all properties defined inside nameOutput ("Firstname", "Lastname")
     *     property(age).               // Explicitly including "Age" property
     *     property(salary);            // Explicitly including "Salary" property
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationOutput OperationOutput} which is inheriting all defined {@link entity/js/api/Property Properties} from
     *  two different {@link bop/js/api/operation/OperationOutput OperationOutput}s.
     * </caption>
     *
     * // Assuming Employee has properties: "Firstname", "Lastname", "Age", "Salary" and some others
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('firstname');
     * var lastname = employee.getProperty('lastname');
     * var age = employee.getProperty('age');
     * var salary = employee.getProperty('salary');
     *
     * var firstOutput = new OperationOutput({
     *     entity: employee
     * }).property(firstname).
     *     property(lastname);
     *
     * var secondOutput = new OperationOutput({
     *     entity: employee
     * }).property(age).
     *     property(salary);
     *
     * // If on the other hand some other operation returns more information about Employee, full-OperationOutput can be used
     * var fullOutput = new OperationOutput({
     *     entity: employee
     * }).propertiesFrom(firstOutput).     // Inheriting all properties defined inside firstOutput ("Firstname", "Lastname")
     *     propertiesFrom(secondOutput);   // Inheriting all properties defined inside secondOutput ("Age", "Salary")
     */
    OperationOutput.prototype.propertiesFrom = function(outputs) {
        AbcsLib.checkDefined(outputs, 'outputs', 'Either an OperationOutput instance or array of OperationOutput\'s needs to be passed.');
        AbcsLib.checkDataType(outputs, [OperationOutput, AbcsLib.Type.ARRAY]);
        AbcsLib.checkParameterCount(arguments, 1);

        if (outputs) {
            if (AbcsLib.isArray(outputs)) {
                for (var i = 0; i < outputs.length; i++) {
                    this._addAllFromOutput(outputs[i]);
                }
            } else {
                this._addAllFromOutput(outputs);
            }
        }
        return this;
    };

    /**
     * Explicitly including additional {@link entity/js/api/Property Properties} into the resulted {@link bop/js/api/operation/OperationOutput OperationOutput}.
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @param {entity/js/api/Property} property - {@link entity/js/api/Property Property} you want to include into the {@link bop/js/api/operation/OperationOutput OperationOutput}.
     * @returns {bop/js/api/operation/OperationOutput} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationOutput OperationOutput} with two explicit properties included.
     * </caption>
     *
     * // Assuming Employee has properties: "Firstname", "Lastname", "Age", "Salary" and some others
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('firstname');
     * var lastname = employee.getProperty('lastname');
     *
     * var output = new OperationOutput({
     *     entity: employee
     * }).property(firstname).
     *     property(lastname);
     */
    OperationOutput.prototype.property = function(property) {
        AbcsLib.checkDefined(property, 'property');
        AbcsLib.checkDataType(property, Property);
        AbcsLib.checkParameterCount(arguments, 1);

        var propertyID = property.getId();
        var exists = this.getEntity().getProperty(propertyID);
        if (!exists) {
            throw new Error('No Property with ID = ' + propertyID + ' exists in the configured Entity. Please be aware that your OperationOutput can\n\
                            be configured to only work with Properties that exists inside defined Entity.');
        }

        this._addProperty(property);
        return this;
    };

    /**
     * Automatically includes all {@link entity/js/api/Property Properties} from the configured {@link entity/js/api/Entity Entity} and adds them into the resulted {@link bop/js/api/operation/OperationOutput OperationOutput}.
     *
     * @returns {bop/js/api/operation/OperationOutput} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationOutput OperationOutput} with two explicit properties included.
     * </caption>
     *
     * // Assuming Employee has properties: "Firstname", "Lastname" and "Age"
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var output = new OperationOutput({
     *     entity: employee
     * }).parametersAll();      // This will automatically includes all three Properties
     */
    OperationOutput.prototype.propertiesAll = function() {
        AbcsLib.checkParameterCount(arguments, 0);

        this._includeAll = true;
        return this;
    };

    /**
     * Adds all {@link entity/js/api/Property Properties} from the given {@link bop/js/api/operation/OperationOutput OperationOutput}.
     *
     * @param {OperationOutput} output
     */
    OperationOutput.prototype._addAllFromOutput = function(output) {
        AbcsLib.checkDataType(output, OperationOutput);

        var entityID = this._entity.getId();
        var extendEntityID = output.getEntity().getId();
        if (entityID !== extendEntityID) {
            throw new Error('This OperationOutput instance is working on top of Entity (ID = "' + entityID + '") but the OperationOutput instance\n\
                            you are trying to extend is working with different one (Entity ID = "' + extendEntityID + '")');
        }

        var properties = output.getProperties();
        for (var i = 0; i < properties.length; i++) {
            this._addProperty(properties[i]);
        }
    };

    OperationOutput.prototype._addProperty = function(property) {
        var self = this;
        for (var i = 0; i < self._properties.length; i++) {
            var existingProperty = self._properties[i];
            if (existingProperty.getId() === property.getId()) {
                return;
            }
        }
        self._properties.push(property);
    };

    /**
     * Gets {@link entity/js/api/Entity Entity} which the {@link bop/js/api/operation/OperationOutput OperationOutput} is working with.
     *
     * @returns {Entity}
     */
    OperationOutput.prototype.getEntity = function() {
        return this._entity;
    };

    /**
     * Gets all {@link entity/js/api/Property Properties} of this {@link bop/js/api/operation/OperationOutput OperationOutput}.
     *
     * @returns {Property[]}
     */
    OperationOutput.prototype.getProperties = function() {
        // If parametersAll(..) had been called, include all available Entity properties automatically
        if (this._includeAll) {
            return this.getEntity().getProperties();
        } else {
            return this._properties;
        }
    };

    /**
     * Builds an {@link data/js/api/OperationOutput OperationOutput} instance based on the current configuration.
     *
     * @returns {data/js/api/OperationOutput}
     */
    OperationOutput.prototype.build = function() {
        return new OperationOutputImpl(this.getEntity(),
            // If we are including all properties lazily load this
            // as custom fields can be added later
            this._includeAll ? undefined : this.getProperties());
    };

    return OperationOutput;
});