/*globals ObjectRow,DataRelation,define,self,jsDataSet,jsDataQuery,sqlFun */
/**
* @module MetaModel
* @description
* Knows how to manipulate DataSet accordingly to MetaData assumptions
*/
(function (jsDataSet,jsDataQuery,_) {
/** Detect free variable `global` from Node.js. */
let freeGlobal = typeof global === 'object' && global && global.Object === Object && global;
//const freeGlobal = freeExports && freeModule && typeof global === 'object' && global;
/** Detect free variable `self`. */
let freeSelf = typeof self === 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */
let root = freeGlobal || freeSelf || Function('return this')();
/** Detect free variable `exports`. */
let freeExports = typeof exports === 'object' && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
let freeModule = freeExports && typeof module === 'object' && module && !module.nodeType && module;
//noinspection JSUnresolvedVariable
/** Detect free variable `global` from Node.js or Browserified code and use it as `root`. (thanks lodash)*/
let moduleExports = freeModule && freeModule.exports === freeExports;
let dataRowState = jsDataSet.dataRowState;
let dataRowVersion = jsDataSet.dataRowVersion;
let q = jsDataQuery;
/**
* Sets of function to manage dataset properties
* @class
* @name MetaModel
*/
function MetaModel() {
}
MetaModel.prototype = {
constructor: MetaModel,
/**
* @method allowClear
* @public
* @description SYNC
* Gets/sets the clearAllowed property for a DataTable "t". It is used to check if a table can be cleared without loosing data
* @param {DataTable} t
* @param {boolean} [allow]
* @returns {boolean}
*/
allowClear: function (t, allow) {
if (allow !== undefined) {
t.denyClear("y");
if (allow) {
t.denyClear(null);
}
}
return (t.denyClear() !== "y");
},
/**
* @method allowAllClear
* @public
* @description SYNC
* Sets all "NotSubEntityChild" tables ofthe dataset "ds" as "CanClear". Called when form is cleared or data is posted
* @param {DataSet} ds
*/
allowAllClear: function (ds) {
const self = this;
_.forEach(ds.tables,
function (t) {
if (!self.notEntityChild(t)){
return;
}
self.allowClear(t, true);
self.notEntityChildFilter(t, null);
});
},
/**
* @method sorting
* @public
* @description SYNC
* Gets/sets the sorting of the DataTable "t"
* @param {DataTable} t
* @param {string} orderBy
* @returns {string}
*/
sorting: function (t, orderBy) {
if (orderBy !== undefined) {
t.orderBy(orderBy);
}
return t.orderBy();
},
/**
* @method notEntityChildFilter
* @public
* @description SYNC
* Gets/sets the "notEntityChild" property of the DataTable "t".
* @param {DataTable} t
* @param {jsDataQuery} [filter]
* @returns {jsDataQuery}
*/
notEntityChildFilter: function (t, filter) {
if (filter){
t.notEntityChild = filter;
}
return t.notEntityChild;
},
/**
* @method notEntityChild
* @public
* @description SYNC
* Gets the "notEntityChild" property of the DataTable "t".
* @param {DataTable} t
* @returns {jsDataQuery}
*/
notEntityChild: function (t) {
return MetaModel.prototype.notEntityChildFilter(t);
},
/**
* @method addNotEntityChild
* @public
* @description SYNC
* Sets the table as "notEntityChild". So the table isn't cleared during freshform and refills
* @param {DataTable} t
* @param {DataTable} child
*/
addNotEntityChild: function (t, child) {
MetaModel.prototype.allowClear(child, false);
MetaModel.prototype.addNotEntityChildFilter(t, child);
},
/**
* @method addNotEntityChildRel
* @public
* @description SYNC
* Sets the table as NotEntitychild. So the table isn't cleared during freshform and refills
* @param {DataTable} child
* @param {string} relName
*/
addNotEntityChildRel: function (child, relName) {
MetaModel.prototype.allowClear(child, false);
MetaModel.prototype.addNotEntityChildFilterRel(child, relName);
},
/**
* @method getName
* @public
* @description SYNC
* @param {DataTable} child
* @param {string} relName
*/
addNotEntityChildFilterRel: function (child, relName) {
if (!child) {
return;
}
if (!relName) {
return;
}
if (!child.dataset.relations[relName]) {
return;
}
let rel = child.dataset.relations[relName];
let filter = null;
const conditions = [];
_.forEach(rel.childCols, function (cName) {
if (!child.columns[cName].isPrimaryKey) {
conditions.push(q.isNull(q.field(cName)));
}
});
if (conditions.length === 1) {
filter = conditions[0];
} else if (conditions.length > 1) {
filter = q.and(conditions);
}
MetaModel.prototype.notEntityChildFilter(child, filter);
},
/**
* @method addNotEntityChildFilter
* @public
* @description SYNC
*
* @param {DataTable} t
* @param {DataTable} child
*/
addNotEntityChildFilter: function (t, child) {
if (MetaModel.prototype.notEntityChildFilter(child)) {
return;
}
const r = t.dataset.getParentChildRelation(t.name, child.name);
if (!r) {
return;
}
if (r.length === 0) {
return;
}
const rel = r[0];
let filter = null;
const conditions = [q.constant(true)];
_.forEach(rel.childCols, function (cName) {
// metodo alternativo jQuery if ($.inArray(cName, child.key()) !== -1 ){
if (!child.columns[cName].isPrimaryKey) {
conditions.push(q.isNull(q.field(cName)));
}
});
if (conditions.length === 1) {
filter = conditions[0];
} else if (conditions.length > 1) {
filter = q.and(conditions);
}
MetaModel.prototype.notEntityChildFilter(child, filter);
},
/**
*
* @method getName
* @public
* @description SYNC
* Removes a table from being a NotEntitychild
* @param {DataTable} table
*/
clearNotEntityChild: function (table) {
MetaModel.prototype.notEntityChildFilter(table, null);
},
/**
* @method temporaryTable
* @public
* @description SYNC
* Gets/sets temporary flag on a table
* @param {DataTable} t
* @param {boolean} [value]
* @returns {boolean}
*/
temporaryTable: function (t, value) {
if (value !== undefined) t.isTemporaryTable = !!value;
return t.isTemporaryTable;
},
/**
* @method getRowTemporaryValues
* @public
* @description SYNC
* Evaluates expressions for the given row
* @param {DataRow} r
*/
getRowTemporaryValues: function (r) {
const that = this;
let /*DataTable*/ t = r.table;
const ds = t.dataset;
_.forEach(t.columns,
/* {DataColumn} */ c=> {
let expr = that.columnExpression(c);
if (!expr) {
return;
}
if (_.isString(expr)) {
if (!ds) {
return;
}
const parts = expr.split(".");
if (parts.length !== 2) {
return;
}
const table = ds.tables[parts[0]];
if (!table) {
return;
}
expr = function (r) {
return that.getRelatedRowColumn(r, table.name, parts[1]);
};
}
if (_.isFunction(expr)) {
const dRow = r.getRow();
if (dRow.state === dataRowState.deleted) {
return;
}
const toMark = (dRow.state === dataRowState.unchanged);
r[c.name] = expr(r);
if (toMark) {
dRow.acceptChanges();
}
}
});
MetaModel.prototype.calculateTable(t);
},
/**
* @method getTemporaryValues
* @public
* @description SYNC
* Evaluates expressions for each DataTable "t" rows
* @param {DataTable} t
*/
getTemporaryValues: function (t) {
const that = this;
const ds = t.dataset;
_.forEach(t.columns,
/* {DataColumn} */ c=> {
let expr = that.columnExpression(c);
if (!expr) {
return;
}
if (_.isString(expr)) {
if (!ds) {
return;
}
const parts = expr.split(".");
if (parts.length !== 2) {
return;
}
const table = ds.tables[parts[0]];
if (!table) {
return;
}
expr = function (r) {
return that.getRelatedRowColumn(r, table.name, parts[1]);
};
}
if (_.isFunction(expr)) {
_.forEach(t.rows,
function (r) {
const dRow = r.getRow();
if (dRow.state === dataRowState.deleted) {
return;
}
const toMark = (dRow.state === dataRowState.unchanged);
r[c.name] = expr(r);
if (toMark) {
dRow.acceptChanges();
}
});
}
});
MetaModel.prototype.calculateTable(t);
},
/**
* @method getRelatedRowColumn
* @public
* @description SYNC
* Returns a field of a row related to row "r" in the table relatedTableName
* @param {ObjectRow} r base row
* @param {string} relatedTableName table where the related row is to be searched
* @param {string} relatedColumn column containing the value
* @returns {object}
*/
getRelatedRowColumn: function (r, relatedTableName, relatedColumn) {
const dr = r.getRow();
let related = dr.getChildInTable(relatedTableName);
if (related.length !== 0) {
return related[0][relatedColumn];
}
related = dr.getParentsInTable(relatedTableName);
if (related.length !== 0) {
return related[0][relatedColumn];
}
return null;
},
/**
* @method calculateTable
* @public
* @description SYNC
* Evaluates custom fields for every row of a DataTable "t". Calls the delegate linked to the table,
* corresponding to the MetaData.CalculateFields() virtual method (if it has been defined).
* @param {DataTable} t
*/
calculateTable: function (t) {
if (!t) {
return;
}
if (!t.calculatingListing) {
return;
}
const listType = t.calculatingListing;
const calc = t.calculateFunction;
if (!_.isFunction(calc)) {
return;
}
_.forEach(t.rows,
function (r) {
const dRow = r.getRow();
if (dRow.state === dataRowState.deleted) {
return;
}
const toMark = (dRow.state === dataRowState.unchanged);
calc(r, listType);
if (toMark) {
dRow.acceptChanges();
}
});
},
/**
* @method calculateRow
* @public
* @description SYNC
* Evaluates custom fields for a single row "r". Calls the delegate linked to the table,
* corresponding to the MetaData.CalculateFields() virtual method (if it has been defined).
* @param {ObjectRow} r
*/
calculateRow: function (r) {
if (!r) {
return;
}
const t = r.getRow().table;
if (!t.calculatingListing) {
return;
}
const listType = t.calculatingListing;
const calc = t.calculateFunction;
if (!_.isFunction(calc)) {
return;
}
const dRow = r.getRow();
if (dRow.state === dataRowState.deleted) {
return;
}
const toMark = (dRow.state === dataRowState.unchanged);
calc(r, listType);
if (toMark) {
dRow.acceptChanges();
}
},
/**
* @method computeRowsAs
* @public
* @description SYNC
* Tells MetaData Engine to call CalculateFields(R,ListingType) whenever:
* - a row is loaded from DataBase
* - a row is changed in a sub-entity form and modification accepted with mainsave
* @param {DataTable} t
* @param {string} listType
* @param {function} calcFunction
*/
computeRowsAs: function (t, listType, calcFunction) {
t.calculatingListing = listType;
t.calculateFunction = calcFunction;
},
/**
* @method realTable
* @public
* @description SYNC
* Checks if a table is a real table (not temporary)
* @param {DataTable} t
* @returns {boolean}
*/
isRealTable: function (t) {
return !MetaModel.prototype.temporaryTable(t);
},
/**
* @method columnExpression
* @public
* @description SYNC
* Gets/Sets an expression associated to a DataColumn "c"
* @method columnExpression
* @param {DataColumn} c
* @param {string|jsDataQuery} value
* @returns {string|jsDataQuery|*}
*/
columnExpression: function (c, value) {
if (value === undefined) {
return c.expression;
}
c.expression = value;
return c;
},
/**
* @method temporaryColumn
* @public
* @description SYNC
* Returns true if the DataColumn "c" is a temporay Column, flase otherwise
* @method temporaryColumn
* @param {DataColumn} c
* @returns {boolean}
*/
temporaryColumn: function (c) {
if (c.name.startsWith("!")) {
return true;
}
if (c.expression) {
return true;
} // expression viene letto dal server, che inserisce o stringa o ME cioè jsDataQuery
return false;
},
/**
* @method clearEntity
* @public
* @description SYNC
* Clears all tables of dataset "d" except for temporary and cached (including pre-filled combobox).
* Also undoes the effect of denyclear on all secondary tables setting tables with AllowClear()
* @param {DataSet} d
* @returns {}
*/
clearEntity: function (d) {
const self = this;
_.forEach(d.tables,
/**
* clears a table if it is an entity
* @param {DataTable} t
* @returns {}
*/
function (t) {
if (self.temporaryTable(t) || self.cachedTable(t)) {
return true;
}
if (!self.visitedFullyTable(t)) {
t.clear();
}
self.allowClear(t, true);
return true;
});
},
/**
* @method lockRead
* @public
* @description SYNC
* Set cached flag on a DataTable "t"
* @param {DataTable} t
*/
lockRead: function (t) {
t.isCached = "1";
},
/**
* @method canRead
* @public
* @description SYNC
* Tells if a table should be cleared and read again during a refresh.
* Cached tables are not read again during refresh if they have been already been read
* @param {DataTable} t
* @returns {boolean}
*/
canRead: function (t) {
if (t.isCached === null || t.isCached === undefined) {
return true;
}
return t.isCached === "0";
},
/**
* @method reCache
* @public
* @description SYNC
* If a table "t" is cached, is marked to be read again in next
* ReadCached. If the table is not cached, has no effect
* @param {DataTable} t
*/
reCache: function (t) {
if (!MetaModel.prototype.cachedTable(t)) {
return;
}
MetaModel.prototype.cachedTable(t, true);
},
/**
* @method setAsRead
* @public
* @description SYNC
* Set a table as "read". It has no effect if table isn't a chached table
* @param {DataTable} t
*/
setAsRead: function (t) {
if (MetaModel.prototype.cachedTable(t)) {
MetaModel.prototype.lockRead(t);
}
},
/**
* @method cachedTable
* @public
* @description SYNC
* Gets/sets cached flag on a table "t"
* @param {DataTable} t
* @param {boolean} [value]
* @returns {*}
*/
cachedTable: function (t, value) {
if (value !== undefined) {
if (value) {
if (t.isCached !== "1") {
t.isCached = "0";
}
} else {
t.isCached = undefined;
}
return t.isCached;
}
return t.isCached !== undefined && t.isCached !== null;
},
/**
* @method insertFilter
* @public
* @description SYNC
* Gets/sets insert filter "value" on a table "t"
* @param {DataTable} t
* @param {jsDataQuery} [value]
* @returns {jsDataQuery}
*/
insertFilter: function (t, value) {
if (value !== undefined) {
t.insertFilter = value;
}
return t.insertFilter;
},
/**
* @method searchFilter
* @public
* @description SYNC
* Gets/Sets a search filter "value" on a table "t"
* @param {DataTable} t
* @param {jsDataQuery} [value]
* @returns {*}
*/
searchFilter: function (t, value) {
if (value !== undefined) {
t.searchFilter = value;
}
return t.searchFilter;
},
/**
* @method getgetMaxLenName
* @public
* @description SYNC
* Given col type returns the length of the field
* @param {DataColumn} col
* @returns {*}
*/
getMaxLen: function (col) {
if (!col) return 32767;
if (col.ctype === "Decimal") return 20;
if (col.ctype === "Float") return 20;
if (col.ctype === "Double") return 20;
if (col.ctype === "Int32") return 10;
if (col.ctype === "Int16") return 5;
if (!col.maxstringlen) return 2147483647;
return col.maxstringlen;
},
/**
* @method denyNull
* @public
* @description SYNC
* Gets/sets denyNull property on DataColumn "c" property
* @param {DataColumn} c
* @param {boolean} [value]
* @returns {boolean}
*/
denyNull: function (c, value) {
if (value === undefined) {
return !MetaModel.prototype.allowDbNull(c);
}
return !MetaModel.prototype.allowDbNull(c,!value);
},
/**
* @method denyZero
* @public
* @description SYNC
* Gets/sets denyZero property on DataColumn "c" property
* @param {DataColumn} c
* @param {boolean} [value]
* @returns {boolean}
*/
denyZero: function (c, value) {
if (value === undefined) {
return !MetaModel.prototype.allowZero(c);
}
return !MetaModel.prototype.allowZero(c,!value);
},
/**
* @method allowDbNull
* @public
* @description SYNC
* Gets/sets "allowDbNull" property on DataColumn "c" property (False if data is not nullable in the database)
* @param {DataColumn} c
* @param {boolean} [value]
* @returns {boolean}
*/
allowDbNull: function (c, value) {
if (value === undefined) {
if (c.allowDbNull === undefined) {
return true;
}
return c.allowDbNull;
}
c.allowDbNull = value;
return this;
},
/**
* @method allowZero
* @public
* @description SYNC
* Gets/sets "allowZero" property on DataColumn "c" property (False if data not permit zero in the database)
* @param {DataColumn} c
* @param {boolean} [value]
* @returns {boolean}
*/
allowZero: function (c, value) {
if (value === undefined) {
if (c.allowZero === undefined) {
return true;
}
return c.allowZero;
}
c.allowZero = value;
return this;
},
/**
* @method isColumnNumeric
* @public
* @description SYNC
* Returns true if the column is numeric, false otherwise
* @param {DataColumn} c
* @returns {boolean}
*/
isColumnNumeric: function (c) {
if (!c) {
return false;
}
const ctype = c.ctype;
if (!ctype) {
return false;
}
const CType = jsDataSet.CType;
return (ctype === CType.number ||
ctype === CType.int);
},
/**
* @method visitedFullyTable
* @public
* @description SYNC
* Gets/Sets cached flag on a table "t"
* @method visitedFullyTable
* @param {DataTable} t
* @param {bool} [value]
* @returns {boolean}
*/
visitedFully: function (t, value) {
if (value !== undefined) {
t.isVisitedFully = !!value;
}
if (!t.isVisitedFully) return false;
return true;
},
/**
* @method isSubEntity
* @public
* @description SYNC
* Returns true if "childTable" is a subentity table of "parentTable", false otherwise
* A table is subentity if it is child and all columns of primary table must be connected to a child key field
* @param {DataTable} childTable
* @param {DataTable} parentTable
* @returns {boolean}
*/
isSubEntity: function (childTable, parentTable) {
const that = this;
return _.some(parentTable.childRelations(),
function (rel) {
if (rel.childTable !== childTable.name) return false;
return that.isSubEntityRelation(rel, childTable, parentTable);
});
},
/**
* @method isParentTableByKey
* @public
* @description SYNC
* Checks if parent table "parentTable" is related with KEY fields of Child table "childTable"
* @param {DataSet} ds
* @param {DataTable} parentTable
* @param {DataTable} childTable
* @returns {boolean}
*/
isParentTableByKey: function (ds, parentTable, childTable) {
const rel = ds.getParentChildRelation(parentTable.name, childTable.name);
if (rel.length === 0) return false;
if (rel[0].childCols.length === 0) return false;
let kFound = true;
_.forEach(rel[0].childCols,
function (col) {
if (!childTable.isKey(col)) {
kFound = false;
return false;
}
return true;
});
return kFound;
},
/**
* @method hasChanges
* @public
* @description SYNC
* Returns true if dataset has changes, false otherwise.
* @param {DataSet} ds
* @param {DataTable} primary
* @param {DataRow} sourceRow >> Row in master DataSet
* @param {boolean} detailPage
* @returns {boolean}
*/
hasChanges: function (ds, primary, sourceRow, detailPage) {
MetaModel.prototype.removeFalseUpdates(ds);
if (!detailPage) return ds.hasChanges();
if (primary.rows.length === 0) return false;
if (!sourceRow) return false;
// Per una subentità (detail form) confronta i dati con quelli dell'origine
const masterDataSet = sourceRow.table.dataset;
const myRow = primary.rows[0];
if (MetaModel.prototype.xVerifyChangeChilds(masterDataSet, sourceRow.table, ds, myRow)) return true;
return MetaModel.prototype.xVerifyChangeChilds(ds, primary, masterDataSet, sourceRow.current);
},
/**
* @method removeFalseUpdates
* @private
* @description SYNC
* Removes false updates from a DataSet, i.e. calls AcceptChanges for any DataRow erroneously marked as modified
* @param {DataSet} ds
*/
removeFalseUpdates: function (ds) {
const that = this;
_.forEach(ds.tables,
function (t) {
if (that.temporaryTable(t)) return true; //doesn't make anything on temporary tables
_.forEach(t.rows,
function (r) {
const dRow = r.getRow();
if (dRow.state !== dataRowState.modified) return true;
if (that.checkForFalseUpdates(dRow)) {
dRow.acceptChanges();
}
return true;
});
return true;
});
},
/**
* @method checkForFalseUpdates
* @public
* @description SYNC
* Returns true if row (modified) is not really a modified row
* @param {DataRow} dRow
* @returns {boolean}
*/
checkForFalseUpdates: function (dRow) {
if (dRow.state !== dataRowState.modified) return false;
let hasRealUpdates = false;
const that = this;
_.forEach(dRow.table.columns,
function (c) {
if (that.temporaryColumn(c)) return true;
if (dRow.getValue(c.name, dataRowVersion.current) !== dRow.getValue(c.name, dataRowVersion.original)) {
if (!(dRow.getValue(c.name, dataRowVersion.original) === undefined && dRow.getValue(c.name, dataRowVersion.current) === null)) {
hasRealUpdates = true;
return false;
}
}
return true;
});
return !hasRealUpdates;
},
/**
* @method xVerifyChangeChilds
* @public
* @description SYNC
* Checks if rSource and all childs have not changed comparing them with Dest content
* Returns true if there are changes
* @param {DataSet} dest
* @param {DataTable} tDest
* @param {DataSet} rif
* @param {ObjectRow} rSource
* @returns {boolean}
*/
xVerifyChangeChilds: function (dest, tDest, rif, rSource) {
if (MetaModel.prototype.xVerifyRowChange(dest, tDest, rif, rSource)) return true;
const drSource = rSource.getRow();
const t = drSource.table;
const that = this;
const changeNotSubentity = _.some(
_.filter(dest.tables, function (t) {
return that.notEntityChildFilter(t) && t.hasChanges();
}));
if (changeNotSubentity) return true;
return _.some(t.childRelations(),
function (rel) { //rel collega la parentTable ove c'è rSource con una childTable con le figlie
if (!dest.tables[rel.childTable]) return false; //la tabella child non è presente nel dataset di origine
if (!that.isEntityChildRelation(rel)) return false; //la relazione non è di tipo "subentity"
return _.some(rel.getChild(rSource),
function (rChild) {
return that.xVerifyChangeChilds(dest, dest.tables[rel.childTable], rif, rChild);
});
});
},
/**
* @method isSubEntityRelation
* @public
* @description SYNC
* Returns true if the relation "rel" is a db relation between "childTable" and "parentTable"
* and all columns of primary table must be connected to a child key field
* @param {DataRelation} rel
* @param {DataTable} childTable
* @param {DataTable} parentTable
* @returns {boolean}
*/
isSubEntityRelation: function (rel, childTable, parentTable) {
// verifica della condizione di subentity forzata dalla metapage
if (MetaModel.prototype.notEntityChildFilter(rel.dataset.tables[rel.childTable])) return true;
if (rel.parentTable !== parentTable.name) return false;
if (rel.childTable !== childTable.name) return false;
// relation must connect all key fields of parentTable
if (_.some(rel.parentCols,
function (cName) {
return !(parentTable.isKey(cName));
})) return false;
if (rel.parentCols.length !== parentTable.key().length) return false;
// Check that ALL columns of relation must be connected to a child key field
return !_.some(rel.childCols,
function (cName) {
return !(childTable.isKey(cName));
});
},
/**
* @method isEntityChildRelation
* @public
* @description SYNC
* Checks if a relation connects any field that is primarykey for both parent and child
* @param {DataRelation} r
* @returns {boolean}
*/
isEntityChildRelation: function (r) {
// Autorelation are not children
if (r.parentTable === r.childTable) return false;
const parentTable = r.dataset.tables[r.parentTable];
const childTable = r.dataset.tables[r.childTable];
return _.some(_.map(
_.zip(r.parentCols, r.childCols),
_.curry(_.zipObject)(['parentCol', 'childCol'])
), function (pair) {
return (parentTable.isKey(pair.parentCol) && childTable.isKey(pair.childCol));
});
},
/**
* @method xVerifyRowChange
* @public
* @description SYNC
* Verifies if a row is not changed between parent and child dataset
* Return true if there are changes
* @param {DataSet} dest
* @param {DataTable} tDest
* @param {DataSet} source
* @param {ObjectRow} rSource
* * @returns {boolean}
*/
xVerifyRowChange: function (dest, tDest, source, rSource) {
const drSource = rSource.getRow();
if (drSource.state === dataRowState.deleted) return false; //shouldn't happen
const tSource = drSource.table;
const found = tDest.select(tSource.keyFilter(rSource));
if (found.length === 0) return true;
const rFound = found[0];
const that = this;
return _.some(tSource.columns,
function (c) {
if (that.temporaryColumn(c)) return false;
if (!tDest.columns[c.name]) return false;
const colDest = tDest.columns[c.name];
if (that.temporaryColumn(colDest)) return false;
return rFound[c.name] !== rSource[c.name];
});
},
/**
* @method xCopy
* @public
* @description SYNC
* Copies a DataRow from dsSource to dsDest.
* rSource and rDest must have same key, or rSource have to not generate conflicts in dsDest
* @param {DataSet} dsSource
* @param {DataSet} dsDest
* @param {DataRow} rSource
* @param {DataRow} rDest
* @returns {DataRow}
*/
xCopy: function (dsSource, dsDest, rSource, rDest) {
const destIsInsert = (rDest.state === dataRowState.added);
const destTableName = rDest.table.name;
MetaModel.prototype.xRemoveChilds(dsSource, rDest);
return MetaModel.prototype.xMoveChilds(dsDest, dsDest.tables[destTableName], dsSource, rSource, destIsInsert);
},
/**
* @method xCopyChilds
* @public
* @description SYNC
* Copies a DataRow and all its childs from "dsSource" to "dsDest"
* @param {DataSet} dsDest
* @param {DataSet} dsSource
* @param {DataRow} rowSource
*/
xCopyChilds: function (dsDest, dsSource, rowSource) {
const t = rowSource.table;
let source_unaliased = t.tableForReading();
if (!dsDest.tables[source_unaliased]) source_unaliased = rowSource.table.name;
MetaModel.prototype.copyDataRow(dsDest.tables[source_unaliased], rowSource);
MetaModel.prototype.allowClear(dsDest.tables[source_unaliased], false);
const self = this;
_.forEach(t.childRelations(), function (rel) {
if (dsDest.tables[rel.childTable]) {
if (self.checkChildRel(rel)) {
self.allowClear(dsDest.tables[rel.childTable], false);
const childs = rowSource.getChildRows(rel.name);
_.forEach(childs, function (child) {
self.xCopyChilds(dsDest, dsSource, child.getRow());
});
}
}
});
},
/**
* @method copyDataRow
* @private
* @description SYNC
* Copies "toCopy" row to "destTables" DataTable
* @param {DataTable} destTable
* @param {DataRow} toCopy
*/
copyDataRow: function (destTable, toCopy) {
const destRow = destTable.newRow();
let isCurrentToConsider = true;
if (toCopy.state === dataRowState.deleted || toCopy.state === dataRowState.modified) {
isCurrentToConsider = false;
}
if (toCopy.state !== dataRowState.added) {
_.forEach(destTable.columns, function (col) {
if (toCopy.table.columns[col.name]) {
if (isCurrentToConsider) {
destRow[col.name] = toCopy.getValue(col.name, dataRowVersion.current);
} else {
destRow[col.name] = toCopy.getValue(col.name, dataRowVersion.original);
}
}
});
destRow.getRow().acceptChanges();
}
if (toCopy.state === dataRowState.deleted) {
destRow.getRow().del();
return;
}
// sopra ho fatto acceptChanges. solo per le diverse da added rimetto il currnet
// così lo stato viene preservato tra tocopy e copiato
if (toCopy.state !== dataRowState.unchanged) {
_.forEach(destTable.columns, function (col) {
if (toCopy.table.columns[col.name]) {
destRow[col.name] = toCopy.getValue(col.name, dataRowVersion.current);
}
});
}
if ((toCopy.state === dataRowState.modified || toCopy.state === dataRowState.unchanged)) {
MetaModel.prototype.calculateRow(destRow);
if (MetaModel.prototype.checkForFalseUpdates(destRow)){
destRow.acceptChanges();
}
return;
}
MetaModel.prototype.calculateRow(destRow);
},
/**
* @method getName
* @public
* @description SYNC
* Moves a DataRow and all its childs from "dsRif" to "dsDest".
* @param {DataSet} dsDest
* @param {DataTable} tDest
* @param {DataSet} dsRif
* @param {DataRow} rSource
* @param {boolean} forceAddState
* @returns {DataRow}
*/
xMoveChilds: function (dsDest, tDest, dsRif, rSource, forceAddState) {
const t = rSource.table;
const resultRow = MetaModel.prototype.moveDataRow(tDest, rSource, forceAddState);
const self = this;
_.forEach(t.childRelations(), function (rel) {
if (dsDest.tables[rel.childTable]) {
if (self.checkChildRel(rel)) {
const childDataTable = dsRif.tables[rel.childTable];
// copia gli autoincrements
self.copyAutoincrementsProperties(childDataTable, dsDest.tables[rel.childTable]);
while (childDataTable.rows.length > 0) {
const child = childDataTable.rows[0];
self.xMoveChilds(dsDest, dsDest.tables[rel.childTable], dsRif, child.getRow(), false);
}
}
}
});
if (rSource.state !== dataRowState.deleted) {
rSource.del();
}
if (rSource.state !== dataRowState.detached) {
rSource.acceptChanges();
}
return resultRow;
},
/**
* @method moveDataRow
* @public
* @description SYNC
* Moves "toCopy" row to "destTable" DataTable
* @param {DataTable} destTable
* @param {DataRow} toCopy
* @param {boolean} forceAddState
* @returns {DataRow}
*/
moveDataRow: function (destTable, toCopy, forceAddState) {
const dest = destTable.newRow();
let isCurrentToConsider = true;
if (toCopy.state === dataRowState.deleted || toCopy.state === dataRowState.modified) {
isCurrentToConsider = false;
}
if (toCopy.state !== dataRowState.added && !forceAddState) {
_.forEach(destTable.columns, function (col) {
if (toCopy.table.columns[col.name]) {
if (isCurrentToConsider) {
dest[col.name] = toCopy.getValue(col.name, dataRowVersion.current);
} else {
dest[col.name] = toCopy.getValue(col.name, dataRowVersion.original);
}
}
});
// destTable.add(dest);
dest.getRow().acceptChanges();
}
if (toCopy.state === dataRowState.deleted) {
dest.getRow().del();
return dest;
}
_.forEach(destTable.columns, function (col) {
if (toCopy.table.columns[col.name]) {
dest[col.name] = toCopy.getValue(col.name, dataRowVersion.current);
}
});
if ((toCopy.state === dataRowState.modified || toCopy.state === dataRowState.unchanged) && !forceAddState) {
MetaModel.prototype.calculateRow(dest);
if (MetaModel.prototype.checkForFalseUpdates(dest.getRow())) dest.getRow().acceptChanges();
return dest;
}
// Vedo se nella tab. di dest. c'è una riga cancellata che matcha
const filter = MetaModel.prototype.getWhereKeyClause(toCopy, toCopy.table, toCopy.table, false);
const deletedFound = _.filter(destTable.select(filter), function (r) {
return r.getRow().state === dataRowState.deleted;
});
if (deletedFound) {
if (deletedFound.length === 1) {
_.forEach(destTable.columns, function (col) {
dest[col.name] = deletedFound[0].getValue(col.name, dataRowVersion.original);
});
// Elimina la riga cancellata dal DataSet
deletedFound[0].getRow().acceptChanges();
// Considera la riga sorgente non più cancellata ma invariata
dest.getRow().acceptChanges();
_.forEach(destTable.columns, function (col) {
if (toCopy.table.columns[col.name]) {
dest[col.name] = toCopy.getValue(col.name, dataRowVersion.current);
}
});
MetaModel.prototype.calculateRow(dest);
if (MetaModel.prototype.checkForFalseUpdates(dest)) dest.getRow().acceptChanges();
return dest;
}
}
MetaModel.prototype.calculateRow(dest);
return dest;
},
/**
* @method getWhereKeyClauseByColumns
* @public
* @description SYNC
* Builds a DataQuery clause where the keys are the columns in "filterColTable" and the values are those in "valueRow" DataRow
* @param {DataRow} valueRow Row to use for getting values to compare
* @param {DataColumn[]} valueCol Columns of ParentRow from which values to be compare have to be taken
* @param {DataColumn[]} filterCol Columns of ChildRows for which the Column NAMES have to be taken
* @param {DataTable} filterColTable table linked to the filtercolcolumns
* @param {bool} posting use posting column names where set
* @returns {sqlFun}
*/
getWhereKeyClauseByColumns: function (valueRow, valueCol, filterCol, filterColTable, posting) {
let conditions = [];
_.forEach(valueCol, function (c, index) {
let val = valueRow.current[c.name] ? valueRow.current[c.name] : null;
let filterColumn = filterCol[index];
let fieldName = filterColumn.name;
if (posting) {
fieldName = MetaModel.prototype.postingColumnName(filterColumn, filterColTable);
}
if (val === null || val === undefined) {
conditions.push(q.isNull(q.field(fieldName)));
} else {
conditions.push(q.eq(q.field(fieldName), val));
}
});
if (conditions.length === 0) {
return undefined;
}
if (conditions.length === 1) {
return conditions[0];
}
return q.and(conditions);
},
/**
* @method getWhereKeyClause
* @public
* @description SYNC
* Builds a DataQuery clause where the keys are the columns in "filterColTable" and the values are those in "valueRow" DataRow
* @param {DataRow} valueRow Row to use for getting values to compare
* @param {DataTable} valueColTable Row Columns of ParentRow from which values to be compare have to be taken
* @param {DataTable} filterColTable Row Column of ChildRows for which the Column NAMES have to be taken
* @param {bool} posting use posting column names where set
* @returns {jsDataQuery}
*/
getWhereKeyClause: function (valueRow, valueColTable, filterColTable, posting) {
let valueCol = _.map(valueColTable.key(),
function (cName) {
return valueColTable.columns[cName];
});
let filterCol = _.map(filterColTable.key(),
function (cName) {
return filterColTable.columns[cName];
});
return MetaModel.prototype.getWhereKeyClauseByColumns(valueRow, valueCol, filterCol, filterColTable, posting);
},
/**
* @method xRemoveChilds
* @public
* @description SYNC
* Removes a row "rDest" with all his subentity childs. Only considers tables of D inters. Rif
* @param {DataSet} dsRif
* @param {DataRow} rDest
*/
xRemoveChilds: function (dsRif, rDest) {
const t = rDest.table;
const self = this;
_.forEach(t.childRelations(), function (rel) {
if (dsRif.tables[rel.childTable]) {
if (self.checkChildRel(rel)) {
const childs = rDest.getChildRows(rel.name);
_.forEach(childs, function (child) {
self.xRemoveChilds(dsRif, child.getRow());
});
}
}
});
rDest.del();
if (rDest.state !== dataRowState.detached) {
rDest.acceptChanges();
}
},
/**
* @method checkChildRel
* @private
* @description SYNC
* Checks if a relation connects any field that is primarykey for both parent and child
* @param {DataRelation} rel
* @returns {boolean}
*/
checkChildRel: function (rel) {
if (MetaModel.prototype.notEntityChildFilter(rel.dataset.tables[rel.childTable])) {
return true;
}
// Autorelation are not childRel
if (rel.parentTable === rel.childTable) {
return false;
}
let linkparentkey = false;
_.forEach(rel.parentCols, function (parentCol, index) {
const childCol = rel.childCols[index];
if (rel.dataset.tables[rel.parentTable].columns[parentCol].isPrimaryKey &&
rel.dataset.tables[rel.childTable].columns[childCol].isPrimaryKey) {
linkparentkey = true;
}
});
return linkparentkey;
},
/**
* @method postingColumnName
* @private
* @description SYNC
* Gets the Column name to use for posting a given field into DB
* @param {DataColumn} col
* @param {DataTable} table DatTable attached to the column
* @returns {string|null}
*/
postingColumnName: function (col, table) {
if (table.tableForWriting() === undefined || col.forPosting === undefined) return col.name;
if (col.forPosting === "") {
return null;
}
return col.forPosting;
},
/**
* @method copyAutoincrementsProperties
* @public
* @description SYNC
* Copies the autoincrement properties form DataTable "dtIn" to DataTable "dtOut"
* @param {DataTable} dtIn
* @param {DataTable} dtOut
*/
copyAutoincrementsProperties: function (dtIn, dtOut) {
// faccio un semplice clone
dtOut.autoIncrementColumns = _.cloneDeep(dtIn.autoIncrementColumns);
},
/**
* @method cmpSelectors
* @public
* @description SYNC
* For each AutoIncrement obj of table t, compares the values of the two rows.
* Returns false if a value is different, returns true if all values on selector columns are equal
* @param {DataTable} t
* @param {DataRow} row1
* @param {DataRow} row2
* @returns {boolean} true if all values for row1 and row2 in all selector columns are equal, false otherwise
*/
cmpSelectors: function (t, row1, row2) {
// valore partenza true, setto a false appena trovo un valore diverso
let cmpRes = true;
// 1o ciclo su ogni AutoIncrementColumn
_.forEach(t.autoIncrementColumns, function (aic) {
// se già è false esco dal ciclo
if (!cmpRes) {
return false;
}
// 2. ciclo sui selettori
_.forEach(aic.selector, function (sel) {
// recupero per quella colonna i valori su row1 e row2
const v1 = row1.current[sel];
const v2 = row2.current[sel];
if (v1 instanceof Date) {
if (v1.valueOf() !== v2.valueOf()) {
cmpRes = false; // valore diverso, metto booleano a false ed esco dal ciclo
return false;
}
} else if (v1 !== v2) {
cmpRes = false; // valore diverso, metto booleano a false ed esco dal ciclo
return false;
}
}); // chiude for interno
}); // chiude for esterno
return cmpRes;
},
/**
* @method calcTemporaryID
* @public
* @description SYNC
* Evaluates a temporary value for a field of a row, basing on AutoIncrement
* properties of the column, without reading from DB.
* @param {DataTable} table
* @param {DataRow} row
*/
calcTemporaryID: function (table, row) {
_.forEach(table.columns, function (dc) {
if (table.autoIncrement(dc.name)) {
table.calcTemporaryId(row.current, dc.name);
}
});
},
/**
* @method applyCascadeDelete
* @public
* @description SYNC
* Does the cascade delete of the row "rowToDelete"
* @param {ObjectRow} rowToDelete
*/
applyCascadeDelete: function ( rowToDelete) {
MetaModel.prototype.cascadeDelete(rowToDelete);
},
/**
* Deletes a row with all subentity child
* @method cascadeDelete
* @param {ObjectRow} row
* @return {*}
*/
cascadeDelete: function (row) {
const r = row.getRow(),
table = r.table,
that = this;
_.forEach(table.dataset.relationsByParent[table.name], function (rel) {
if (that.checkChildRel(rel)) {
_.forEach(rel.getChild(row), function (toDel) {
if (toDel.getRow().state !== dataRowState.deleted) {
that.cascadeDelete(toDel);
}
});
} else {
_.forEach(rel.getChild(row), function (toUnlink) {
rel.makeChild(null, toUnlink);
}
);
}
});
r.del();
},
/**
* @method copyPrimaryKey
* @private
* @description SYNC
* Set the primary key of Dest conformingly to table Source
* @param {DataTable} dest
* @param {DataTable} source
*/
copyPrimaryKey: function (dest, source) {
// se già contiene una chiave esco
const destKeys = dest.key();
if (destKeys && destKeys.length>0) {
return;
}
const keys = _.filter(source.key(), function (cName) {
return !!dest.columns[cName];
});
if (keys.length > 0) {
dest.key(keys);
}
},
/**
* @method columnNameList
* @public
* @description ASYNC
* Returns the list of real (not temporary or expression) columns NAMES of a table "table"
* formatting it like "fieldname1, fieldname2,...."
* Returns "*" if no column is set
* @param {DataTable} table
* @returns {string}
*/
columnNameList: function (table) {
const self = this;
const cols = _.map(_.filter(table.columns,
function (c) {
return !self.temporaryColumn(c);
}),
function (dc) {
return dc.name;
});
if (cols.length > 0) return cols.join(",");
return '*';
},
/**
* Sets a field to DBNull (or -1(int) or 0-like values when DBNull is not allowed)
* @param {DataColumn} col The col to check to return the clear value
* @returns {Object}
*/
clearValue: function (col) {
if (MetaModel.prototype.allowDbNull(col)) {
return null;
}
const typename = col.ctype;
switch (typename) {
case "String":
case "Char":
return "";
case "Single":
case "Double":
case "Int16":
case "Int32":
case "Byte":
case "Decimal":
return 0;
case "DateTime":
return new Date("1000-01-01");
default:
return "";
}
}
};
let metaModel = new MetaModel();
// Some AMD build optimizers like r.js check for condition patterns like the following:
//noinspection JSUnresolvedVariable
if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
// Expose lodash to the global object when an AMD loader is present to avoid
// errors in cases where lodash is loaded by a script tag and not intended
// as an AMD module. See http://requirejs.org/docs/errors.html#mismatch for
// more details.
root.metaModel = metaModel;
// Define as an anonymous module so, through path mapping, it can be
// referenced as the "underscore" module.
//noinspection JSUnresolvedFunction
define(function () {
return metaModel;
});
}
// Check for `exports` after `define` in case a build optimizer adds an `exports` object.
else if (freeExports && freeModule) {
// Export for Node.js or RingoJS.
if (moduleExports) {
(freeModule.exports = metaModel).metaModel = metaModel;
}
// Export for Narwhal or Rhino -require.
else {
freeExports.metaModel = metaModel;
}
}
else {
// Export for a browser or Rhino.
if (root.appMeta){
root.appMeta.metaModel = metaModel;
}
else {
root.metaModel=metaModel;
}
}
//window.appMeta.metaModel = new MetaModel();
}.call(this,
(typeof jsDataSet === 'undefined') ? require('./jsDataSet') : jsDataSet,
(typeof jsDataQuery === 'undefined') ? require('./jsDataQuery') : jsDataQuery,
(typeof _ === 'undefined') ? require('lodash') : _,
) );