Skip to content
Snippets Groups Projects
contentProcess.js 14.49 KiB
import("system.db");
import("system.translate");
import("system.result");
import("system.vars");
import("ObjectRelation_lib");
import("Context_lib");
import("Sql_lib");
import("Contact_lib");

/**
 * Rough functioning of the code:
 * - get parameters
 * - load data via _loadObjectRelationTree
 *  * _loadObjectRelationTree calls itself recursively
 * - data is insertet into the tree, 
 *   ->  this.push in _loadObjectRelationTree changes tree
 * - the "tree" is returned
 */

//TODO: Refactoring in this process. It's rather hard to understand and uses more sql queries than necessary.

//uid array structure
var UID = {
    objectId: 0,
    layer: 1,
    objectRelationTypeId: 2,
    otherObjectType: 3,
    relationTypeData: 4,
    myObjectType: 5,
    objectRelationId: 6,
    hierarchy: 7
}

var tree = [];

// uidParam: if only one row should be loaded
var uidParam;
if (vars.exists("$param.Uid_param") && vars.get("$param.Uid_param"))
    uidParam = vars.getString("$param.Uid_param");
else if(vars.exists("$local.idvalues") && vars.get("$local.idvalues") && vars.get("$local.idvalues").length > 0)
    uidParam = vars.get("$local.idvalues")[0];

if (uidParam && uidParam.includes("[")) // "[" -> is JSON
{
    // Load one by uid. Basically the data to return is also encoded in the uid itself -> no extra data loading is needed.
    var uid = JSON.parse(uidParam);

    // if objectRelationTypeId is a string it is a relation node and no Grouping
    if (uid != null && typeof uid[UID.objectRelationTypeId] == "string")
    {
        var relationTypeData = ObjectRelationUtils.getRelationType(uid[UID.objectRelationTypeId]);
        _insertEntry(tree, _getEntryData(uid[UID.objectId], relationTypeData[3], relationTypeData[7], relationTypeData[8], undefined, false, 
            uid[UID.objectRelationId]), "", 0, uid[UID.otherObjectType], relationTypeData[10], relationTypeData[12], relationTypeData[4]);
    }
}
else
{
    // load all
    var filter = vars.get("$local.filter")
    var selectedRelationType = null;

    // filter for the object relation type
    if (filter && filter.filter)
    {
        filter = filter.filter;
        if (filter.childs.length > 0)
            selectedRelationType = filter.childs[0].key;
    }
    
    // get the base object types / ids to load the tree fo
    if (vars.exists("$param.ObjectIds_param") && vars.get("$param.ObjectIds_param") && vars.get("$param.ObjectTypes_param")) 
    {
        var originalObjectIds = JSON.parse(vars.getString("$param.ObjectIds_param"));
        var originalObjectTypes = JSON.parse(vars.getString("$param.ObjectTypes_param"));
        
        if (originalObjectIds.length != originalObjectTypes.length)
            throw "the parameters ObjectIds_param and ObjectTypes_param do not contain a json array of the same length";
        
        // load the object relations for each given objectId / Type
        for (let i = 0; i < originalObjectIds.length; i++) 
        {
            //.call, because tree is used as 'this' to prevent accidentally overwriting tree
            _loadObjectRelationTree.call(tree, originalObjectIds[i], originalObjectTypes[i], selectedRelationType);
        }
    }
    if (uidParam) //workaround! 
        tree = tree.filter(function (row) {return JSON.parse(row[0])[UID.objectRelationId] == uidParam});
}

result.object(tree);

/**
 * This function loads the whole tree and calls itself recursively
 * @param {String} pObjectId The id of the object to load the tree for (e.g. a ContactId)
 * @param {String} pObjectType The type of the object to load the tree for (e.g. "Person")
 * @param {String} [pObjectRelationTypeId=undefined] if given: the tree is filtered by this relation type and only nodes with this type are added
 * @param {String} [pNodeId=null] the current nodeid is the node for which the next childrens are loaded. Needed for Recursion.
 * @param {Integer} [pLayer=0] this is the current layer to load. Needed for Recursion.
 * @param {String[][]} [pRelationTypeData=undefined] NOT ESSENTIAL it is just needed to not load the type data every time the function is called.
 *                                                   If it is missing, it's loaded via the id encoded inside of the uid
 *                                                   It only exists for better performance
 */
