'use strict';
//region Imports
var util = require('util');
var config = require('./shared/configuration-reader.js');
var Argument = require('./system/argument-check.js');
var Enumeration = require('./system/enumeration.js');
var ModelBase = require('./model-base.js');
var ModelError = require('./shared/model-error.js');
var ExtensionManagerSync = require('./shared/extension-manager-sync.js');
var EventHandlerList = require('./shared/event-handler-list.js');
var DataStore = require('./shared/data-store.js');
var DataType = require('./data-types/data-type.js');
var PropertyInfo = require('./shared/property-info.js');
var PropertyManager = require('./shared/property-manager.js');
var PropertyContext = require('./shared/property-context.js');
var ValidationContext = require('./rules/validation-context.js');
var TransferContext = require('./shared/transfer-context.js');
var RuleManager = require('./rules/rule-manager.js');
var DataTypeRule = require('./rules/data-type-rule.js');
var BrokenRuleList = require('./rules/broken-rule-list.js');
var RuleSeverity = require('./rules/rule-severity.js');
var AuthorizationAction = require('./rules/authorization-action.js');
var AuthorizationContext = require('./rules/authorization-context.js');
var DataPortalAction = require('./shared/data-portal-action.js');
var DataPortalContext = require('./shared/data-portal-context.js');
var DataPortalEvent = require('./shared/data-portal-event.js');
var DataPortalEventArgs = require('./shared/data-portal-event-args.js');
var DataPortalError = require('./shared/data-portal-error.js');
var MODEL_STATE = require('./shared/model-state.js');
var CLASS_NAME = 'EditableChildModelSync';
var MODEL_DESC = 'Editable child model';
var M_FETCH = DataPortalAction.getName(DataPortalAction.fetch);
//endregion
/**
* Factory method to create definitions of synchronous editable child models.
*
* Valid child model types are:
*
* * EditableChildCollectionSync
* * EditableChildModelSync
*
* @function bo.EditableChildModelSync
* @param {bo.shared.PropertyManager} properties - The property definitions.
* @param {bo.shared.RuleManager} rules - The validation and authorization rules.
* @param {bo.shared.ExtensionManager} extensions - The customization of the model.
* @returns {EditableChildModelSync} The constructor of a synchronous editable child model.
*
* @throws {@link bo.system.ArgumentError Argument error}: The properties must be a PropertyManager object.
* @throws {@link bo.system.ArgumentError Argument error}: The rules must be a RuleManager object.
* @throws {@link bo.system.ArgumentError Argument error}: The extensions must be a ExtensionManagerSync object.
*
* @throws {@link bo.shared.ModelError Model error}:
* The child objects must be EditableChildCollectionSync or EditableChildModelSync instances.
*/
var EditableChildModelSyncFactory = function (properties, rules, extensions) {
var check = Argument.inConstructor(CLASS_NAME);
properties = check(properties).forMandatory('properties').asType(PropertyManager);
rules = check(rules).forMandatory('rules').asType(RuleManager);
extensions = check(extensions).forMandatory('extensions').asType(ExtensionManagerSync);
// Verify the model types of child models.
properties.verifyChildTypes([ 'EditableChildCollectionSync', 'EditableChildModelSync' ]);
// Get data access object.
var dao = extensions.getDataAccessObject(properties.name);
/**
* @classdesc
* Represents the definition of a synchronous editable child model.
* @description
* Creates a new synchronous editable child model instance.
*
* _The name of the model type available as:
* __<instance>.constructor.modelType__, returns 'EditableChildModelSync'._
*
* Valid parent model types are:
*
* * EditableChildCollectionSync
* * EditableRootModelSync
* * EditableChildModelSync
*
* @name EditableChildModelSync
* @constructor
* @param {object} parent - The parent business object.
* @param {bo.shared.EventHandlerList} [eventHandlers] - The event handlers of the instance.
*
* @extends ModelBase
*
* @throws {@link bo.system.ArgumentError Argument error}:
* The parent object must be an EditableChildCollectionSync, EditableRootModelSync or
* EditableChildModelSync instance.
* @throws {@link bo.system.ArgumentError Argument error}:
* The event handlers must be an EventHandlerList object or null.
*
* @fires EditableChildModelSync#preCreate
* @fires EditableChildModelSync#postCreate
* @fires EditableChildModelSync#preFetch
* @fires EditableChildModelSync#postFetch
* @fires EditableChildModelSync#preInsert
* @fires EditableChildModelSync#postInsert
* @fires EditableChildModelSync#preUpdate
* @fires EditableChildModelSync#postUpdate
* @fires EditableChildModelSync#preRemove
* @fires EditableChildModelSync#postRemove
*/
var EditableChildModelSync = function (parent, eventHandlers) {
ModelBase.call(this);
var check = Argument.inConstructor(properties.name);
// Verify the model type of the parent model.
parent = check(parent).for('parent').asModelType([
'EditableRootCollectionSync',
'EditableChildCollectionSync',
'EditableRootModelSync',
'EditableChildModelSync'
]);
eventHandlers = check(eventHandlers).forOptional('eventHandlers').asType(EventHandlerList);
var self = this;
var state = null;
var isDirty = false;
var store = new DataStore();
var brokenRules = new BrokenRuleList(properties.name);
var isValidated = false;
var propertyContext = null;
var dataContext = null;
// Set up business rules.
rules.initialize(config.noAccessBehavior);
// Set up event handlers.
if (eventHandlers)
eventHandlers.setup(self);
//region Mark object state
/*
* From: To: | pri | cre | cha | mfr | rem
* -------------------------------------------------
* NULL | + | + | N | N | N
* -------------------------------------------------
* pristine | o | - | + | + | -
* -------------------------------------------------
* created | + | o | o | (-) | +
* -------------------------------------------------
* changed | + | - | o | + | -
* -------------------------------------------------
* markedForRemoval | - | - | o | o | +
* -------------------------------------------------
* removed | - | - | - | - | o
* -------------------------------------------------
*
* Explanation:
* + : possible transition
* - : not allowed transition, throws exception
* o : no change, no action
* N : impossible start up, throws exception
*/
function markAsPristine() {
if (state === MODEL_STATE.markedForRemoval || state === MODEL_STATE.removed)
illegal(MODEL_STATE.pristine);
else if (state !== MODEL_STATE.pristine) {
state = MODEL_STATE.pristine;
isDirty = false;
}
}
function markAsCreated() {
if (state === null) {
state = MODEL_STATE.created;
isDirty = true;
propagateChange(); // up to the parent
}
else if (state !== MODEL_STATE.created)
illegal(MODEL_STATE.created);
}
function markAsChanged(itself) {
if (state === MODEL_STATE.pristine) {
state = MODEL_STATE.changed;
isDirty = isDirty || itself;
propagateChange(); // up to the parent
isValidated = false;
}
else if (state === MODEL_STATE.created) {
isDirty = isDirty || itself;
propagateChange(); // up to the parent
isValidated = false;
}
else if (state === MODEL_STATE.removed)
illegal(MODEL_STATE.changed);
}
function markForRemoval() {
if (state === MODEL_STATE.pristine || state === MODEL_STATE.changed) {
state = MODEL_STATE.markedForRemoval;
isDirty = true;
propagateRemoval(); // down to children
propagateChange(); // up to the parent
}
else if (state === MODEL_STATE.created)
state = MODEL_STATE.removed;
else if (state !== MODEL_STATE.markedForRemoval)
illegal(MODEL_STATE.markedForRemoval);
}
function markAsRemoved() {
if (state === MODEL_STATE.created || state === MODEL_STATE.markedForRemoval) {
state = MODEL_STATE.removed;
isDirty = false;
}
else if (state !== MODEL_STATE.removed)
illegal(MODEL_STATE.removed);
}
function illegal(newState) {
throw new ModelError('transition',
(state == null ? 'NULL' : MODEL_STATE.getName(state)),
MODEL_STATE.getName(newState));
}
function propagateChange() {
parent.childHasChanged();
}
/**
* Notes that a child object has changed.
* <br/>_This method is called by child objects._
*
* @function EditableChildModelSync#childHasChanged
* @protected
*/
this.childHasChanged = function() {
markAsChanged(false);
};
function propagateRemoval() {
properties.children().forEach(function(property) {
var child = getPropertyValue(property);
child.remove();
});
}
//endregion
//region Show object state
/**
* Gets the state of the model. Valid states are:
* pristine, created, changed, markedForRemoval and removed.
*
* @function EditableChildModelSync#getModelState
* @returns {string} The state of the model.
*/
this.getModelState = function () {
return MODEL_STATE.getName(state);
};
/**
* Indicates whether the business object has been created newly and
* not has been yet saved, i.e. its state is created.
*
* @function EditableChildModelSync#isNew
* @returns {boolean} True when the business object is new, otherwise false.
*/
this.isNew = function () {
return state === MODEL_STATE.created;
};
/**
* Indicates whether the business object itself or any of its child objects differs the one
* that is stored in the repository, i.e. its state is created, changed or markedForRemoval.
*
* @function EditableChildModelSync#isDirty
* @returns {boolean} True when the business object has been changed, otherwise false.
*/
this.isDirty = function () {
return state === MODEL_STATE.created ||
state === MODEL_STATE.changed ||
state === MODEL_STATE.markedForRemoval;
};
/**
* Indicates whether the business object itself, ignoring its child objects, differs the one
* that is stored in the repository.
*
* @function EditableChildModelSync#isSelfDirty
* @returns {boolean} True when the business object itself has been changed, otherwise false.
*/
this.isSelfDirty = function () {
return isDirty;
};
/**
* Indicates whether the business object will be deleted from the repository,
* i.e. its state is markedForRemoval.
*
* @function EditableChildModelSync#isDeleted
* @returns {boolean} True when the business object will be deleted, otherwise false.
*/
this.isDeleted = function () {
return state === MODEL_STATE.markedForRemoval;
};
//endregion
//region Transfer object methods
function getTransferContext (authorize) {
return authorize ?
new TransferContext(properties.toArray(), readPropertyValue, writePropertyValue) :
new TransferContext(properties.toArray(), getPropertyValue, setPropertyValue);
}
function baseToDto() {
var dto = {};
properties.filter(function (property) {
return property.isOnDto;
}).forEach(function (property) {
dto[property.name] = getPropertyValue(property);
});
return dto;
}
function toDto () {
if (extensions.toDto)
return extensions.toDto.call(self, getTransferContext(false));
else
return baseToDto();
}
function baseFromDto(dto) {
properties.filter(function (property) {
return property.isOnDto;
}).forEach(function (property) {
if (dto.hasOwnProperty(property.name) && typeof dto[property.name] !== 'function') {
setPropertyValue(property, dto[property.name]);
}
});
}
function fromDto (dto) {
if (extensions.fromDto)
extensions.fromDto.call(self, getTransferContext(false), dto);
else
baseFromDto(dto);
}
function baseToCto() {
var cto = {};
properties.filter(function (property) {
return property.isOnCto;
}).forEach(function (property) {
cto[property.name] = readPropertyValue(property);
});
return cto;
}
/**
* Transforms the business object to a plain object to send to the client.
* <br/>_This method is usually called by the parent object._
*
* @function EditableChildModelSync#toCto
* @returns {object} The client transfer object.
*/
this.toCto = function () {
var cto = {};
if (extensions.toCto)
cto = extensions.toCto.call(self, getTransferContext(true));
else
cto = baseToCto();
properties.children().forEach(function(property) {
var child = getPropertyValue(property);
cto[property.name] = child.toCto();
});
return cto;
};
function baseFromCto(cto) {
if (cto && typeof cto === 'object') {
properties.filter(function (property) {
return property.isOnCto;
}).forEach(function (property) {
if (cto.hasOwnProperty(property.name) && typeof cto[property.name] !== 'function') {
writePropertyValue(property, cto[property.name]);
}
});
}
}
/**
* Rebuilds the business object from a plain object sent by the client.
* <br/>_This method is usually called by the parent object._
*
* @function EditableChildModelSync#fromCto
* @param {object} cto - The client transfer object.
*/
this.fromCto = function (cto) {
if (extensions.fromCto)
extensions.fromCto.call(self, getTransferContext(true), cto);
else
baseFromCto(cto);
properties.children().forEach(function(property) {
var child = getPropertyValue(property);
if (cto[property.name]) {
child.fromCto(cto[property.name]);
}
});
};
/**
* Determines that the passed data contains current values of the model key.
*
* @function EditableChildModelSync#keyEquals
* @protected
* @param {object} data - Data object whose properties can contain the values of the model key.
* @param {internal~getValue} getPropertyValue - A function that returns
* the current value of the given property.
* @returns {boolean} True when the values are equal, false otherwise.
*/
this.keyEquals = function (data) {
return properties.keyEquals(data, getPropertyValue);
};
//endregion
//region Permissions
function getAuthorizationContext(action, targetName) {
return new AuthorizationContext(action, targetName || '', brokenRules);
}
function canBeRead (property) {
return rules.hasPermission(
getAuthorizationContext(AuthorizationAction.readProperty, property.name)
);
}
function canBeWritten (property) {
return rules.hasPermission(
getAuthorizationContext(AuthorizationAction.writeProperty, property.name)
);
}
function canDo (action) {
return rules.hasPermission(
getAuthorizationContext(action)
);
}
function canExecute (methodName) {
return rules.hasPermission(
getAuthorizationContext(AuthorizationAction.executeMethod, methodName)
);
}
//endregion
//region Child methods
function fetchChildren(dto) {
properties.children().forEach(function(property) {
var child = getPropertyValue(property);
child.fetch(dto[property.name]);
});
}
function insertChildren(connection) {
properties.children().forEach(function(property) {
var child = getPropertyValue(property);
child.save(connection);
});
}
function updateChildren(connection) {
properties.children().forEach(function(property) {
var child = getPropertyValue(property);
child.save(connection);
});
}
function removeChildren(connection) {
properties.children().forEach(function(property) {
var child = getPropertyValue(property);
child.save(connection);
});
}
function childrenAreValid() {
return properties.children().every(function(property) {
var child = getPropertyValue(property);
return child.isValid();
});
}
function checkChildRules() {
properties.children().forEach(function(property) {
var child = getPropertyValue(property);
child.checkRules();
});
}
function getChildBrokenRules (namespace, bro) {
properties.children().forEach(function (property) {
var child = getPropertyValue(property);
var childBrokenRules = child.getBrokenRules(namespace);
if (childBrokenRules) {
if (childBrokenRules instanceof Array)
bro.addChildren(property.name, childBrokenRules);
else
bro.addChild(property.name, childBrokenRules);
}
});
return bro;
}
//endregion
//region Data portal methods
//region Helper
function getDataContext (connection) {
if (!dataContext)
dataContext = new DataPortalContext(
dao, properties.toArray(), getPropertyValue, setPropertyValue
);
return dataContext.setState(connection, isDirty);
}
function raiseEvent (event, methodName, error) {
self.emit(
DataPortalEvent.getName(event),
new DataPortalEventArgs(event, properties.name, null, methodName, error)
);
}
function wrapError (action, error) {
return new DataPortalError(MODEL_DESC, properties.name, action, error);
}
//endregion
//region Create
function data_create () {
if (extensions.dataCreate || dao.$hasCreate()) {
var connection = null;
try {
// Open connection.
connection = config.connectionManager.openConnection(extensions.dataSource);
// Launch start event.
/**
* The event arises before the business object instance will be initialized in the repository.
* @event EditableChildModelSync#preCreate
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} oldObject - The instance of the model before the data portal action.
*/
raiseEvent(DataPortalEvent.preCreate);
// Execute creation.
if (extensions.dataCreate) {
// *** Custom creation.
extensions.dataCreate.call(self, getDataContext(connection));
} else {
// *** Standard creation.
var dto = dao.$runMethod('create', connection);
fromDto.call(self, dto);
}
markAsCreated();
// Launch finish event.
/**
* The event arises after the business object instance has been initialized in the repository.
* @event EditableChildModelSync#postCreate
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} newObject - The instance of the model after the data portal action.
*/
raiseEvent(DataPortalEvent.postCreate);
// Close connection.
connection = config.connectionManager.closeConnection(extensions.dataSource, connection);
} catch (e) {
// Wrap the intercepted error.
var dpError = wrapError(DataPortalAction.create, e);
// Launch finish event.
if (connection)
raiseEvent(DataPortalEvent.postCreate, null, dpError);
// Close connection.
connection = config.connectionManager.closeConnection(extensions.dataSource, connection);
// Rethrow error.
throw dpError;
}
}
}
//endregion
//region Fetch
function data_fetch (data, method) {
// Check permissions.
if (method === M_FETCH ? canDo(AuthorizationAction.fetchObject) : canExecute(method)) {
try {
// Launch start event.
/**
* The event arises before the business object instance will be retrieved from the repository.
* @event EditableChildModelSync#preFetch
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} oldObject - The instance of the model before the data portal action.
*/
raiseEvent(DataPortalEvent.preFetch, method);
// Execute fetch.
var dto = null;
if (extensions.dataFetch) {
// *** Custom fetch.
dto = extensions.dataFetch.call(self, getDataContext(null), data, method);
} else {
// *** Standard fetch.
// Child element gets data from parent.
dto = data;
fromDto.call(self, dto);
}
// Fetch children as well.
fetchChildren(dto);
markAsPristine();
// Launch finish event.
/**
* The event arises after the business object instance has been retrieved from the repository.
* @event EditableChildModelSync#postFetch
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} newObject - The instance of the model after the data portal action.
*/
raiseEvent(DataPortalEvent.postFetch, method);
} catch (e) {
// Wrap the intercepted error.
var dpError = wrapError(DataPortalAction.fetch, e);
// Launch finish event.
raiseEvent(DataPortalEvent.postFetch, method, dpError);
// Rethrow the original error.
throw e;
}
}
}
//endregion
//region Insert
function data_insert (connection) {
// Check permissions.
if (canDo(AuthorizationAction.createObject)) {
try {
// Launch start event.
/**
* The event arises before the business object instance will be created in the repository.
* @event EditableChildModelSync#preInsert
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} oldObject - The instance of the model before the data portal action.
*/
raiseEvent(DataPortalEvent.preInsert);
// Copy the values of parent keys.
var references = properties.filter(function (property) {
return property.isParentKey;
});
for (var i = 0; i < references.length; i++) {
var referenceProperty = references[i];
var parentValue = parent[referenceProperty.name];
if (parentValue !== undefined)
setPropertyValue(referenceProperty, parentValue);
}
// Execute insert.
if (extensions.dataInsert) {
// *** Custom insert.
extensions.dataInsert.call(self, getDataContext(connection));
} else {
// *** Standard insert.
var dto = toDto.call(self);
dto = dao.$runMethod('insert', connection, dto);
fromDto.call(self, dto);
}
// Insert children as well.
insertChildren(connection);
markAsPristine();
// Launch finish event.
/**
* The event arises after the business object instance has been created in the repository.
* @event EditableChildModelSync#postInsert
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} newObject - The instance of the model after the data portal action.
*/
raiseEvent(DataPortalEvent.postInsert);
} catch (e) {
// Wrap the intercepted error.
var dpError = wrapError(DataPortalAction.insert, e);
// Launch finish event.
raiseEvent(DataPortalEvent.postInsert, null, dpError);
// Rethrow the original error.
throw e;
}
}
}
//endregion
//region Update
function data_update (connection) {
// Check permissions.
if (canDo(AuthorizationAction.updateObject)) {
try {
// Launch start event.
/**
* The event arises before the business object instance will be updated in the repository.
* @event EditableChildModelSync#preUpdate
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} oldObject - The instance of the model before the data portal action.
*/
raiseEvent(DataPortalEvent.preUpdate);
// Execute update.
if (extensions.dataUpdate) {
// *** Custom update.
extensions.dataUpdate.call(self, getDataContext(connection));
} else if (isDirty) {
// *** Standard update.
var dto = toDto.call(self);
dto = dao.$runMethod('update', connection, dto);
fromDto.call(self, dto);
}
// Update children as well.
updateChildren(connection);
markAsPristine();
// Launch finish event.
/**
* The event arises after the business object instance has been updated in the repository.
* @event EditableChildModelSync#postUpdate
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} newObject - The instance of the model after the data portal action.
*/
raiseEvent(DataPortalEvent.postUpdate);
} catch (e) {
// Wrap the intercepted error.
var dpError = wrapError(DataPortalAction.update, e);
// Launch finish event.
raiseEvent(DataPortalEvent.postUpdate, null, dpError);
// Rethrow the original error.
throw e;
}
}
}
//endregion
//region Remove
function data_remove (connection) {
// Check permissions.
if (canDo(AuthorizationAction.removeObject)) {
try {
// Launch start event.
/**
* The event arises before the business object instance will be removed from the repository.
* @event EditableChildModelSync#preRemove
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} oldObject - The instance of the model before the data portal action.
*/
raiseEvent(DataPortalEvent.preRemove);
// Remove children first.
removeChildren(connection);
// Execute delete.
if (extensions.dataRemove) {
// *** Custom removal.
extensions.dataRemove.call(self, getDataContext(connection));
} else {
// *** Standard removal.
var filter = properties.getKey(getPropertyValue);
dao.$runMethod('remove', connection, filter);
}
markAsRemoved();
// Launch finish event.
/**
* The event arises after the business object instance has been removed from the repository.
* @event EditableChildModelSync#postRemove
* @param {bo.shared.DataPortalEventArgs} eventArgs - Data portal event arguments.
* @param {EditableChildModelSync} newObject - The instance of the model after the data portal action.
*/
raiseEvent(DataPortalEvent.postRemove);
} catch (e) {
// Wrap the intercepted error.
var dpError = wrapError(DataPortalAction.remove, e);
// Launch finish event.
raiseEvent(DataPortalEvent.postRemove, null, dpError);
// Rethrow the original error.
throw e;
}
}
}
//endregion
//endregion
//region Actions
/**
* Initializes a newly created business object.
* <br/>_This method is called by the parent object._
*
* @function EditableChildModelSync#create
* @protected
*/
this.create = function() {
data_create();
};
/**
* Initializes a business object with data retrieved from the repository.
* <br/>_This method is called by the parent object._
*
* @function EditableChildModelSync#fetch
* @protected
* @param {object} [data] - The data to load into the business object.
* @param {string} [method] - An alternative fetch method to check for permission.
*/
this.fetch = function(data, method) {
data_fetch(data, method || M_FETCH);
};
/**
* Saves the changes of the business object to the repository.
* <br/>_This method is called by the parent object._
*
* @function EditableChildModelSync#save
* @protected
* @param {object} connection - The connection data.
* @returns {EditableChildModelSync} The business object with the new state after the save.
*/
this.save = function(connection) {
if (this.isValid()) {
switch (state) {
case MODEL_STATE.created:
data_insert(connection);
return this;
case MODEL_STATE.changed:
data_update(connection);
return this;
case MODEL_STATE.markedForRemoval:
data_remove(connection);
return null;
default:
return this;
}
}
};
/**
* Marks the business object to be deleted from the repository on next save.
*
* @function EditableChildModelSync#remove
*/
this.remove = function() {
markForRemoval();
};
//endregion
//region Validation
/**
* Indicates whether all the validation rules of the business object, including
* the ones of its child objects, succeeds. A valid business object may have
* broken rules with severity of success, information and warning.
*
* _This method is called by the parent object._
*
* @function EditableChildModelSync#isValid
* @protected
* @returns {boolean} True when the business object is valid, otherwise false.
*/
this.isValid = function() {
if (!isValidated)
this.checkRules();
return brokenRules.isValid() && childrenAreValid();
};
/**
* Executes all the validation rules of the business object, including the ones
* of its child objects.
*
* _This method is called by the parent object._
*
* @function EditableChildModelSync#checkRules
* @protected
*/
this.checkRules = function() {
brokenRules.clear();
var context = new ValidationContext(store, brokenRules);
properties.forEach(function(property) {
rules.validate(property, context);
});
checkChildRules();
isValidated = true;
};
/**
* Gets the broken rules of the business object.
*
* _This method is called by the parent object._
*
* @function EditableChildModelSync#getBrokenRules
* @protected
* @param {string} [namespace] - The namespace of the message keys when messages are localizable.
* @returns {bo.rules.BrokenRulesOutput} The broken rules of the business object.
*/
this.getBrokenRules = function(namespace) {
var bro = brokenRules.output(namespace);
bro = getChildBrokenRules(namespace, bro);
return bro.$length ? bro : null;
};
//endregion
//region Properties
function getPropertyValue(property) {
return store.getValue(property);
}
function setPropertyValue(property, value) {
if (store.setValue(property, value))
markAsChanged(true);
}
function readPropertyValue(property) {
if (canBeRead(property)) {
if (property.getter)
return property.getter(getPropertyContext(property));
else
return store.getValue(property);
} else
return null;
}
function writePropertyValue(property, value) {
if (canBeWritten(property)) {
var changed = property.setter ?
property.setter(getPropertyContext(property), value) :
store.setValue(property, value);
if (changed === true)
markAsChanged(true);
}
}
function getPropertyContext(primaryProperty) {
if (!propertyContext)
propertyContext = new PropertyContext(properties.toArray(), readPropertyValue, writePropertyValue);
return propertyContext.with(primaryProperty);
}
properties.map(function(property) {
if (property.type instanceof DataType) {
// Normal property
store.initValue(property);
Object.defineProperty(self, property.name, {
get: function () {
return readPropertyValue(property);
},
set: function (value) {
if (property.isReadOnly)
throw new ModelError('readOnly', properties.name, property.name);
writePropertyValue(property, value);
},
enumerable: true
});
rules.add(new DataTypeRule(property));
} else {
// Child item/collection
if (property.type.create) // Item
store.initValue(property, property.type.create(self, eventHandlers));
else // Collection
store.initValue(property, new property.type(self, eventHandlers));
Object.defineProperty(self, property.name, {
get: function () {
return readPropertyValue(property);
},
set: function (value) {
throw new ModelError('readOnly', properties.name, property.name);
},
enumerable: false
});
}
});
//endregion
// Immutable object.
Object.freeze(this);
};
util.inherits(EditableChildModelSync, ModelBase);
/**
* The name of the model type.
*
* @property {string} EditableChildModelSync.constructor.modelType
* @default EditableChildModelSync
* @readonly
*/
Object.defineProperty(EditableChildModelSync, 'modelType', {
get: function () { return CLASS_NAME; }
});
/**
* The name of the model. However, it can be hidden by a model property with the same name.
*
* @name EditableChildModelSync#$modelName
* @type {string}
* @readonly
*/
EditableChildModelSync.prototype.$modelName = properties.name;
//region Factory methods
/**
* Creates a new editable business object instance.
* <br/>_This method is called by the parent object._
*
* @function EditableChildModelSync.create
* @protected
* @param {object} parent - The parent business object.
* @param {bo.shared.EventHandlerList} [eventHandlers] - The event handlers of the instance.
* @returns {EditableChildModelSync} A new editable business object.
*
* @throws {@link bo.rules.AuthorizationError Authorization error}:
* The user has no permission to execute the action.
* @throws {@link bo.shared.DataPortalError Data portal error}:
* Creating the business object has failed.
*/
EditableChildModelSync.create = function(parent, eventHandlers) {
var instance = new EditableChildModelSync(parent, eventHandlers);
instance.create();
return instance;
};
/**
* Initializes an editable business object width data retrieved from the repository.
* <br/>_This method is called by the parent object._
*
* @function EditableChildModelSync.load
* @protected
* @param {object} parent - The parent business object.
* @param {object} data - The data to load into the business object.
* @param {bo.shared.EventHandlerList} [eventHandlers] - The event handlers of the instance.
* @returns {EditableChildModelSync} The required editable business object.
*
* @throws {@link bo.rules.AuthorizationError Authorization error}:
* The user has no permission to execute the action.
*/
EditableChildModelSync.load = function(parent, data, eventHandlers) {
var instance = new EditableChildModelSync(parent, eventHandlers);
instance.fetch(data);
return instance;
};
//endregion
return EditableChildModelSync;
};
module.exports = EditableChildModelSyncFactory;