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

Source: components/js/fieldValueEditor/FieldValueEditorViewModel.js

define([
    'components/js/comboboxPicker/ComboboxPickerViewModel',
    'components/js/fieldValueEditor/DefaultFieldValueEditorHelper',
    'components/js/fieldValueEditor/FieldValueEditorUtils',
    'components/js/fieldValueEditor/FieldValueEditorValueMode',
    'core/js/api/Listenable',
    'entity/js/api/PropertyType',
    'text!components/templates/fieldValueEditor/fieldValueEditorResources.html',
    'template!components/templates/fieldValueEditor/fieldValueEditor.html',
    'template!components/js/comboboxPicker/ComboboxPickerTemplate.html'
], function (
        ComboboxPickerViewModel,
        defaultFieldValueEditorHelper,
        FieldValueEditorUtils,
        FieldValueEditorValueMode,
        Listenable,
        PropertyType,
        fieldValueEditorResourcesMarkup
        ) {

    'use strict';

    var markup = $(fieldValueEditorResourcesMarkup);
    $('body').append(markup);

    var _RANDOM_USAGE_ID = 0;

    /**
     * Usage example:
     *
     *    In JavaScript:
     *    function MyViewModel(sourceEntity, targetEntity) {
     *        this.sourceEntity = sourceEntity;
     *        this.targetEntity = targetEntity;
     *        this.myInputInfo = {
     *            propertyId: ko.observableArray(['somePropertyId']),
     *            invalidComponentTracker: ko.observable(),
     *            valueMode: ko.observable(FieldValueEditorValueMode.STATIC),
     *            value: ko.observable()
     *        };
     *    }
     *
     *    In HTML:
     *    <field-value-editor params="sourceEntity             sourceEntity,
     *                                targetEntity:            targetEntity,
     *                                propertyId:              myInputInfo.propertyId,
     *                                invalidComponentTracker: myInputInfo.invalidComponentTracker,
     *                                valueMode:               myInputInfo.valueMode,
     *                                value:                   myInputInfo.value"></field-value-editor>
     *
     *    In JavaScript:
     *    function MyViewModel(entity) {
     *        this.entity = entity;
     *        this.myInputInfo = {
     *            targetPropertyType: ko.observable(PropertyType.TEXT),
     *            valueMode: ko.observable(FieldValueEditorValueMode.STATIC),
     *            value: ko.observable()
     *        };
     *    }
     *
     *    In HTML:
     *    <field-value-editor params="sourceEntity:       entity,
     *                                targetPropertyType: myInputInfo.targetPropertyType,
     *                                valueMode:          myInputInfo.valueMode,
     *                                value:              myInputInfo.value"></field-value-editor>
     *
     */
    var FieldValueEditorViewModel = function (params) {
        AbcsLib.checkThis(this);
//        AbcsLib.checkDefined(params.sourceEntity, 'params.sourceEntity');
//        AbcsLib.checkDefined(params.targetEntity || params.targetPropertyType, 'params.targetEntity or params.targetPropertyType');
        if (params.targetEntity) {
            AbcsLib.checkDefined(params.propertyId || params.property, 'params.propertyId or params.property');
        }
        if (params.executableExpressionsAllowed) {
            AbcsLib.checkDefined(params.expressionEditorTemplateName, 'params.expressionEditorTemplateName');
        }

        Listenable.apply(this);

        var self = this;

        this.sourceEntity = ko.unwrap(params.sourceEntity); // RHS entity
        this.targetEntity = ko.unwrap(params.targetEntity); // LHS entity
        this.targetPropertyType = params.targetPropertyType; // LHS property type
        this.propertyId = params.propertyId;
        this.invalidComponentTracker = params.invalidComponentTracker ? params.invalidComponentTracker : ko.observable();
        this.validators = params.validators ? params.validators : [];
        this.valueMode = params.valueMode ? params.valueMode : ko.observable(FieldValueEditorValueMode.STATIC);
        this.expressionEditorTemplateName = params.expressionEditorTemplateName;
        this.executableExpressionsAllowed = params.executableExpressionsAllowed;
        this.getReferenceValueCallback = params.getReferenceValueCallback;
        this.helper = params.fieldValueEditorHelper || defaultFieldValueEditorHelper;
        this.runtimeMode = params.runtimeMode;
        this.referenceOptionAllowed = params.referenceOptionAllowed;
        this.editable = params.editable ? params.editable : ko.observable(true);
        this.visible = params.visible ? params.visible : ko.observable(true);
        this.allReferenceOptions = ko.utils.unwrapObservable(params.referenceOptions);
        this.lookupValueRequired = !!params.lookupValueRequired;

        this.properties = params.properties ? params.properties : ko.observable(this.targetEntity && this.targetEntity.getProperties());
        // If value passed in is an observable, use that
        if (params.value) {
            this._value = params.value;
        } else {
            this._value = ko.observable();
        }
        this.loadData = params.loadData;

        if (params.property) {
            this.property = params.property;
        } else {
            this.property = ko.computed(function () {
                var selectedId = this.propertyId && this.propertyId()[0];
                var selected;
                if (selectedId) {
                    $.each(this.properties(), function (index, prop) {
                        if (selectedId === prop.getId()) {
                            selected = prop;
                            return false;
                        }
                    });
                }
                return selected;
            }, this);
        }

        this.referenceValue = ko.observable();
        this.placeHolder = ko.observable();
        this.filteredReferenceOptions = ko.observableArray();
        this.expressionEditor = ko.observable();

        this.property.subscribe(function () {
            self.chooseValueComponent();
            self.fireEvent(FieldValueEditorViewModel.EVENT_VALUE_CHANGED);
        });
        this.targetPropertyType && this.targetPropertyType.subscribe(function () {
            self.chooseValueComponent();
            self.fireEvent(FieldValueEditorViewModel.EVENT_VALUE_CHANGED);
        });

        this.visible.subscribe(function () {
            self.chooseValueComponent();
        });

        this.usageId = 'usage' + _RANDOM_USAGE_ID++;

        this.value = ko.pureComputed({
            read: function () {
                var value = self._value();
                if (value && self.valueMode() === FieldValueEditorValueMode.EXPRESSION &&
                        value.indexOf('=') !== 0) {
                    value = '=' + value;
                }
                return value;
            },
            write: function (value) {
                self._value(value);
            }
        });

        this.value.subscribe(function () {
            self.fireEvent(FieldValueEditorViewModel.EVENT_VALUE_CHANGED);
        });

        this.editableInUI = ko.pureComputed(function () {
            var valueMode = this.valueMode();
            return this.editable() && (valueMode !== FieldValueEditorValueMode.EXPRESSION);
        }, this);

        this.expressionDisplayName = ko.observable('');
        this.ojComponentType = ko.observable('ojInputText');

        this.valueMode.subscribe(function () {
            self.chooseValueComponent();
        });
        this.displayValueComponent = ko.observable(this.visible());
        this.displayName = ko.pureComputed(function () {
            var value = this.expressionDisplayName() || this.value();
            if (this.referenceObjectValue) {
                value = this.referenceObjectValue()[this._relation.getDefaultDisplayProperty()] || value;
            }
            return value;
        }, this);
        this.valueModeLabel = ko.pureComputed(function () {
            var valueMode = this.valueMode();
            if (valueMode === FieldValueEditorValueMode.EXPRESSION) {
                return AbcsLib.i18n('components.fieldValueEditorExpression');
            } else if (valueMode === FieldValueEditorValueMode.STATIC) {
                return AbcsLib.i18n('components.fieldValueEditorStaticValue');
            } else if (valueMode === FieldValueEditorValueMode.REFERENCE) {
                return AbcsLib.i18n('components.fieldValueEditorReference');
            } else {
                return null;
            }
        }, this);
        this.openExpressionBuilder = function() {
            self._openExpressionEditor(FieldValueEditorValueMode.EXPRESSION);
        };

        this.menuItemSelect = function (event, ui) {
            var mode = ui.item.attr('data-item-mode');
            if (mode) {
                var previousMode = self.valueMode();
                self.switchMode(mode);
                if (mode === FieldValueEditorValueMode.EXPRESSION) {
                    self._openExpressionEditor(previousMode);
                }
            }
        };

        // keyup event binding is used only for ojInputNumber
        this.onKeyup = function (viewModel, event) {
            var inputElem = $(event.target).parent().find('input');
            inputElem.ojInputNumber('validate');
        };

        // onOptionChange binding is used for ojInputText.
        this.onOptionChange = function (event, data) {
            if (data.option === 'rawValue') {
                var inputElem = $(event.target).parent().find('input');
                inputElem.ojInputText('validate');
            }
        };

        this.chooseValueComponent(true);
    };
    AbcsLib.extend(FieldValueEditorViewModel, Listenable);

    FieldValueEditorViewModel.FieldValueEditorValueMode = FieldValueEditorValueMode; // Access to .html
    FieldValueEditorViewModel.EVENT_VALUE_CHANGED = 'valueChanged';

    FieldValueEditorViewModel.prototype.dispose = function () {
        this._disposed = true;
        this._disposeLOVPicker();
    };

    FieldValueEditorViewModel.prototype._openExpressionEditor = function (previousMode) {
        var self = this;
        if (this.executableExpressionsAllowed) {
            var value = FieldValueEditorViewModel._removeStartingEqualSign(this.value());
            var initialValueEditorModel = this.helper.createExpressionModel(
                    this.sourceEntity,
                    this.property(),
                    function (newInitialValue) {
                        self.value('=' + newInitialValue);
                    },
                    value);
            this.expressionEditor(initialValueEditorModel);
            initialValueEditorModel && initialValueEditorModel.open().then(function (finished) {
                self.expressionEditor(undefined);
                if (!finished) {
                    self.switchMode(previousMode);
                }
            });
        }
    };

    FieldValueEditorViewModel.prototype.switchMode = function (mode) {
        if (mode !== this.valueMode()) {
            this.value('');
            this.displayValueComponent(false);
        }
        this.valueMode(mode);
        this.displayValueComponent(this.visible());
    };

    FieldValueEditorViewModel.prototype.chooseValueComponent = function (isInit) {
        var oldComponentType = this.ojComponentType();
        var propertyType = this._getTargetPropertyType();
        this.displayValueComponent(false);
        this._updateExtraValueModel();
        var resetValue = false;

        var componentType = FieldValueEditorUtils.getComponentType(this.valueMode(), propertyType, this._relation);
        if (!componentType) {
            componentType = 'ojInputText';
        } else if (componentType === 'abcsComboBox') {
            resetValue = !this.value();
        } else if (componentType === 'ojSelect-reference') {
            var filtered = this._filterReferenceOptions();
            this.filteredReferenceOptions.removeAll();
            ko.utils.arrayPushAll(this.filteredReferenceOptions, filtered);
        }

        if (oldComponentType !== componentType) {
            if (!isInit || resetValue) {
                this.value('');
            }
            // this is necessary, without first removing the value are
            // and readding it again the components are incorrectly painted
            // Changing the component does not work, some artefacts still remain
            // in the DOM.
            this.ojComponentType(componentType);
        }

        this.displayValueComponent(this.visible());
    };

    /**
     * Get the target property type from targetEntity or targetPropertyType. If both are specified, targetPropertyType is used.
     */
    FieldValueEditorViewModel.prototype._getTargetPropertyType = function () {
        if (this.targetPropertyType) {
            return this.targetPropertyType();
        } else if (this.targetEntity) {
            var prop = this.property();
            return prop ? prop.getType() : PropertyType.TEXT; // If fieldId has not been specified, use ojInputText as the placeholder input component.
        } else {
            return PropertyType.TEXT;
        }
    };

    FieldValueEditorViewModel.prototype.getValueOptions = function () {
        return this.extraModel && this.extraModel.valueOptions || [];
    };

    FieldValueEditorViewModel._removeStartingEqualSign = function (value) {
        if (value && typeof value === 'string') {
            value = value.trim();
            if (value.indexOf('=') === 0) {
                value = value.substr(1);
            }
        }
        return value;
    };

    var ReferenceValueModel = function (parent, referenceValue) {
        var self = this;
        self.valueOption = ko.observableArray([parent.value()]);
        self.valueOptions = referenceValue ? [referenceValue] : [];
        self.valueOption.subscribe(function (newVal) {
            if (newVal instanceof Array) {
                if (newVal.length) {
                    newVal = newVal[0];
                } else {
                    newVal = '';
                }
            }
            parent.value(newVal);
        });
    };

    FieldValueEditorViewModel.prototype._updateExtraValueModel = function () {
        var previousReferenceValue = this.referenceValue();
        var property = this.property();
        var referenceValue = this.getReferenceValueCallback ?
            this.getReferenceValueCallback(property) : this.helper.getReferenceFor(property);
        if (previousReferenceValue && !referenceValue) {
            this.extraModel = undefined;
            this.value('');
        }
        if (this.savedProperty && property && this.savedProperty !== property && property.getType() === PropertyType.REFERENCE) {
            this.value('');
        }
        this.savedProperty = property;
        if (!previousReferenceValue && referenceValue) {
            this.extraModel = new ReferenceValueModel(this, referenceValue);
        }
        this.referenceValue(referenceValue);

        this._initLOVModel();
    };

    FieldValueEditorViewModel.prototype._initLOVModel = function () {
        var property = this.property();
        var initAsLOV;
        var relation = FieldValueEditorUtils.getRelation(this.runtimeMode, property);
        if (relation) {
            initAsLOV = true;
        }

        if (this._relation) {
            if (!initAsLOV) {
                this._disposeLOVPicker();
            } else if (this.valueMode() === FieldValueEditorValueMode.EXPRESSION) {
                // already initialized
                initAsLOV = false;
            }
        }
        if (initAsLOV) {
            this._relation = relation;
            this.referenceObjectValue = ko.observable();
            this.extraModel = new ComboboxPickerViewModel({
                relation: this._relation,
                value: this.value,
                required: this.lookupValueRequired,
                objectValue: this.referenceObjectValue,
                placeholder: this.placeHolder() || AbcsLib.i18n('components.comboBoxSelectValue'),
                loadData: this.loadData
            });
        }
    };

    FieldValueEditorViewModel.prototype._disposeLOVPicker = function () {
        if (this._relation) {
            this.extraModel.dispose();
            this.extraModel = undefined;
            this.value('');
            this.referenceObjectValue = undefined;
            this._relation = undefined;
        }
    };

    FieldValueEditorViewModel.prototype._filterReferenceOptions = function () {
        if (!this.allReferenceOptions) {
            this.allReferenceOptions = FieldValueEditorUtils.getFieldReferenceOptions(this.sourceEntity);
        }
        var lhsProperty = this.property(), rhsProperty;
        var lhsEntityId, rhsEntityId, option, options = [];
        var textSupportedPropertyTypes = [PropertyType.CURRENCY,
                                          PropertyType.EMAIL,
                                          PropertyType.NUMBER,
                                          PropertyType.PERCENTAGE,
                                          PropertyType.PHONE,
                                          PropertyType.URL];

        var dateSupportedPropertyTypes = [PropertyType.DATE, PropertyType.TIME];

        for (var i = 0; i < this.allReferenceOptions.length; i++) {
            option = this.allReferenceOptions[i];
            rhsProperty = option.property;
            if (lhsProperty && lhsProperty.getType() === rhsProperty.getType()) {
                if (lhsProperty.getType() === PropertyType.REFERENCE) {
                    lhsEntityId = FieldValueEditorUtils.getReferencedEntityFromProperty(lhsProperty).getId();
                    rhsEntityId = FieldValueEditorUtils.getReferencedEntityFromProperty(rhsProperty).getId();
                    if (lhsEntityId === rhsEntityId) {
                        options.push(option);
                    }
                } else {
                    options.push(option);
                }
            } else if (lhsProperty && lhsProperty.getType() === PropertyType.REFERENCE && rhsProperty.getType() === PropertyType.KEY) {
                lhsEntityId = FieldValueEditorUtils.getReferencedEntityFromProperty(lhsProperty).getId();
                rhsEntityId = rhsProperty.getEntity().getId();
                if (lhsEntityId === rhsEntityId) {
                    options.push(option);
                }
            } else if (!lhsProperty) {
                if (this.targetPropertyType() === rhsProperty.getType()
                    || this.targetPropertyType() === PropertyType.TEXT && textSupportedPropertyTypes.indexOf(rhsProperty.getType()) !== -1
                    || this.targetPropertyType() === PropertyType.DATETIME && dateSupportedPropertyTypes.indexOf(rhsProperty.getType()) !== -1) {
                        options.push(option);
                }
            }
        }
        return options;
    };

    return FieldValueEditorViewModel;
});