function _loadObjectRelationTree(pObjectId, pObjectType, pObjectRelationTypeId, pNodeId, pLayer, pRelationTypeData)
{
    // prevent stack overflows
    if (pLayer > 30 || !pObjectId || !pObjectType)
        return;
    
    if (pLayer == undefined)
        pLayer = 0;
    
    if (pNodeId === undefined)
        pNodeId = null;
    
    var currentObjectId = pObjectId;
        
    if (pLayer == 0)
    {
        if (pObjectRelationTypeId)
        {
            let relationTypeData = ObjectRelationUtils.getRelationType(pObjectRelationTypeId);
            // if hirachy: get most top id else use the current currentObjectId
            if (relationTypeData[4] == "1")
            {   // use always reverse-type
                relationTypeData = ObjectRelationUtils.getRelationType(relationTypeData[8]);
                currentObjectId = _getRootID(currentObjectId, relationTypeData);
            } 

            var parentTmpTree = [];
            var childsTmpTree = [];
            // add as group to parentTmpTree but not yet to the tree itself
                                                                                                                                  // true to enable the insert button always --v
            let uids = _insertEntry(parentTmpTree, [[currentObjectId, "", "", "", "", relationTypeData[7]]], pNodeId, pLayer, pObjectType, pObjectRelationTypeId, relationTypeData[12], true)
            for (let i = 0; i < uids.length; i++) 
            {   // recursive call
                _loadObjectRelationTree.call(childsTmpTree, uids[i][UID.objectId], uids[i][UID.otherObjectType], relationTypeData[0], uids[i], pLayer+1, relationTypeData);
            }
            
            // only add parent if childs exist
            if (childsTmpTree.length > 0)
                this.push.apply(this, parentTmpTree.concat(childsTmpTree)); //use push since you can't overwrite this
        }
        else // no ObjectType chosen
        {
            // load all ObjectRelationTypes
            var relationTypes = _getPossibleRelationTypes(pObjectType);
            
            relationTypes.forEach(function (relationType, i)
            {
                var [relationTypeId, title,, direction,,,, relationType1, relationTye2,,,, icon] = relationType;
                var data = _getEntryData(currentObjectId, direction, relationType1, relationTye2);

                // if any subentry: show objectType
                if (data.length > 0)
                {
                    // TODO: Icons, BINDATA
                    // var icon = getIcon...
                    let uid = [currentObjectId, i, relationType];
                    // add the relationtype as grouping
                    this.push([JSON.stringify(uid), null, translate.text(title), JSON.stringify(pNodeId), true, null, null, "", relationTypeId, icon]);

                    _loadObjectRelationTree.call(this, pObjectId, pObjectType, pObjectRelationTypeId, uid, pLayer+1, relationTypes[i]);
                }
            }, this);
        }
    }
    else if (pLayer >= 1)
    {
        // if no relationType given, load from nodeId
        if (!pRelationTypeData)
            pRelationTypeData = pNodeId[UID.objectRelationTypeId];

        // if it's only the id, load via function
        if (typeof pRelationTypeData == "string")
            pRelationTypeData = ObjectRelationUtils.getRelationType(pRelationTypeData);
        
        var [thisRelationTypeId,,, direction, hierarchy,, destObjectType, relationType1, relationType2,, otherRelationTypeId,, icon] = pRelationTypeData;

        var relationTypeIdForNew = otherRelationTypeId;

        if (hierarchy == "1")
        {
            var myData = _getEntryData(pNodeId[0], direction, relationType1, relationType2)

            // if hierarchy and selected RelationType -> use the selected one
            relationTypeIdForNew = pObjectRelationTypeId || thisRelationTypeId;

            let uids = _insertEntry(this, myData, pNodeId, pLayer, destObjectType, relationTypeIdForNew, icon, hierarchy)
            for (let i = 0; i < uids.length; i++) 
            {   // recursive call
                _loadObjectRelationTree.call(this, uids[i][UID.objectId], uids[i][UID.otherObjectType], pObjectRelationTypeId, uids[i], pLayer+1, pRelationTypeData);
            }
        }
        else
        {
            // pNodeId[4] is the previous NodeId and pNodeId[4][0] the previous ObjectId
            var prevObjectId;
            if (pNodeId[4] != undefined)
                prevObjectId = pNodeId[UID.relationTypeData][0];

            var entryData = _getEntryData(pNodeId[UID.objectId], direction, relationType1, relationType2, prevObjectId, true);

            if (direction == "same")
                relationTypeIdForNew = thisRelationTypeId

            // add both sides. Only one will succeed, because the prevObjectId will be filtered
            _insertEntry(this, entryData, pNodeId, pLayer, destObjectType, thisRelationTypeId, icon, hierarchy, 0);
            if (direction == "same")
            {
                var otherEntryData = _getEntryData(pNodeId[UID.objectId], "normal", relationType1, relationType2, prevObjectId, true);
                _insertEntry(this, otherEntryData, pNodeId, pLayer, destObjectType, thisRelationTypeId, icon, hierarchy, 1);
            }
        }
    }
}

/**
 * load data for a relation.
 * OBJECT_ROWID, AB_OBJECTRELATIONID, OBJECT_TYPE, RELATION_TITLE, AB_OBJECTRELATIONTYPEID
 * 
 * @param {String} pObjectId
 * @param {String} pDirection
 * @param {String} pRelationType1
 * @param {String} pRelationType2
 * @param {String} [pPrevId=undefined] Id of the previous node to exclude it
 * @param {Boolean} [pNoRecursion=false] if false: select for direction "same" the other direction, if result is empty.
 * @param {String} [pObjectRelationId] provide if only one special node is needed
 * 
 * @return {[][]}
 */
