/*globals ObjectRow,DataRelation,define,self,jsDataSet,sqlFun,metaModel,appMeta,_ */
/**
* @module MetaData
* @description
* Contains all the information for a MetaData
*/
(function(_,metaModel,localResource,Deferred,
getDataUtils,logger,logType,getMeta,getDataExt,/*{CType}*/ CType,securityExt) {
/** Detect free variable `global` from Node.js. */
let freeGlobal = typeof global === 'object' && global && global.Object === 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;
/**
* @constructor AutoInfo
* @description Information abount an AutoManage or AutoChoose div
* @param {element} G usually DIV or SPAN
* @param {string} type
* @param {jsDataQuery} startFilter
* @param {string} startField
* @param {string} table
* @param {string} kind
*/
function AutoInfo( G,
type,
startFilter,
startField,
table,
kind) {
this.G = G;
this.type = type;
this.startfield = startField;
this.startFilter = startFilter;
this.table = table;
this.kind = kind;
}
/**
* @constructor MetaData
* @description Information about a metadata
* @param {string} tableName
*/
function MetaData(tableName) {
this.tableName = tableName;
this.metaPage= null;
this.listTop = 0;
this.localResource = localResource;
this.getData = getDataExt;
this.security = securityExt;
return this;
}
MetaData.prototype = {
constructor: MetaData,
/**
* Preserve method code. Used server side.
* @param {Request} req
*/
setRequest: function (req){
if (!req) return;
const ctx = req.app.locals.context;
this.localResource = ctx.localResource;
this.getData = ctx.getDataInvoke;
this.security = ctx.environment;
this.getMeta = ctx.getMeta;
},
/**
*
* @param {string} lan
*/
setLanguage: function (lan){
this.localResource = this.localResource.prototype.getLocalResource(lan);
},
/**
* @method isValid
* @public
* @description ASYNC
* Checks if a DataRow "r" has a valid data. Returns an object { warningMsg, errMsg, errField, row }
* @param {DataRow} r
* @returns {Deferred} can be null or Object
*/
isValid: function(r) {
const emptyKeyMsg = this.localResource.dictionary.emptyKeyMsg;
const emptyFieldMsg = this.localResource.dictionary.emptyFieldMsg;
const stringTooLong = this.localResource.dictionary.stringTooLong;
const noDataSelected = this.localResource.dictionary.noDataSelected;
const emptyDate = "1000-01-01";
let outMsg = null;
let outField = null;
let val;
let outCaption = '';
let foundCondition = false;
const self = this;
if (!r) return self.getPromiseIsValidObject(noDataSelected, noDataSelected, outCaption, r);
_.forEach(
r.table.key() ,
function(colName) {
const col = r.table.columns[colName];
outCaption = colName;
if (col.caption && col.caption !== "") outCaption = col.caption;
val = r.current[colName];
if (col.caption && col.caption !== "" && col.caption !== colName) outCaption = col.caption;
if ((val === null)) {
outMsg = emptyKeyMsg;
outField = colName;
foundCondition = true;
return false;
}
if (col.ctype === CType.date){ //( typeof val === "object" && val.constructor === Date) {
if (!val) {
outMsg = emptyKeyMsg;
outField = colName;
foundCondition = true;
return false;
}
if (val.getTime && val.getTime() === new Date(emptyDate).getTime()){
outMsg = emptyKeyMsg;
outField = colName;
foundCondition = true;
return false;
}
}
if ( col.ctype === CType.string &&
(val.replace(/\s*$/,"") === "")) { //Esegue il trimEnd
outMsg = emptyKeyMsg;
outField = colName;
foundCondition = true;
return false;
}
if (!metaModel.allowZero(col) && metaModel.isColumnNumeric(col) && val === 0) {
outMsg = emptyKeyMsg;
outField = colName;
foundCondition = true;
return false;
}
return true;
});
// esco con promise se ho trovato una condizione di uscita
if (foundCondition) return self.getPromiseIsValidObject(outMsg, outField, outCaption, r);
outCaption = '';
_.forOwn(
r.table.columns ,
/**
* @param {DataColumn} c
* @return {boolean}
*/
function(c) {
let colname = c.name;
val = r.current[colname];
// caption è valorizzata dal back da colDescr oppure tramite la setCaption dal programmatore.
outCaption = colname;
if (c.caption && c.caption !== "") outCaption = c.caption;
if (c.ctype === CType.string) {
const thisLen = val ? val.toString().length : 0;
const maxLen = metaModel.getMaxLen(c);
if (maxLen > 0 && thisLen > maxLen) {
outMsg = stringTooLong;
outField = colname;
return false;
}
}
if (metaModel.allowDbNull(c) && !metaModel.denyNull(c)) {
return true; // Continua
}
if ((val === null) || (val === undefined)) {
outMsg = emptyFieldMsg;
outField = colname;
return false;
}
if (c.ctype === CType.date ) {
if (!val) {
outMsg = emptyKeyMsg;
outField = colname;
return false;
}
if (val.getTime && val.getTime() === new Date(emptyDate).getTime()) {
outMsg = emptyFieldMsg;
outField = colname;
return false;
}
}
// E' passato il if ((val === null) || (val === undefined)) ma devo fare attenzione a stringa vuota o a zero
if ((c.ctype === CType.string) && (val.replace(/\s*$/, "") === "")) {
outMsg = emptyFieldMsg;
outField = colname;
return false;
}
if (!metaModel.allowZero(c) && metaModel.isColumnNumeric(c) && metaModel.denyZero(c) && val === 0) {
outMsg = emptyFieldMsg;
outField = colname;
return false;
}
return true;
});
return self.getPromiseIsValidObject(outMsg, outField, outCaption, r);
},
/**
* @method setCaption
* @private
* @description SYNC
* To override in extended classes if user want to assign a friendly name to the column.
* Friendly names are used in isValid messages
* N.B:captions are set on backend, if there is a configuration o coldescr
* @param {DataTable} table
* @param {string} edittype
*/
setCaption: function(table, edittype) {
},
/**
* @method primaryKey
* @public
* @description SYNC
* Returns the list of primary key fields, this has to be redefined for views.
* @return {string[]}
*/
primaryKey: function() {
return [];
},
/**
* @method getPromiseIsValidObject
* @private
* @description ASYNC
* Auxiliar function that builds the result of the isValid promise
* @param {string} errMessage
* @param {string} colname
* @param {string} outCaption
* @param {DataRow} row
* @param {string} [warningMessage]
* @returns {Deferred}
*/
getPromiseIsValidObject: function (errMessage, colname, outCaption, row, warningMessage) {
let def = Deferred("getPromiseIsValidObject");
if(!errMessage && !colname) return def.resolve(null);
let objRes = {
warningMsg: warningMessage,
errMsg: errMessage + " (" + outCaption + ")",
errField: colname,
outCaption: outCaption,
row: row
};
return def.resolve(objRes).promise();
},
/**
* @method describeColumnsStructure
* @public
* @description ASYNCH
* Sets Captions, DenyNull and Format properties of Columns. They are usually set on backend.
* @param {DataTable} table
* @returns {*}
*/
describeColumnsStructure: function(table) {
},
/**
* @method describeColumnsStructure
* @public
* @description SYNC
* Set some information (useful on visualization) on column "cName"
* @param {DataTable} t
* @param {string} cName
* @param {string} caption
* @param {string} format
* @param {Number} pos
* @param {Number} maxLen
*/
describeAColumn: function (t, cName, caption, format, pos, maxLen) {
const c = t.columns[cName];
if (!c) return;
c.caption = (caption === '') ? '' : caption || c.name;
if (format) c.format = format;
if (maxLen) c.maxstringlen = maxLen;
if (maxLen) c.length = maxLen;
c.listColPos = pos || -1;
},
/**
* @method insertFilter
* @public
* @description SYNC
* @returns {jsDataQuery|null}
*/
insertFilter:function() {
return null;
},
/**
* @method searchFilter
* @public
* @description SYNC
* @returns {jsDataQuery|null}
*/
searchFilter: function () {
return null;
},
/**
* @method describeColumns
* @public
* @description ASYNC
* Describes a listing type (captions, column order, formulas, column formats and so on)
* @param {DataTable} table
* @param {string} listType
* @returns {Promise<DataTable>}
*/
describeColumns: function (table, listType) {
let def = Deferred("describeColumns");
// let self = this;
// // recupero dal server o dalla cache la tabella di cui è fatto il describe columns
// const res = getData.describeColumns(table, listType)
// .then(function (dtDescribed) {
// // applico alle colonne della output table quelle calcolate dalla describeColumns
// self.copyColumnsPropertiesToDescribe(dtDescribed.columns, table.columns);
// // 2. applico sorting e staticFilter che sono static calcolati in questa fase
// const sorting = self.getSorting(listType);
// metaModel.sorting(table, sorting ? sorting : metaModel.sorting(dtDescribed));
// table.staticFilter(dtDescribed.staticFilter());
// def.resolve();
// });
return def.resolve(table).promise();
},
// /**
// * @method copyColumnsPropertiesToDescribe
// * @public
// * @description SYNC
// * Copies the properties "columnsKeysDescribed" of the column from src to dest.
// * @param {{DataColumn}} colsSrc
// * @param {{DataColumn}} colsDest
// */
// copyColumnsPropertiesToDescribe:function (colsSrc, colsDest) {
// _.forIn(colsSrc, function (colSrc) {
// const colDest = colsDest[colSrc.name];
// if (!colDest) {
// return true; // prossima iterazione
// }
// _.forEach(['caption', 'listColPos', 'format', 'expression'], function (key) {
// colDest[key] = colSrc[key];
// });
// });
// },
/**
* @method describeTree
* @public
* @description ASYNC
* Describes the table of the tree
* @param {DataTable} table
* @param {string} listType
* @returns {{rootCondition: sqlFun, nodeDispatcher: TreeNode_Dispatcher, maxDepth: int}}
*/
describeTree:function (table, listType) {
return Deferred.resolve(true).promise();
},
/**
* @method getStaticFilter
* @public
* @description ASYNC
* Gets the static filter associated to the "listType"
* @param listType
* @return {jsDataQuery | null}
*/
getStaticFilter:function (listType) {
return null;
},
/**
* @method sortedColumnNameList
* @public
* @description ASYNC
* Returns the list of real (not temporary or expression) columns NAMES of a table "table"
* formatting it like "fieldname1, fieldname2,...."
* @param {DataTable} table
*/
sortedColumnNameList:function (table) {
return _.join(
_.map(
_.sortBy(
_.filter(table.columns,
function(c) {
if (metaModel.temporaryColumn(c)) return false;
if (c.name.startsWith("!")) return false;
// if (!c.listColPos) return false;
return c.listColPos !== -1;
}),
'listColPos'),
function (dc) {
return dc.name;
}),
",");
},
/**
* @method getName
* @public
* @description SYNC
* Gets metadata name
* @param {string} editType
* @returns {string}
*/
getName: function(editType) {
return this.tableName;
},
/**
* @method setDefaults
* @private
* @description ASYNCH
* Sets the default values for a DataTable. DataTable coming from server ha already its defaults. This method can contain some customization
* @param {DataTable} table
*/
setDefaults: function(table) {
// si intende che il datatable sia già corredato dai defaults per come è stato deserializzato dal server
// questo metodo può contenere al massimo delle personalizzazioni
},
/**
* @method getSorting
* @public
* @description SYNC
* Returns the default sorting for a list type "listType"
* @param {string} listType
* @returns {string|null}
*/
getSorting: function(listType) {
return null;
},
/**
* @method getNewRow
* @public
* @description ASYNC
* Gets new row, having ParentRow as Parent, and adds it on DataTable "dtDest"
* @param {DataRow} parentRow. Parent Row of the new Row to create, or null if no parent is present
* @param {DataTable} dtDest Table in which row has to be added
* @returns {Deferred<DataRow|null>}
*/
getNewRow:function (parentRow, dtDest) {
let def = new Deferred("getNewRow");
let realParentObjectRow = parentRow ? parentRow.current : undefined;
let objRow = dtDest.newRow({}, realParentObjectRow);
// restituisco la dataRow creata
return def.resolve(objRow.getRow());
},
/**
* @method copyExtraPropertiesTable
* @public
* @description SYNC
* Copies some useful properties form dtIn to dtOut
* @param {DataTable} dtIn
* @param {DataTable} dtDest
*/
copyExtraPropertiesTable:function (dtIn, dtDest) {
// copio tutte le proprietà delle colonne eventualmente ricalcolate, tranne
// quelle descritte nella describeColumns, cioè posizione e caption e formato
_.forEach(dtIn.columns, function (c) {
_.forOwn(c, function(value, key) {
if (_.includes(['caption', 'listColPos', 'format', 'expression'], key)) return true; // continua non copiare
if (dtDest.columns[c.name]) dtDest.columns[c.name][key] = value;
} );
});
// copia gli autoincrements
metaModel.copyAutoincrementsProperties(dtIn, dtDest);
// copia i defaults
dtDest.defaults(dtIn.defaults());
},
/**
* @method doDelete
* @public
* @description SYNC
* To override eventually. Copies the value of the column col of the row "source" on the row "dest"
* @param {DataColumn} col
* @param {ObjectRow} sourceRow
* @param {ObjectRow} destRow
*/
insertCopyColumn:function (col, sourceRow, destRow) {
destRow[col.name] = sourceRow[col.name];
},
/**
* @method selectByCondition
* @private
* @description ASYNC
* Returns a row searched by a filter condition if there is only one row that satisfy
* the filter, and it is a selectable row. Otherwise returns null
* @param {jsDataQuery} filter
* @param {string} tableName
* @returns {Deferred<DataRow>} A row belonging to a table equal to PrimaryTable
*/
selectByCondition:function (filter, tableName) {
const def = Deferred("selectByCondition");
const self = this;
const res = this.getData.selectCount(tableName, filter)
.then(function (resultCount) {
if (resultCount !== 1) return def.resolve(null);
return self.getData.runSelect(self.primaryTableName, "*", filter, null)
.then(function (dataTable) {
if (!dataTable.rows.length) return def.resolve(null);
return def.from(self.checkSelectRow(dataTable, dataTable.rows[0].getRow()));
});
});
return def.from(res).promise();
},
/***
* Client function
* @method checkSelectRow
* @private
* @description ASYNC
* @public
* Resolves a Deferred with dataRow if dataRow is selectable, null otherwise
* @param {DataTable} t
* @param {DataRow} dataRow
* @returns {Deferred<null | DataRow>}
*/
checkSelectRow:function(t, dataRow) {
if (typeof appMeta=== undefined)return ;
const modal = appMeta.BootstrapModal;
const def = Deferred("MetaData-checkSelectRow");
if (!dataRow) return def.resolve(null);
const res = this.canSelect(dataRow)
.then(function (result) {
if (result) {
return def.resolve(dataRow);
}
const winModal = new modal(
localResource.dictionary.alert,
localResource.dictionary.itemChooseNotSelectable, [localResource.dictionary.ok],
null,
null);
return winModal.show()
.then(function () {
return def.resolve(null);
});
});
return def.from(res).promise();
},
/**
*
* @param {DataRow} dataRow
* @returns {Promise<boolean>}
*/
canSelect:function (dataRow) {
return Deferred().resolve(true).promise();
},
recusiveNewCopyChilds: recusiveNewCopyChilds
};
MetaData.prototype.AutoInfo = AutoInfo;
/**
*
* @param {ObjectRow} destRow
* @param {ObjectRow} sourceRow
*/
function recusiveNewCopyChilds (destRow, sourceRow) {
/* DataTable */
let sourceTable = sourceRow.getRow().table;
/* DataSet */
let dsSource = sourceTable.dataset;
let relations = sourceTable.childRelations();
let allNewChildRowDeferred = [];
_.forEach(relations,
/**
* @param {DataRelation} rel
* @return {boolean}
*/
function (rel) {
let childTableName = rel.childTable;
let /* DataTable */ childTable = dsSource.tables[childTableName];
if (childTable.skipInsertCopy()) return true;
if (!metaModel.isSubEntity(childTable, destRow.getRow().table)) {
return true; // continua nel ciclo
}
if (childTableName === sourceTable.tableName) {
return true; // continua nel ciclo
}
let childRowCopy = rel.getChild(sourceRow); // sourceRow.getRow().getChildRows(rel.name);
let metaChild = this.getMeta(childTableName);
metaChild.setDefaults(childTable);
// creo catena di deferred iterative, ognuna ha bisogno del risultato precedente. poichè se ci sono più child devo inserire in
// self.state.DS.tables[defObj.childTableName] le righe con id momentaneo calcolato diverso. Lui riesce a calcolare
// l'id ovviamente solo se già ci sono le righe messe in precedenza. Nel vecchi metodo prima di questa modifica,
// metteva solo una riga l'ultima poichè l'id era sempre lo stesso. nel ciclo passavo sempre la tabella vuota all'inizio
let chain = Deferred.resolve(true);
_.forEach(childRowCopy, function (childSourceRow) {
chain = chain.then(function () {
return metaChild.getNewRow(destRow,childTable)
.then(function (newChildRow) {
// copio la riga child calcolata sul dt destinazione, così vado ogni volta ad incrementare le righe.
// nel successivo .then della catena il dt sarà modificato
_.forIn(childTable.columns,
/**
* @param {DataColumn} childCol
* @param {string} childColName
*/
function (childCol, childColName) {
if (rel.childCols.some( c => c === childColName )) return true; // continuo nel ciclo
metaChild.insertCopyColumn(childCol, childSourceRow, newChildRow);
});
return recusiveNewCopyChilds(newChildRow, childSourceRow);
});
});
// inserisco array di deferred , cioè uno per ogni relazione di cui eventualmente devo vedere i figli
allNewChildRowDeferred.push(chain);
});
}); // chiude primo for sulle relazioni
return Deferred.when.apply(Deferred, allNewChildRowDeferred);
}
// 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.MetaData = MetaData;
// Define as an anonymous module so, through path mapping, it can be
// referenced as the "underscore" module.
//noinspection JSUnresolvedFunction
define(function () {
return MetaData;
});
}
// Check for `exports` after `define` in case a build optimizer adds an `exports` object.
else if (freeExports && freeModule) {
if (moduleExports) { // Export for Node.js or RingoJS.
(freeModule.exports = MetaData).MetaData = MetaData;
}
else { // Export for Narwhal or Rhino -require.
freeExports.MetaData = MetaData;
}
}
else {
// Export for a browser or Rhino.
if (root.appMeta){
root.appMeta.MetaData = MetaData;
}
else {
root.MetaData=MetaData;
}
}
}( (typeof _ === 'undefined') ? require('lodash') : _,
(typeof appMeta === 'undefined') ? require('./MetaModel').metaModel : appMeta.metaModel,
(typeof appMeta === 'undefined') ? require('./LocalResource').localResource : appMeta.localResource,
(typeof appMeta === 'undefined') ? require('./EventManager').Deferred : appMeta.Deferred,
(typeof appMeta === 'undefined') ? require('./GetDataUtils') : appMeta.getDataUtils,
(typeof appMeta === 'undefined') ? require('./Logger').logger : appMeta.logger,
(typeof appMeta === 'undefined') ? require('./Logger').logTypeEnum : appMeta.logTypeEnum,
(typeof appMeta === 'undefined') ? undefined : appMeta.getMeta.bind(appMeta),
(typeof appMeta === 'undefined') ? undefined : appMeta.getData,
(typeof jsDataSet === 'undefined') ? require('./../metadata/jsDataSet').CType : jsDataSet.CType,
(typeof appMeta === 'undefined') ? undefined : appMeta.security,
)
);