Source: argos-sdk/src/_SDataEditMixin.js

/* Copyright (c) 2010, Sage Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @class argos._SDataEditMixin
 * @classdesc Enables SData for the Edit view.
 * Extends the SDataDetail Mixin by providing functions for $template requests.
 * @extends argos._SDataDetailMixin
 * @requires argos.SData
 */
import declare from 'dojo/_base/declare';
import lang from 'dojo/_base/lang';

import convert from './Convert';
import _SDataDetailMixin from './_SDataDetailMixin';
import MODEL_TYPES from './Models/Types';

const __class = declare('argos._SDataEditMixin', [_SDataDetailMixin], /** @lends argos._SDataEditMixin# */{
  /**
   * @property {Object}
   * The saved SData response.
   */
  entry: null,

  /**
   * @property {Object}
   * The saved template SData response.
   */
  templateEntry: null,
  diffPropertyIgnores: [
    '$etag',
    '$updated',
  ],

  _buildRefreshMessage: function _buildRefreshMessage() {
    const message = this.inherited(arguments);

    return lang.mixin(message, {
      resourceKind: this.resourceKind,
    });
  },
  onRefresh: function onRefresh() {
    this.entry = false;
  },
  onRefreshInsert: function onRefreshInsert() {
    if (this.options.template) {
      this.processTemplateEntry(this.options.template);
    } else {
      this.requestTemplate();
    }
  },
  createEntryForUpdate: function createEntryForUpdate(v) {
    let values = v;
    values = this.inherited(arguments);
    values = lang.mixin(values, {
      $key: this.entry.$key,
      $etag: this.entry.$etag,
      $name: this.entry.$name,
    });

    if (!this._isConcurrencyCheckEnabled()) {
      delete values.$etag;
    }

    return values;
  },
  createEntryForInsert: function createEntryForInsert(v) {
    let values = v;
    values = this.inherited(arguments);
    return lang.mixin(values, {
      $name: this.entityName,
    });
  },
  /**
   * ApplyContext is called during {@link #processTemplateEntry processTemplateEntry} and is
   * intended as a hook for when you are inserting a new entry (not editing) and wish to apply
   * values from context, ie, from a view in the history.
   *
   * The cycle of a template values is (first to last, last being the one that overwrites all)
   *
   * 1\. Set the values of the template SData response
   * 2\. Set any field defaults (the fields `default` property)
   * 3\. ApplyContext is called
   * 4\. If `this.options.entry` is defined, apply those values
   *
   * @param templateEntry
   */
  applyContext: function applyContext(/* templateEntry*/) {},
  /**
   * Creates Sage.SData.Client.SDataTemplateResourceRequest instance and sets a number of known properties.
   *
   * List of properties used `this.property/this.options.property`:
   *
   * `resourceKind`, `querySelect`, `queryInclude`
   *
   * @return {Object} Sage.SData.Client.SDataTemplateResourceRequest instance.
   */
  createTemplateRequest: function createTemplateRequest() {
    const request = new Sage.SData.Client.SDataTemplateResourceRequest(this.getService());

    if (this.resourceKind) {
      request.setResourceKind(this.resourceKind);
    }

    if (this.querySelect) {
      request.setQueryArg(Sage.SData.Client.SDataUri.QueryArgNames.Select, this.querySelect.join(','));
    }

    if (this.queryInclude) {
      request.setQueryArg(Sage.SData.Client.SDataUri.QueryArgNames.Include, this.queryInclude.join(','));
    }

    if (this.contractName) {
      request.setContractName(this.contractName);
    }

    return request;
  },
  /**
   * Initiates the SData request for the template (default values).
   */
  requestTemplate: function requestTemplate() {
    const request = this.createTemplateRequest();
    if (request) {
      request.read({
        success: this.onRequestTemplateSuccess,
        failure: this.onRequestTemplateFailure,
        scope: this,
      });
    }
  },
  /**
   * Handler when an error occurs while request data from the SData endpoint.
   * @param {Object} response The response object.
   * @param {Object} o The options that were passed when creating the Ajax request.
   */
  onRequestTemplateFailure: function onRequestTemplateFailure(response/* , o*/) {
    this.handleError(response);
  },
  /**
   * Handler when a request to SData is successful, calls processTemplateEntry
   * @param {Object} entry The SData response
   */
  onRequestTemplateSuccess: function onRequestTemplateSuccess(entry) {
    this.processTemplateEntry(entry);
  },
  /**
   * Processes the returned SData template entry by saving it to `this.templateEntry` and applies
   * the default values to fields by:
   *
   * The cycle of a template values is (first to last, last being the one that overwrites all)
   *
   * 1\. Set the values of the template SData response
   * 2\. Set any field defaults (the fields `default` property)
   * 3\. ApplyContext is called
   * 4\. If `this.options.entry` is defined, apply those values
   *
   * @param {Object} templateEntry SData template entry
   */
  processTemplateEntry: function processTemplateEntry(templateEntry) {
    this.templateEntry = this.convertEntry(templateEntry || {});
    this.resetInteractionState();
    this.setValues(this.templateEntry, true);
    this.applyFieldDefaults();
    this.applyContext(this.templateEntry);

    // if an entry has been passed through options, apply it here, now that the template has been applied.
    // in this case, since we are doing an insert (only time template is used), the entry is applied as modified data.
    if (this.options.entry) {
      this.entry = this.convertEntry(this.options.entry);
      this.setValues(this.entry);
    }

    $(this.domNode).removeClass('panel-loading');
  },
  /**
   * Does the reverse of {@link #convertEntry convertEntry} in that it loops the payload being
   * sent back to SData and converts Date objects into SData date strings
   * @param {Object} values Payload
   * @return {Object} Entry with string dates
   */
  convertValues: function convertValues(values) {
    for (const n in values) {
      if (values[n] instanceof Date) {
        values[n] = this.getService().isJsonEnabled() ? convert.toJsonStringFromDate(values[n]) : convert.toIsoStringFromDate(values[n]);
      }
    }

    return values;
  },
  /**
   * Loops a given entry testing for SData date strings and converts them to javascript Date objects
   * @param {Object} entry SData entry
   * @return {Object} Entry with actual Date objects
   */
  convertEntry: function convertEntry(entry) {
    for (const n in entry) {
      if (convert.isDateString(entry[n])) {
        entry[n] = convert.toDateFromString(entry[n]);
      }
    }

    return entry;
  },
  resetInteractionState: function resetInteractionState() {
    if (this._previousFieldState === null) {
      this._previousFieldState = {};
      return;
    }

    // Restore the previous state of each field before the security was applied
    Object.keys(this._previousFieldState)
      .forEach((k) => {
        const field = this.fields[k];
        const previousState = this._previousFieldState[k];

        if (previousState.disabled) {
          field.disable();
        } else {
          field.enable();
        }

        if (previousState.hidden) {
          field.hide();
        } else {
          field.show();
        }
      });

    this._previousFieldState = {};
  },
  _previousFieldState: null,
  processFieldLevelSecurity: function processFieldLevelSecurity(entry) {
    this.resetInteractionState();
    const { $permissions: permissions } = entry;
    // permissions is an array of objects:
    // { name: "FieldName", access: "ReadOnly" }
    if (permissions && permissions.length) {
      permissions.forEach((p) => {
        const { name, access } = p;
        const field = this.fields[name];
        if (!field) {
          return;
        }

        // Capture the current state before we apply the changes
        this._previousFieldState[name] = {
          disabled: field.isDisabled(),
          hidden: field.isHidden(),
        };

        if (access === 'NoAccess') {
          field.disable();
          field.hide();
        } else if (access === 'ReadOnly') {
          field.disable();
        }
      });
    }
  },
  _applyStateToPutOptions: function _applyStateToPutOptions(putOptions) {
    const store = this.get('store');

    if (this._isConcurrencyCheckEnabled()) {
      // The SData store will take the version and apply it to the etag
      putOptions.version = store.getVersion(this.entry);
    }

    putOptions.entity = store.getEntity(this.entry) || this.entityName;
  },
  _applyStateToAddOptions: function _applyStateToAddOptions(addOptions) {
    addOptions.entity = this.entityName;
  },
  _isConcurrencyCheckEnabled: function _isConcurrencyCheckEnabled() {
    return App && App.enableConcurrencyCheck;
  },
  initModel: function initModel() {
    const model = this.getModel();
    if (model) {
      this._model = model;
      this._model.init();
      if (this._model.ModelType === MODEL_TYPES.SDATA) {
        this._applyViewToModel(this._model);
      }
    }
  },
  _applyViewToModel: function _applyViewToModel(model) {
    if (!model) {
      return;
    }

    const queryModel = model._getQueryModelByName('detail');
    if (this.resourceKind) {
      model.resourceKind = this.resourceKind;
    }

    if (!queryModel) {
      return;
    }

    // Attempt to mixin the view's querySelect, queryInclude, queryWhere,
    // queryArgs, queryOrderBy, resourceProperty, resourcePredicate properties
    // into the layout. The past method of extending a querySelect for example,
    // was to modify the protoype of the view's querySelect array.
    if (this.querySelect && this.querySelect.length) {
      /* eslint-disable */
      console.warn(`A view's querySelect is deprecated. Register a customization to the models layout instead.`);
      /* eslint-enable */
      if (!queryModel.querySelect) {
        queryModel.querySelect = [];
      }

      queryModel.querySelect = queryModel.querySelect.concat(this.querySelect.filter((item) => {
        return queryModel.querySelect.indexOf(item) < 0;
      }));
    }

    if (this.queryInclude && this.queryInclude.length) {
      /* eslint-disable */
      console.warn(`A view's queryInclude is deprecated. Register a customization to the models layout instead.`);
      /* eslint-enable */
      if (!queryModel.queryInclude) {
        queryModel.queryInclude = [];
      }

      queryModel.queryInclude = queryModel.queryInclude.concat(this.queryInclude.filter((item) => {
        return queryModel.queryInclude.indexOf(item) < 0;
      }));
    }

    if (this.queryWhere) {
      /* eslint-disable */
      console.warn(`A view's queryWhere is deprecated. Register a customization to the models layout instead.`);
      /* eslint-enable */
      queryModel.queryWhere = this.queryWhere;
    }

    if (this.queryArgs) {
      /* eslint-disable */
      console.warn(`A view's queryArgs is deprecated. Register a customization to the models layout instead.`);
      /* eslint-enable */
      queryModel.queryArgs = lang.mixin({}, queryModel.queryArgs, this.queryArgs);
    }

    if (this.queryOrderBy && this.queryOrderBy.length) {
      /* eslint-disable */
      console.warn(`A view's queryOrderBy is deprecated. Register a customization to the models layout instead.`);
      /* eslint-enable */
      if (Array.isArray(this.queryOrderBy)) {
        if (!queryModel.queryOrderBy) {
          queryModel.queryOrderBy = [];
        }

        queryModel.queryOrderBy = queryModel.queryOrderBy.concat(this.queryOrderBy.filter((item) => {
          return queryModel.queryOrderBy.indexOf(item) < 0;
        }));
      } else {
        queryModel.queryOrderBy = this.queryOrderBy;
      }
    }

    if (this.resourceProperty) {
      /* eslint-disable */
      console.warn(`A view's resourceProperty is deprecated. Register a customization to the models layout instead.`);
      /* eslint-enable */
      queryModel.resourceProperty = this.resourceProperty;
    }

    if (this.resourcePredicate) {
      /* eslint-disable */
      console.warn(`A view's resourcePredicate is deprecated. Register a customization to the models layout instead.`);
      /* eslint-enable */
      queryModel.resourcePredicate = this.resourcePredicate;
    }
  },
});

export default __class;