function _getEntryData(pObjectId, pDirection, pRelationType1, pRelationType2, pPrevId, pNoRecursion, pObjectRelationId)
{
    if (pRelationType1 == undefined || pRelationType2 == undefined) 
        return [];
    
    var [myNum, otherNum] = pDirection == "normal" ? [2, 1] : [1, 2];
    
    onConditionForRelationTypeJoin = newWhere("AB_OBJECTRELATIONTYPEID = AB_OBJECTRELATIONTYPE" + myNum)
        .and("AB_OBJECTRELATION.AB_OBJECTRELATIONTYPE1", pRelationType1)
        .and("AB_OBJECTRELATION.AB_OBJECTRELATIONTYPE2", pRelationType2)
        .and("AB_OBJECTRELATION.OBJECT" + myNum + "_ROWID", pObjectId)
        .andIfSet("AB_OBJECTRELATION.AB_OBJECTRELATIONID", pObjectRelationId || null); // set id to null, as only null works with .andIfSet
    
    // exclude previous node
    if (!pPrevId)
        onConditionForRelationTypeJoin.and("AB_OBJECTRELATION.OBJECT" + otherNum + "_ROWID is not null");
    else
        onConditionForRelationTypeJoin.and("AB_OBJECTRELATION.OBJECT" + otherNum + "_ROWID", pPrevId, SqlBuilder.NOT_EQUAL());
    
    // TODO: BINDATA?
    // var image = getImageObject("Beziehung");
    var data = newSelect("OBJECT" + (pObjectRelationId ? myNum : otherNum) + "_ROWID, AB_OBJECTRELATIONID, OBJECT_TYPE, RELATION_TITLE, INFO, AB_OBJECTRELATIONTYPEID")
        .from("AB_OBJECTRELATION")
        .join("AB_OBJECTRELATIONTYPE", onConditionForRelationTypeJoin)
        .table();
                        
    // try again with other side for "same"
    if (data.length == 0 && pDirection == "same" && !pNoRecursion || pObjectRelationId && data.length > 0 && !data[0][0])
         return _getEntryData(pObjectId, "normal", pRelationType1, pRelationType2, pPrevId, true, pObjectRelationId)
    
    // TODO: BINDATA?
    //for ( var i = 0; i < data.length; i++)  data[i][2] = image[data[i][2]] == undefined ? "" : image[data[i][2]];
    return data;
}

function _getPossibleRelationTypes(pObjectType)
{
    // TODO: load from entity when possible
    return ObjectRelationUtils.getPossibleRelationTypes([pObjectType], true);
}

/**
 * insert a new Entry
 * 
 * @param {Array} pTree
 * @param {Array[][]} pEntryData
 * @param {Array[][]} pNodeId id of the parent
 * @param {Integer} pLayer layernumber
 * @param {String} pObjectType
 * @param {String} pNewRelationTypeId the RelationType, a new relation should have, if this node is selected.
 * @param {String} pIcon the icon which should be added to the entry
 * @param {Boolean} pHierarchy add if hierarchy is enabled or not for this entry
 * @param {Integer} [pNum=undefined] optional number added to the key. Needed, if the key would not be unique.
 * 
 * @return {Array[][]} the uids of the inserted data. Consists of [ObjectId, pEntryData-Index, AB_OBJECTRELATIONTYPEID, pObjectType (from param), pNodeId, objecttype (from entryId), objectrelationid, hierarchy]
 */
function _insertEntry(pTree, pEntryData, pNodeId, pLayer, pObjectType, pNewRelationTypeId, pIcon, pHierarchy, pNum)
{
    var expanded = pLayer <= 10;
    
    // TODO: display address (tooltip wird denke ich nicht benötigt, da preview vorhanden)
    var uids = [];
    pEntryData.forEach(function ([objectId, objectRelationId, objectType, relationTitle, info, objectRelationTypeId], i)
    {
        //TODO: entities.getRow, check if this is possible with fewer queries
        var display = db.cell(ContextUtils.getNameSql(pObjectType, objectId));
        // TODO: Icon                       
        var uid = [objectId, i, objectRelationTypeId, pObjectType, pNodeId, objectType, objectRelationId, pHierarchy];

        if (pNum)
            uid.push(pNum);
        uids.push(uid);
        pTree.push([JSON.stringify(uid), objectRelationId, display, JSON.stringify(pNodeId), expanded, objectId, pObjectType, info, pNewRelationTypeId, pIcon]);
    });
    return uids;
}

/*
* get most top root of a node
*
* @param {String} pObjectId
* @param {String[]} pObjectRelationTypeData
*
* @return {String} RootObjectId        
*/
function _getRootID(pObjectId, pObjectRelationTypeData) 
{
    var sourceid = pObjectId;
    var max = 100;
    var rootid = sourceid;
    for (let max = 100; sourceid && max > 0; max--)
    {
        //TODO: refactor for less queries
        sourceid = newSelect("OBJECT1_ROWID")
            .from("AB_OBJECTRELATION")
            .where("AB_OBJECTRELATION.OBJECT2_ROWID", sourceid)
            .and("AB_OBJECTRELATION.AB_OBJECTRELATIONTYPE1", pObjectRelationTypeData[7])
            .and("AB_OBJECTRELATION.AB_OBJECTRELATIONTYPE2", pObjectRelationTypeData[8])
            .cell();
    }
    
    return rootid; 
}