<commit_before>var safeRequire = require('../utils').safeRequire;

/**
 * Module dependencies
 */
var neo4j = safeRequire('neo4j');

exports.initialize = function initializeSchema(schema, callback) {
    schema.client = new neo4j.GraphDatabase(schema.settings.url);
    schema.adapter = new Neo4j(schema.client);
    process.nextTick(callback);
};

function Neo4j(client) {
    this._models = {};
    this.client = client;
    this.cache = {};
}

Neo4j.prototype.define = function defineModel(descr) {
    this.mixClassMethods(descr.model, descr.properties);
    this.mixInstanceMethods(descr.model.prototype, descr.properties);
    this._models[descr.model.modelName] = descr;
};

Neo4j.prototype.createIndexHelper = function (cls, indexName) {
    var db = this.client;
    var method = 'findBy' + indexName[0].toUpperCase() + indexName.substr(1);
    cls[method] = function (value, cb) {
        db.getIndexedNode(cls.modelName, indexName, value, function (err, node) {
            if (err) return cb(err);
            if (node) {
                node.data.id = node.id;
                cb(null, new cls(node.data));
            } else {
                cb(null, null);
            }
        });
    };
};

Neo4j.prototype.mixClassMethods = function mixClassMethods(cls, properties) {
    var neo = this;

    Object.keys(properties).forEach(function (name) {
        if (properties[name].index) {
            neo.createIndexHelper(cls, name);
        }
    });

    cls.setupCypherQuery = function (name, queryStr, rowHandler) {
        cls[name] = function cypherQuery(params, cb) {
            if (typeof params === 'function') {
                cb = params;
                params = [];
            } else if (params.constructor.name !== 'Array') {
                params = [params];
            }

            var i = 0;
            var q = queryStr.replace(/\?/g, function () {
                return params[i++];
            });

            neo.client.query(function (err, result) {
                if (err) return cb(err, []);
                cb(null, result.map(rowHandler));
            }, q);
        };
    };

    /**
     * @param from - id of object to check relation from
     * @param to - id of object to check relation to
     * @param type - type of relation
     * @param direction - all | incoming | outgoing
     * @param cb - callback (err, rel || false)
     */
    cls.relationshipExists = function relationshipExists(from, to, type, direction, cb) {
        neo.node(from, function (err, node) {
            if (err) return cb(err);
            node._getRelationships(direction, type, function (err, rels) {
                if (err && cb) return cb(err);
                if (err && !cb) throw err;
                var found = false;
                if (rels && rels.forEach) {
                    rels.forEach(function (r) {
                        if (r.start.id === from && r.end.id === to) {
                            found = true;
                        }
                    });
                }
                cb  && cb(err, found);
            });
        });
    };

    cls.createRelationshipTo = function createRelationshipTo(id1, id2, type, data, cb) {
        var fromNode, toNode;
        neo.node(id1, function (err, node) {
            if (err && cb) return cb(err);
            if (err && !cb) throw err;
            fromNode = node;
            ok();
        });
        neo.node(id2, function (err, node) {
            if (err && cb) return cb(err);
            if (err && !cb) throw err;
            toNode = node;
            ok();
        });
        function ok() {
            if (fromNode && toNode) {
                fromNode.createRelationshipTo(toNode, type, cleanup(data), cb);
            }
        }
    };

    cls.createRelationshipFrom = function createRelationshipFrom(id1, id2, type, data, cb) {
        cls.createRelationshipTo(id2, id1, type, data, cb);
    }

    // only create relationship if it is not exists
    cls.ensureRelationshipTo = function (id1, id2, type, data, cb) {
        cls.relationshipExists(id1, id2, type, 'outgoing', function (err, exists) {
            if (err && cb) return cb(err);
            if (err && !cb) throw err;
            if (exists) return cb && cb(null);
            cls.createRelationshipTo(id1, id2, type, data, cb);
        });
    }
};

Neo4j.prototype.mixInstanceMethods = function mixInstanceMethods(proto) {
    var neo = this;

    /**
     * @param obj - Object or id of object to check relation with
     * @param type - type of relation
     * @param cb - callback (err, rel || false)
     */
    proto.isInRelationWith = function isInRelationWith(obj, type, direction, cb) {
        this.constructor.relationshipExists(this.id, obj.id || obj, type, 'all', cb);
    };
};

Neo4j.prototype.node = function find(id, callback) {
    if (this.cache[id]) {
        callback(null, this.cache[id]);
    } else {
        this.client.getNodeById(id, function (err, node) {
            if (node) {
                this.cache[id] = node;
            }
            callback(err, node);
        }.bind(this));
    }
};

Neo4j.prototype.create = function create(model, data, callback) {
    data.nodeType = model;
    var node = this.client.createNode();
    node.data = cleanup(data);
    node.data.nodeType = model;
    node.save(function (err) {
        if (err) {
            return callback(err);
        }
        this.cache[node.id] = node;
        node.index(model, 'id', node.id, function (err) {
            if (err) return callback(err);
            this.updateIndexes(model, node, function (err) {
                if (err) return callback(err);
                callback(null, node.id);
            });
        }.bind(this));
    }.bind(this));
};

Neo4j.prototype.updateIndexes = function updateIndexes(model, node, cb) {
    var props = this._models[model].properties;
    var wait = 1;
    Object.keys(props).forEach(function (key) {
        if (props[key].index && node.data[key]) {
            wait += 1;
            node.index(model, key, node.data[key], done);
        }
    });

    done();

    var error = false;
    function done(err) {
        error = error || err;
        if (--wait === 0) {
            cb(error);
        }
    }
};

Neo4j.prototype.save = function save(model, data, callback) {
    var self = this;
    this.node(data.id, function (err, node) {
        if (err) return callback(err);
        node.data = cleanup(data);
        node.save(function (err) {
            if (err) return callback(err);
            self.updateIndexes(model, node, function (err) {
                if (err) return console.log(err);
                callback(null, node.data);
            });
        });
    });
};

Neo4j.prototype.exists = function exists(model, id, callback) {
    delete this.cache[id];
    this.node(id, callback);
};

Neo4j.prototype.find = function find(model, id, callback) {
    delete this.cache[id];
    this.node(id, function (err, node) {
        if (node && node.data) {
            node.data.id = id;
        }
        callback(err, this.readFromDb(model, node && node.data));
    }.bind(this));
};

Neo4j.prototype.readFromDb = function readFromDb(model, data) {
    if (!data) return data;
    var res = {};
    var props = this._models[model].properties;
    Object.keys(data).forEach(function (key) {
        if (props[key] && props[key].type.name === 'Date') {
            res[key] = new Date(data[key]);
        } else {
            res[key] = data[key];
        }
    });
    return res;
};

Neo4j.prototype.destroy = function destroy(model, id, callback) {
    var force = true;
    this.node(id, function (err, node) {
        if (err) return callback(err);
        node.delete(function (err) {
            if (err) return callback(err);
            delete this.cache[id];
        }.bind(this), force);
    });
};

Neo4j.prototype.all = function all(model, filter, callback) {
    this.client.queryNodeIndex(model, 'id:*', function (err, nodes) {
        if (nodes) {
            nodes = nodes.map(function (obj) {
                obj.data.id = obj.id;
                return this.readFromDb(model, obj.data);
            }.bind(this));
        }
        if (filter) {
            nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
            if (filter.order) {
                var key = filter.order.split(' ')[0];
                var dir = filter.order.split(' ')[1];
                nodes = nodes.sort(function (a, b) {
                    return a[key] > b[key];
                });
                if (dir === 'DESC') nodes = nodes.reverse();
            }
        }
        callback(err, nodes);
    }.bind(this));
};

Neo4j.prototype.allNodes = function all(model, callback) {
    this.client.queryNodeIndex(model, 'id:*', function (err, nodes) {
        callback(err, nodes);
    });
};

function applyFilter(filter) {
    if (typeof filter.where === 'function') {
        return filter.where;
    }
    var keys = Object.keys(filter.where || {});
    return function (obj) {
        var pass = true;
        keys.forEach(function (key) {
            if (!test(filter.where[key], obj[key])) {
                pass = false;
            }
        });
        return pass;
    }

    function test(example, value) {
        if (typeof value === 'string' && example && example.constructor.name === 'RegExp') {
            return value.match(example);
        }
        if (typeof value === 'object' && value.constructor.name === 'Date' && typeof example === 'object' && example.constructor.name === 'Date') {
            return example.toString() === value.toString();
        }
        // not strict equality
        return example == value;
    }
}

Neo4j.prototype.destroyAll = function destroyAll(model, callback) {
    var wait, error = null;
    this.allNodes(model, function (err, collection) {
        if (err) return callback(err);
        wait = collection.length;
        collection && collection.forEach && collection.forEach(function (node) {
            node.delete(done, true);
        });
    });

    function done(err) {
        error = error || err;
        if (--wait === 0) {
            callback(error);
        }
    }
};

Neo4j.prototype.count = function count(model, callback, conds) {
    this.all(model, {where: conds}, function (err, collection) {
        callback(err, collection ? collection.length : 0);
    });
};

Neo4j.prototype.updateAttributes = function updateAttributes(model, id, data, cb) {
    data.id = id;
    this.node(id, function (err, node) {
        this.save(model, merge(node.data, data), cb);
    }.bind(this));
};

function cleanup(data) {
    if (!data) return null;

    var res = {};
    Object.keys(data).forEach(function (key) {
        var v = data[key];
        if (v === null) {
            // skip
            // console.log('skip null', key);
        } else if (v && v.constructor.name === 'Array' && v.length === 0) {
            // skip
            // console.log('skip blank array', key);
        } else if (typeof v !== 'undefined') {
            res[key] = v;
        }
    });
    return res;
}

function merge(base, update) {
    Object.keys(update).forEach(function (key) {
        base[key] = update[key];
    });
    return base;
}
<commit_msg>re-adding fix to not save id as a property on update<commit_after>var safeRequire = require('../utils').safeRequire;

/**
 * Module dependencies
 */
var neo4j = safeRequire('neo4j');

exports.initialize = function initializeSchema(schema, callback) {
    schema.client = new neo4j.GraphDatabase(schema.settings.url);
    schema.adapter = new Neo4j(schema.client);
    process.nextTick(callback);
};

function Neo4j(client) {
    this._models = {};
    this.client = client;
    this.cache = {};
}

Neo4j.prototype.define = function defineModel(descr) {
    this.mixClassMethods(descr.model, descr.properties);
    this.mixInstanceMethods(descr.model.prototype, descr.properties);
    this._models[descr.model.modelName] = descr;
};

Neo4j.prototype.createIndexHelper = function (cls, indexName) {
    var db = this.client;
    var method = 'findBy' + indexName[0].toUpperCase() + indexName.substr(1);
    cls[method] = function (value, cb) {
        db.getIndexedNode(cls.modelName, indexName, value, function (err, node) {
            if (err) return cb(err);
            if (node) {
                node.data.id = node.id;
                cb(null, new cls(node.data));
            } else {
                cb(null, null);
            }
        });
    };
};

Neo4j.prototype.mixClassMethods = function mixClassMethods(cls, properties) {
    var neo = this;

    Object.keys(properties).forEach(function (name) {
        if (properties[name].index) {
            neo.createIndexHelper(cls, name);
        }
    });

    cls.setupCypherQuery = function (name, queryStr, rowHandler) {
        cls[name] = function cypherQuery(params, cb) {
            if (typeof params === 'function') {
                cb = params;
                params = [];
            } else if (params.constructor.name !== 'Array') {
                params = [params];
            }

            var i = 0;
            var q = queryStr.replace(/\?/g, function () {
                return params[i++];
            });

            neo.client.query(function (err, result) {
                if (err) return cb(err, []);
                cb(null, result.map(rowHandler));
            }, q);
        };
    };

    /**
     * @param from - id of object to check relation from
     * @param to - id of object to check relation to
     * @param type - type of relation
     * @param direction - all | incoming | outgoing
     * @param cb - callback (err, rel || false)
     */
    cls.relationshipExists = function relationshipExists(from, to, type, direction, cb) {
        neo.node(from, function (err, node) {
            if (err) return cb(err);
            node._getRelationships(direction, type, function (err, rels) {
                if (err && cb) return cb(err);
                if (err && !cb) throw err;
                var found = false;
                if (rels && rels.forEach) {
                    rels.forEach(function (r) {
                        if (r.start.id === from && r.end.id === to) {
                            found = true;
                        }
                    });
                }
                cb  && cb(err, found);
            });
        });
    };

    cls.createRelationshipTo = function createRelationshipTo(id1, id2, type, data, cb) {
        var fromNode, toNode;
        neo.node(id1, function (err, node) {
            if (err && cb) return cb(err);
            if (err && !cb) throw err;
            fromNode = node;
            ok();
        });
        neo.node(id2, function (err, node) {
            if (err && cb) return cb(err);
            if (err && !cb) throw err;
            toNode = node;
            ok();
        });
        function ok() {
            if (fromNode && toNode) {
                fromNode.createRelationshipTo(toNode, type, cleanup(data), cb);
            }
        }
    };

    cls.createRelationshipFrom = function createRelationshipFrom(id1, id2, type, data, cb) {
        cls.createRelationshipTo(id2, id1, type, data, cb);
    }

    // only create relationship if it is not exists
    cls.ensureRelationshipTo = function (id1, id2, type, data, cb) {
        cls.relationshipExists(id1, id2, type, 'outgoing', function (err, exists) {
            if (err && cb) return cb(err);
            if (err && !cb) throw err;
            if (exists) return cb && cb(null);
            cls.createRelationshipTo(id1, id2, type, data, cb);
        });
    }
};

Neo4j.prototype.mixInstanceMethods = function mixInstanceMethods(proto) {
    var neo = this;

    /**
     * @param obj - Object or id of object to check relation with
     * @param type - type of relation
     * @param cb - callback (err, rel || false)
     */
    proto.isInRelationWith = function isInRelationWith(obj, type, direction, cb) {
        this.constructor.relationshipExists(this.id, obj.id || obj, type, 'all', cb);
    };
};

Neo4j.prototype.node = function find(id, callback) {
    if (this.cache[id]) {
        callback(null, this.cache[id]);
    } else {
        this.client.getNodeById(id, function (err, node) {
            if (node) {
                this.cache[id] = node;
            }
            callback(err, node);
        }.bind(this));
    }
};

Neo4j.prototype.create = function create(model, data, callback) {
    data.nodeType = model;
    var node = this.client.createNode();
    node.data = cleanup(data);
    node.data.nodeType = model;
    node.save(function (err) {
        if (err) {
            return callback(err);
        }
        this.cache[node.id] = node;
        node.index(model, 'id', node.id, function (err) {
            if (err) return callback(err);
            this.updateIndexes(model, node, function (err) {
                if (err) return callback(err);
                callback(null, node.id);
            });
        }.bind(this));
    }.bind(this));
};

Neo4j.prototype.updateIndexes = function updateIndexes(model, node, cb) {
    var props = this._models[model].properties;
    var wait = 1;
    Object.keys(props).forEach(function (key) {
        if (props[key].index && node.data[key]) {
            wait += 1;
            node.index(model, key, node.data[key], done);
        }
    });

    done();

    var error = false;
    function done(err) {
        error = error || err;
        if (--wait === 0) {
            cb(error);
        }
    }
};

Neo4j.prototype.save = function save(model, data, callback) {
    var self = this;
    //delete id property since that's redundant and we use the node.id
    delete data.id;
    this.node(data.id, function (err, node) {
        if (err) return callback(err);
        node.data = cleanup(data);
        node.save(function (err) {
            if (err) return callback(err);
            self.updateIndexes(model, node, function (err) {
                if (err) return console.log(err);
                //map node id to the id property being sent back
                node.data.id = node.id;
                callback(null, node.data);
            });
        });
    });
};

Neo4j.prototype.exists = function exists(model, id, callback) {
    delete this.cache[id];
    this.node(id, callback);
};

Neo4j.prototype.find = function find(model, id, callback) {
    delete this.cache[id];
    this.node(id, function (err, node) {
        if (node && node.data) {
            node.data.id = id;
        }
        callback(err, this.readFromDb(model, node && node.data));
    }.bind(this));
};

Neo4j.prototype.readFromDb = function readFromDb(model, data) {
    if (!data) return data;
    var res = {};
    var props = this._models[model].properties;
    Object.keys(data).forEach(function (key) {
        if (props[key] && props[key].type.name === 'Date') {
            res[key] = new Date(data[key]);
        } else {
            res[key] = data[key];
        }
    });
    return res;
};

Neo4j.prototype.destroy = function destroy(model, id, callback) {
    var force = true;
    this.node(id, function (err, node) {
        if (err) return callback(err);
        node.delete(function (err) {
            if (err) return callback(err);
            delete this.cache[id];
        }.bind(this), force);
    });
};

Neo4j.prototype.all = function all(model, filter, callback) {
    this.client.queryNodeIndex(model, 'id:*', function (err, nodes) {
        if (nodes) {
            nodes = nodes.map(function (obj) {
                obj.data.id = obj.id;
                return this.readFromDb(model, obj.data);
            }.bind(this));
        }
        if (filter) {
            nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
            if (filter.order) {
                var key = filter.order.split(' ')[0];
                var dir = filter.order.split(' ')[1];
                nodes = nodes.sort(function (a, b) {
                    return a[key] > b[key];
                });
                if (dir === 'DESC') nodes = nodes.reverse();
            }
        }
        callback(err, nodes);
    }.bind(this));
};

Neo4j.prototype.allNodes = function all(model, callback) {
    this.client.queryNodeIndex(model, 'id:*', function (err, nodes) {
        callback(err, nodes);
    });
};

function applyFilter(filter) {
    if (typeof filter.where === 'function') {
        return filter.where;
    }
    var keys = Object.keys(filter.where || {});
    return function (obj) {
        var pass = true;
        keys.forEach(function (key) {
            if (!test(filter.where[key], obj[key])) {
                pass = false;
            }
        });
        return pass;
    }

    function test(example, value) {
        if (typeof value === 'string' && example && example.constructor.name === 'RegExp') {
            return value.match(example);
        }
        if (typeof value === 'object' && value.constructor.name === 'Date' && typeof example === 'object' && example.constructor.name === 'Date') {
            return example.toString() === value.toString();
        }
        // not strict equality
        return example == value;
    }
}

Neo4j.prototype.destroyAll = function destroyAll(model, callback) {
    var wait, error = null;
    this.allNodes(model, function (err, collection) {
        if (err) return callback(err);
        wait = collection.length;
        collection && collection.forEach && collection.forEach(function (node) {
            node.delete(done, true);
        });
    });

    function done(err) {
        error = error || err;
        if (--wait === 0) {
            callback(error);
        }
    }
};

Neo4j.prototype.count = function count(model, callback, conds) {
    this.all(model, {where: conds}, function (err, collection) {
        callback(err, collection ? collection.length : 0);
    });
};

Neo4j.prototype.updateAttributes = function updateAttributes(model, id, data, cb) {
    data.id = id;
    this.node(id, function (err, node) {
        this.save(model, merge(node.data, data), cb);
    }.bind(this));
};

function cleanup(data) {
    if (!data) return null;

    var res = {};
    Object.keys(data).forEach(function (key) {
        var v = data[key];
        if (v === null) {
            // skip
            // console.log('skip null', key);
        } else if (v && v.constructor.name === 'Array' && v.length === 0) {
            // skip
            // console.log('skip blank array', key);
        } else if (typeof v !== 'undefined') {
            res[key] = v;
        }
    });
    return res;
}

function merge(base, update) {
    Object.keys(update).forEach(function (key) {
        base[key] = update[key];
    });
    return base;
}
<|endoftext|>


<commit_before>/*
* Shader Compiled Data
* @author: Mikael Emtinger, gomo.se
*/

GLOW.CompiledData = (function() {

    "use strict"; "use restrict";

    // private data, functions and initializations here

    // constructor

    function GLOWCompiledData( program, uniforms, attributes, interleavedAttributes, elements, extras ) {
	    this.program = program;
	    this.uniforms = uniforms !== undefined ? uniforms : {};
	    this.attributes = attributes !== undefined ? attributes : {};
	    this.interleavedAttributes = interleavedAttributes !== undefined ? interleavedAttributes : {};
	    this.elements = elements;

	    extras = extras !== undefined ? extras : {};
	    this.preDrawCallback = extras.preDrawCallback;
	    this.postDrawCallback = extras.postDrawCallback;
	    this.blend = extras.blend;
	    this.stencil = extras.stencil;
    }

    // methods
    GLOWCompiledData.prototype.clone = function( except ) {
    	var clone = new GLOW.CompiledData();
    	except = except !== undefined ? except : {};

    	var u;
    	for( u in this.uniforms ) {
    		if( except[ u ] ) {
    			clone.uniforms[ u ] = new GLOW.Uniform( this.uniforms[ u ], except[ u ] );
				if( clone.uniforms[ u ].type === GL.SAMPLER_2D ||
				    clone.uniforms[ u ].type === GL.SAMPLER_CUBE ) {
					clone.uniforms[ u ].data.init();
					clone.uniforms[ u ].textureUnit = this.uniforms[ u ].textureUnit;
				}
    		} else {
    			clone.uniforms[ u ] = this.uniforms[ u ];
    		}
    	}

    	var a;
    	for( a in this.attributes ) {
    		if( except[ a ] ) {
    			clone.attributes[ a ] = new GLOW.Attribute( this.attributes[ a ], except[ a ] );
    		} else {
    			clone.attributes[ a ] = this.attributes[ a ];
    		}
    	}

    	var i;
    	for( i in this.interleavedAttributes ) {
    	    if( except[ i ] ) {
// This really needs some cleaning up... somehow.
//    	        clone.interleavedAttributes[ i ] = new GLOW.InterleavedAttributes( except[ i ] );
    	        clone.interleavedAttributes[ i ] = except[ i ];
    	    } else {
    	        clone.interleavedAttributes[ i ] = this.interleavedAttributes[ i ];
    	    }
    	}

    	if( except.elements ) {
    		clone.elements = new GLOW.Elements( except.elements );
    	} else {
    		clone.elements = this.elements;
    	}

        if( except.program ) {
        	clone.program = except.program;
        } else {
        	clone.program = this.program;
        }

        if( except.blend ) {
            clone.blend = except.blend;
        } else {
            clone.blend = this.blend;
        }

        if( except.stencil ) {
            clone.stencil = except.stencil;
        } else {
            clone.stencil = this.stencil;
        }

        if( except.preDrawCallback ) {
            clone.preDrawCallback = except.preDrawCallback;
        } else {
            clone.preDrawCallback = this.preDrawCallback;
        }

        if( except.postDrawCallback ) {
            clone.postDrawCallback = except.postDrawCallback;
        } else {
            clone.postDrawCallback = this.postDrawCallback;
        }

    	return clone;
    };

    return GLOWCompiledData;
})();

<commit_msg>Fixed so cloning except elements now looks for indices and primitives<commit_after>/*
* Shader Compiled Data
* @author: Mikael Emtinger, gomo.se
*/

GLOW.CompiledData = (function() {

    "use strict"; "use restrict";

    // private data, functions and initializations here

    // constructor

    function GLOWCompiledData( program, uniforms, attributes, interleavedAttributes, elements, extras ) {
	    this.program = program;
	    this.uniforms = uniforms !== undefined ? uniforms : {};
	    this.attributes = attributes !== undefined ? attributes : {};
	    this.interleavedAttributes = interleavedAttributes !== undefined ? interleavedAttributes : {};
	    this.elements = elements;

	    extras = extras !== undefined ? extras : {};
	    this.preDrawCallback = extras.preDrawCallback;
	    this.postDrawCallback = extras.postDrawCallback;
	    this.blend = extras.blend;
	    this.stencil = extras.stencil;
    }

    // methods
    GLOWCompiledData.prototype.clone = function( except ) {
    	var clone = new GLOW.CompiledData();
    	except = except !== undefined ? except : {};

    	var u;
    	for( u in this.uniforms ) {
    		if( except[ u ] ) {
    			clone.uniforms[ u ] = new GLOW.Uniform( this.uniforms[ u ], except[ u ] );
				if( clone.uniforms[ u ].type === GL.SAMPLER_2D ||
				    clone.uniforms[ u ].type === GL.SAMPLER_CUBE ) {
					clone.uniforms[ u ].data.init();
					clone.uniforms[ u ].textureUnit = this.uniforms[ u ].textureUnit;
				}
    		} else {
    			clone.uniforms[ u ] = this.uniforms[ u ];
    		}
    	}

    	var a;
    	for( a in this.attributes ) {
    		if( except[ a ] ) {
    			clone.attributes[ a ] = new GLOW.Attribute( this.attributes[ a ], except[ a ] );
    		} else {
    			clone.attributes[ a ] = this.attributes[ a ];
    		}
    	}

    	var i;
    	for( i in this.interleavedAttributes ) {
    	    if( except[ i ] ) {
// This really needs some cleaning up... somehow.
//    	        clone.interleavedAttributes[ i ] = new GLOW.InterleavedAttributes( except[ i ] );
    	        clone.interleavedAttributes[ i ] = except[ i ];
    	    } else {
    	        clone.interleavedAttributes[ i ] = this.interleavedAttributes[ i ];
    	    }
    	}

    	if( except.indices ) {
    		clone.elements = new GLOW.Elements( except.indices, except.primitives );
    	} else {
    		clone.elements = this.elements;
    	}

        if( except.program ) {
        	clone.program = except.program;
        } else {
        	clone.program = this.program;
        }

        if( except.blend ) {
            clone.blend = except.blend;
        } else {
            clone.blend = this.blend;
        }

        if( except.stencil ) {
            clone.stencil = except.stencil;
        } else {
            clone.stencil = this.stencil;
        }

        if( except.preDrawCallback ) {
            clone.preDrawCallback = except.preDrawCallback;
        } else {
            clone.preDrawCallback = this.preDrawCallback;
        }

        if( except.postDrawCallback ) {
            clone.postDrawCallback = except.postDrawCallback;
        } else {
            clone.postDrawCallback = this.postDrawCallback;
        }

    	return clone;
    };

    return GLOWCompiledData;
})();

<|endoftext|>


<commit_before>import clone from 'simput/src/core/clone';

import LocalizedData from 'simput/src/core/LocalizedData';
import HookManager from 'simput/src/core/HookManager';

const ALWAYS_SHOW = () => true;

const UI_KEYS = [
  'id',
  'size',
  'layout',
  'domain',
  'default',
  'type',
  'label',
  'propType',
];

// ----------------------------------------------------------------------------
// ModelManager
// ----------------------------------------------------------------------------

export default class ModelManager {
  constructor(module, simputModel) {
    this.module = module;
    this.model = module.model;

    this.hideViews = simputModel.hideViews || [];
    // used in hooks
    this.fullData = simputModel;
    // convenient access to fullData.data
    this.data = simputModel.data || {};
    this.external = simputModel.external || {};
    this.data = simputModel.data;
    this.external = simputModel.external;
    this.type = simputModel.type;

    this.localizedData = new LocalizedData(module);

    this.cache = {
      ui: {},
      show: {},
      parameter: {},
    };

    this.activeViewName = null;
    this.activeViewIndex = 0;
    this.collapseState = {};
  }

  // -------- Public methods ------------

  getOutput() {
    const fullModel = {
      data: this.data,
      hideViews: this.hideViews,
      external: this.external,
      type: this.type,
    };
    const result = this.module.convert(fullModel);
    if (result.errors) {
      // FIXME expose errors to UI
      console.error(result.errors);
    }
    return result;
  }

  // --------

  activateView(name, index = 0) {
    this.activeViewName = name;
    this.activeViewIndex = index;
  }

  // --------

  isCollapsed(viewName, viewIndex) {
    const key = `${viewName}::${viewIndex}`;
    return this.collapseState[key];
  }

  // --------

  toggleCollapse(viewName, viewIndex) {
    const key = `${viewName}::${viewIndex}`;
    this.collapseState[key] = !this.collapseState[key];
  }

  // --------

  addView(viewName) {
    const viewList = this.data[viewName] || [];
    const index = viewList.length;
    const name = `${this.localizedData.getView(viewName)} ${index}`;

    viewList.push({ name });
    this.data = Object.assign({}, this.data, { [viewName]: viewList });
    this.activateView(viewName, index);
  }

  // --------

  deleteView(viewName, index) {
    this.data[viewName].splice(index, 1);

    if (this.activeViewName === viewName) {
      if (this.data[viewName].length) {
        const newIndex = this.activeViewIndex === index ? index - 1 : index;
        this.activateView(viewName, newIndex);
      } else {
        this.activateView(null, -1);
      }
    } else {
      this.activateView(viewName, -1);
    }
  }

  // --------

  getActiveViewData() {
    if (this.activeViewName && this.activeViewIndex > -1) {
      return this.data[this.activeViewName][this.activeViewIndex];
    }
    return {};
  }

  // --------

  updateViewData(newData) {
    if (this.activeViewName && this.activeViewIndex > -1) {
      const viewData = this.data[this.activeViewName][this.activeViewIndex];
      const keypath = newData.id.split('.');
      const attrName = keypath.shift();
      viewData[attrName][keypath.join('.')] = Object.assign(
        {},
        viewData[attrName][keypath.join('.')],
        { value: newData.value }
      );

      const hooks = this.model.views[this.activeViewName].hooks || [];
      hooks.forEach((hook) =>
        HookManager.applyHook(hook, this.fullData, viewData, this.model)
      );
    }
  }

  // --------

  getPropertyList() {
    const propertyList = [];

    if (!this.activeViewName || this.activeViewIndex === -1) {
      return propertyList;
    }

    // Create containers if missing
    if (!this.data[this.activeViewName]) {
      this.data[this.activeViewName] = [];
    }
    if (!this.data[this.activeViewName][this.activeViewIndex]) {
      this.data[this.activeViewName][this.activeViewIndex] = {};
    }

    const viewData = this.data[this.activeViewName][this.activeViewIndex];
    const viewAttrs = this.model.views[this.activeViewName].attributes || [];

    // FIXME should add attribute separator + or management
    viewAttrs.forEach((attrName) => {
      const attr = {
        title: this.localizedData.getAttribute(attrName),
      };
      const contents = [];
      const attrChildren = [];
      this.model.definitions[attrName].parameters.forEach((paramAttr, idx) => {
        if (Array.isArray(paramAttr)) {
          // OR prop
          paramAttr.forEach((orAttr) => {
            const keepTitle =
              this.model.definitions[attrName].parameters.length - 1 === idx;
            const orCtx = {
              show: this.getShowFunction(attrName, orAttr),
              contents,
              title: this.localizedData.getAttribute(orAttr),
            };
            if (keepTitle) {
              orCtx.contents = [];
            }
            this.model.definitions[orAttr].parameters.forEach((param) => {
              const prop = this.getParameterData(orAttr, param.id);
              prop.viewData = viewData;
              if (this.model.definitions[attrName].children[orAttr]) {
                prop.show = orCtx.show;
              }
              orCtx.contents.push(prop);
            });

            if (keepTitle) {
              attrChildren.push(orCtx);
            }
          });
        } else {
          const prop = this.getParameterData(attrName, paramAttr.id);
          prop.viewData = viewData;
          if (paramAttr.show) {
            prop.show = this.getShowParamFunction(attrName, paramAttr.show);
          }
          contents.push(prop);
        }
      });
      attr.contents = contents;
      propertyList.push(attr);

      for (let i = 0; i < attrChildren.length; i++) {
        propertyList.push(attrChildren[i]);
      }
    });

    return propertyList;
  }

  // --------

  getMenuList() {
    const menu = [];
    this.model.order.forEach((id) => {
      if (this.hideViews.indexOf(id) === -1) {
        const node = {
          id,
          label: this.localizedData.getView(id),
          bullet: this.isCollapsed(id, 0)
            ? 'collapsedBullet'
            : 'expendedBullet',
          index: 0,
        };
        const { size, children } = this.model.views[id];
        const childCount = this.data[id] && this.data[id].length;

        // Flags
        node.addChildButton = size === -1;
        node.active =
          this.activeViewName === id && childCount < 2 && !node.addChildButton;

        // Children
        const nodeChildren = [];
        if (node.addChildButton) {
          if (!(id in this.data)) {
            this.addView(id);
          }
          this.data[id].forEach((viewItem, idx) => {
            const child = {
              id,
              label: viewItem.name || node.label,
              bullet: this.isCollapsed(id, idx)
                ? 'collapsedBullet'
                : 'expendedBullet',
              index: idx,
              active:
                this.activeViewName === id && this.activeViewIndex === idx,
              noDelete: this.model.views[id].noDelete || viewItem.noDelete,
              readOnly: this.model.views[id].readOnly || viewItem.readOnly,
            };
            nodeChildren.push(child);
          });
        } else if (children) {
          children.forEach((subViewId, idx) => {
            const viewItem = this.data[subViewId][id] || {};
            const child = {
              id: subViewId,
              label: viewItem.name || node.label,
              bullet: this.isCollapsed(subViewId, idx)
                ? 'collapsedBullet'
                : 'expendedBullet',
              index: idx,
              active:
                this.activeViewName === subViewId &&
                this.activeViewIndex === idx,
              noDelete: true,
              readOnly: true,
            };
            nodeChildren.push(child);
          });
        }

        // Add children if any
        if (nodeChildren.length) {
          node.children = nodeChildren;
        }

        menu.push(node);
      }
    });
    return menu;
  }

  // -------- Internal methods ------------

  getParameter(attributeName, parameterId) {
    if (!this.cache.parameter[attributeName]) {
      this.cache.parameter[attributeName] = {};
    }
    if (!this.cache.parameter[attributeName][parameterId]) {
      this.model.definitions[attributeName].parameters.forEach((param) => {
        this.cache.parameter[attributeName][param.id] = param;
      });
    }
    return this.cache.parameter[attributeName][parameterId];
  }

  // --------

  getNextViewId() {
    let nextId = 0;
    const viewNames = Object.keys(this.data);
    while (viewNames.length) {
      const views = this.data[viewNames.pop()];
      for (let i = 0; i < views.length; i++) {
        if (views[i].id) {
          nextId = Math.max(nextId, views[i].id);
        }
      }
    }
    return nextId + 1;
  }

  // --------

  generateUI(attributeName, parameter) {
    const ui = {
      componentLabels: [],
    };

    // Clone parameters into UI
    Object.keys(parameter).forEach((key) => {
      if (UI_KEYS.indexOf(key) !== -1) {
        ui[key] = clone(parameter[key]);
      }
    });

    // Generate domain from external fields
    if (parameter.domain && parameter.domain.external) {
      if (Array.isArray(parameter.domain.external)) {
        ui.domain = {};
        for (let i = 0; i < parameter.domain.external.length; i++) {
          const key = parameter.domain.external[i];
          if (this.external && this.external[key]) {
            ui.domain[key] = this.external[key];
          } else if (this.module.external && this.module.external[key]) {
            ui.domain[key] = this.module.external[key];
          }
        }
      } else {
        const externalKey = parameter.domain.external;
        delete ui.domain.external;
        if (this.external && this.external[externalKey]) {
          ui.domain = this.external[externalKey];
        } else if (this.module.external && this.module.external[externalKey]) {
          ui.domain = this.module.external[externalKey];
        } else {
          console.log(
            `no domain found for external (${externalKey})`,
            this.external,
            this.module.external
          );
          ui.domain = {
            [`${externalKey} not found`]: `${externalKey}-not-found`,
          };
        }
      }
    }

    if (parameter.domain && parameter.domain.dynamic) {
      ui.dynamic = true;
    }

    // Widget selection
    ui.propType = parameter.ui || parameter.propType || 'TextField';

    // Label
    ui.label = parameter.label || parameter.name;
    if (
      this.localizedData &&
      this.localizedData.getParameter(attributeName, parameter.id)
    ) {
      ui.label = this.localizedData.getParameter(attributeName, parameter.id);
    }

    // Help
    ui.help = parameter.help;
    if (
      this.localizedData &&
      this.localizedData.getHelp(attributeName, parameter.id)
    ) {
      ui.help = this.localizedData.getHelp(attributeName, parameter.id);
    }

    return ui;
  }

  // --------

  getUI(attributeName, parameterId) {
    if (!this.cache.ui[attributeName]) {
      this.cache.ui[attributeName] = {};
    }
    if (!this.cache.ui[attributeName][parameterId]) {
      const newUI = this.generateUI(
        attributeName,
        this.getParameter(attributeName, parameterId)
      );
      if (!newUI.dynamic) {
        this.cache.ui[attributeName][parameterId] = newUI;
      }
      return newUI;
    }
    return this.cache.ui[attributeName][parameterId];
  }

  // --------

  getData(attributeName, parameterId) {
    if (!this.activeViewName) {
      console.error('No active view');
      return {}; // FIXME
    }

    // Create active view container if not yet available
    if (!this.data[this.activeViewName]) {
      this.data[this.activeViewName] = [];
    }
    while (this.data[this.activeViewName].length <= this.activeViewIndex) {
      this.data[this.activeViewName].push({});
    }
    const containerData = this.data[this.activeViewName][this.activeViewIndex];

    // Fill attribute
    if (!containerData[attributeName]) {
      containerData[attributeName] = {};
    }

    if (!containerData[attributeName][parameterId]) {
      const paramDef = this.getParameter(attributeName, parameterId);
      if (paramDef.default !== undefined) {
        containerData[attributeName][parameterId] = {
          id: `${attributeName}.${parameterId}`,
          value: [].concat(clone(paramDef.default)),
        };
      } else if (paramDef.domain && paramDef.domain.external) {
        // external param (no default)
        let externalObj;

        if (this.external && this.external[paramDef.domain.external]) {
          externalObj = this.external[paramDef.domain.external];
        } else if (
          this.module.external &&
          this.module.external[paramDef.domain.external]
        ) {
          externalObj = this.module.external[paramDef.domain.external];
        } else {
          externalObj = {
            [`${paramDef.domain.external} not found`]: `${
              paramDef.domain.external
            }-not-found`,
          };
        }

        const val = externalObj[Object.keys(externalObj)[0]];

        containerData[attributeName][parameterId] = {
          id: `${attributeName}.${parameterId}`,
          value: [].concat(clone(val)),
        };
      } else if (paramDef.domain) {
        // const val = paramDef.domain[Object.keys(paramDef.domain)[0]];
        containerData[attributeName][parameterId] = {
          id: `${attributeName}.${parameterId}`,
          value: [].concat(clone(null)),
        };
      } else {
        containerData[attributeName][parameterId] = {
          id: `${attributeName}.${parameterId}`,
          value: [],
        };
      }
    }

    return containerData[attributeName][parameterId];
  }

  // --------

  getParameterData(attributeName, parameterId) {
    return {
      ui: this.getUI(attributeName, parameterId),
      data: this.getData(attributeName, parameterId),
      show: ALWAYS_SHOW,
    };
  }

  // --------

  getShowFunction(attributeName, orAttribute) {
    if (!this.cache.show[attributeName]) {
      this.cache.show[attributeName] = {};
    }

    if (!this.cache.show[attributeName][orAttribute]) {
      const attrList = [attributeName].concat(
        Object.keys(this.model.definitions[attributeName].children)
      );
      const boolExpr = this.model.definitions[attributeName].children[
        orAttribute
      ];
      const funcTemplate = [
        `
        var global = {};
        for (var attr in viewData) {
             global[attr] = {};
             for (var key in viewData[attr]) {
                 global[attr][key] = viewData[attr][key].value;
             }
        }
      `,
      ];

      attrList.forEach((attr) => {
        funcTemplate.push(
          `var ${attr.replace(/-|\./g, '')} = global['${attr}'];`
        ); // `attr1 = global.attr1,`
      });
      funcTemplate.push(`return ${boolExpr.replace(/ /g, '')};`);

      /* eslint-disable no-new-func */
      this.cache.show[attributeName][orAttribute] = new Function(
        'viewData',
        funcTemplate.join('\n')
      );
      /* eslint-enable no-new-function */
    }

    return this.cache.show[attributeName][orAttribute];
  }

  // --------

  getShowParamFunction(attributeName, boolExpr) {
    const funcTemplate = [];
    this.model.definitions[attributeName].parameters.forEach((param) => {
      if (!Array.isArray(param)) {
        funcTemplate.push(
          `var ${param.id.replace(
            /-|\./g,
            ''
          )} = viewData['${attributeName}']['${param.id}'].value`
        );
      }
    });

    funcTemplate.push(`return ${boolExpr};`);
    return new Function('viewData', funcTemplate.join('\n'));
  }
}
<commit_msg>fix(ModelManager): Add notification infrastructure<commit_after>import clone from 'simput/src/core/clone';

import LocalizedData from 'simput/src/core/LocalizedData';
import HookManager from 'simput/src/core/HookManager';

const ALWAYS_SHOW = () => true;

const UI_KEYS = [
  'id',
  'size',
  'layout',
  'domain',
  'default',
  'type',
  'label',
  'propType',
];

// ----------------------------------------------------------------------------
// ModelManager
// ----------------------------------------------------------------------------

export default class ModelManager {
  constructor(module, simputModel) {
    this.module = module;
    this.model = module.model;

    this.hideViews = simputModel.hideViews || [];
    // used in hooks
    this.fullData = simputModel;
    // convenient access to fullData.data
    this.data = simputModel.data || {};
    this.external = simputModel.external || {};
    this.data = simputModel.data;
    this.external = simputModel.external;
    this.type = simputModel.type;

    this.localizedData = new LocalizedData(module);

    this.cache = {
      ui: {},
      show: {},
      parameter: {},
    };

    this.activeViewName = null;
    this.activeViewIndex = 0;
    this.collapseState = {};
    this.listeners = [];
  }

  // -------- Public methods ------------

  subscribe(callback) {
    const id = this.listeners.length;
    this.listeners.push(callback);
    const unsubscribe = () => {
      this.listeners[id] = null;
      while (
        this.listeners.length &&
        this.listeners[this.listeners.length - 1]
      ) {
        this.listeners.pop();
      }
    };
    const run = () => {
      if (this.listeners[id]) {
        this.listeners[id]();
      }
    };
    return {
      unsubscribe,
      run,
    };
  }

  // --------

  getOutput() {
    const fullModel = {
      data: this.data,
      hideViews: this.hideViews,
      external: this.external,
      type: this.type,
    };
    const result = this.module.convert(fullModel);
    if (result.errors) {
      // FIXME expose errors to UI
      console.error(result.errors);
    }
    return result;
  }

  // --------

  activateView(name, index = 0) {
    this.activeViewName = name;
    this.activeViewIndex = index;
  }

  // --------

  isCollapsed(viewName, viewIndex) {
    const key = `${viewName}::${viewIndex}`;
    return this.collapseState[key];
  }

  // --------

  toggleCollapse(viewName, viewIndex) {
    const key = `${viewName}::${viewIndex}`;
    this.collapseState[key] = !this.collapseState[key];
  }

  // --------

  addView(viewName) {
    const viewList = this.data[viewName] || [];
    const index = viewList.length;
    const name = `${this.localizedData.getView(viewName)} ${index}`;

    viewList.push({ name });
    this.data = Object.assign({}, this.data, { [viewName]: viewList });
    this.activateView(viewName, index);
  }

  // --------

  deleteView(viewName, index) {
    this.data[viewName].splice(index, 1);

    if (this.activeViewName === viewName) {
      if (this.data[viewName].length) {
        const newIndex = this.activeViewIndex === index ? index - 1 : index;
        this.activateView(viewName, newIndex);
      } else {
        this.activateView(null, -1);
      }
    } else {
      this.activateView(viewName, -1);
    }
  }

  // --------

  getActiveViewData() {
    if (this.activeViewName && this.activeViewIndex > -1) {
      return this.data[this.activeViewName][this.activeViewIndex];
    }
    return {};
  }

  // --------

  updateViewData(newData) {
    if (this.activeViewName && this.activeViewIndex > -1) {
      const viewData = this.data[this.activeViewName][this.activeViewIndex];
      const keypath = newData.id.split('.');
      const attrName = keypath.shift();
      viewData[attrName][keypath.join('.')] = Object.assign(
        {},
        viewData[attrName][keypath.join('.')],
        { value: newData.value }
      );

      const hooks = this.model.views[this.activeViewName].hooks || [];
      hooks.forEach((hook) =>
        HookManager.applyHook(hook, this.fullData, viewData, this.model)
      );

      this.invokeListeners();
    }
  }

  // --------

  getPropertyList() {
    const propertyList = [];

    if (!this.activeViewName || this.activeViewIndex === -1) {
      return propertyList;
    }

    // Create containers if missing
    if (!this.data[this.activeViewName]) {
      this.data[this.activeViewName] = [];
    }
    if (!this.data[this.activeViewName][this.activeViewIndex]) {
      this.data[this.activeViewName][this.activeViewIndex] = {};
    }

    const viewData = this.data[this.activeViewName][this.activeViewIndex];
    const viewAttrs = this.model.views[this.activeViewName].attributes || [];

    // FIXME should add attribute separator + or management
    viewAttrs.forEach((attrName) => {
      const attr = {
        title: this.localizedData.getAttribute(attrName),
      };
      const contents = [];
      const attrChildren = [];
      this.model.definitions[attrName].parameters.forEach((paramAttr, idx) => {
        if (Array.isArray(paramAttr)) {
          // OR prop
          paramAttr.forEach((orAttr) => {
            const keepTitle =
              this.model.definitions[attrName].parameters.length - 1 === idx;
            const orCtx = {
              show: this.getShowFunction(attrName, orAttr),
              contents,
              title: this.localizedData.getAttribute(orAttr),
            };
            if (keepTitle) {
              orCtx.contents = [];
            }
            this.model.definitions[orAttr].parameters.forEach((param) => {
              const prop = this.getParameterData(orAttr, param.id);
              prop.viewData = viewData;
              if (this.model.definitions[attrName].children[orAttr]) {
                prop.show = orCtx.show;
              }
              orCtx.contents.push(prop);
            });

            if (keepTitle) {
              attrChildren.push(orCtx);
            }
          });
        } else {
          const prop = this.getParameterData(attrName, paramAttr.id);
          prop.viewData = viewData;
          if (paramAttr.show) {
            prop.show = this.getShowParamFunction(attrName, paramAttr.show);
          }
          contents.push(prop);
        }
      });
      attr.contents = contents;
      propertyList.push(attr);

      for (let i = 0; i < attrChildren.length; i++) {
        propertyList.push(attrChildren[i]);
      }
    });

    return propertyList;
  }

  // --------

  getMenuList() {
    const menu = [];
    this.model.order.forEach((id) => {
      if (this.hideViews.indexOf(id) === -1) {
        const node = {
          id,
          label: this.localizedData.getView(id),
          bullet: this.isCollapsed(id, 0)
            ? 'collapsedBullet'
            : 'expendedBullet',
          index: 0,
        };
        const { size, children } = this.model.views[id];
        const childCount = this.data[id] && this.data[id].length;

        // Flags
        node.addChildButton = size === -1;
        node.active =
          this.activeViewName === id && childCount < 2 && !node.addChildButton;

        // Children
        const nodeChildren = [];
        if (node.addChildButton) {
          if (!(id in this.data)) {
            this.addView(id);
          }
          this.data[id].forEach((viewItem, idx) => {
            const child = {
              id,
              label: viewItem.name || node.label,
              bullet: this.isCollapsed(id, idx)
                ? 'collapsedBullet'
                : 'expendedBullet',
              index: idx,
              active:
                this.activeViewName === id && this.activeViewIndex === idx,
              noDelete: this.model.views[id].noDelete || viewItem.noDelete,
              readOnly: this.model.views[id].readOnly || viewItem.readOnly,
            };
            nodeChildren.push(child);
          });
        } else if (children) {
          children.forEach((subViewId, idx) => {
            const viewItem = this.data[subViewId][id] || {};
            const child = {
              id: subViewId,
              label: viewItem.name || node.label,
              bullet: this.isCollapsed(subViewId, idx)
                ? 'collapsedBullet'
                : 'expendedBullet',
              index: idx,
              active:
                this.activeViewName === subViewId &&
                this.activeViewIndex === idx,
              noDelete: true,
              readOnly: true,
            };
            nodeChildren.push(child);
          });
        }

        // Add children if any
        if (nodeChildren.length) {
          node.children = nodeChildren;
        }

        menu.push(node);
      }
    });
    return menu;
  }

  // -------- Internal methods ------------

  invokeListeners() {
    for (let i = 0; i < this.listeners.length; i++) {
      if (this.listeners[i]) {
        this.listeners[i]();
      }
    }
  }

  // --------

  getParameter(attributeName, parameterId) {
    if (!this.cache.parameter[attributeName]) {
      this.cache.parameter[attributeName] = {};
    }
    if (!this.cache.parameter[attributeName][parameterId]) {
      this.model.definitions[attributeName].parameters.forEach((param) => {
        this.cache.parameter[attributeName][param.id] = param;
      });
    }
    return this.cache.parameter[attributeName][parameterId];
  }

  // --------

  getNextViewId() {
    let nextId = 0;
    const viewNames = Object.keys(this.data);
    while (viewNames.length) {
      const views = this.data[viewNames.pop()];
      for (let i = 0; i < views.length; i++) {
        if (views[i].id) {
          nextId = Math.max(nextId, views[i].id);
        }
      }
    }
    return nextId + 1;
  }

  // --------

  generateUI(attributeName, parameter) {
    const ui = {
      componentLabels: [],
    };

    // Clone parameters into UI
    Object.keys(parameter).forEach((key) => {
      if (UI_KEYS.indexOf(key) !== -1) {
        ui[key] = clone(parameter[key]);
      }
    });

    // Generate domain from external fields
    if (parameter.domain && parameter.domain.external) {
      if (Array.isArray(parameter.domain.external)) {
        ui.domain = {};
        for (let i = 0; i < parameter.domain.external.length; i++) {
          const key = parameter.domain.external[i];
          if (this.external && this.external[key]) {
            ui.domain[key] = this.external[key];
          } else if (this.module.external && this.module.external[key]) {
            ui.domain[key] = this.module.external[key];
          }
        }
      } else {
        const externalKey = parameter.domain.external;
        delete ui.domain.external;
        if (this.external && this.external[externalKey]) {
          ui.domain = this.external[externalKey];
        } else if (this.module.external && this.module.external[externalKey]) {
          ui.domain = this.module.external[externalKey];
        } else {
          console.log(
            `no domain found for external (${externalKey})`,
            this.external,
            this.module.external
          );
          ui.domain = {
            [`${externalKey} not found`]: `${externalKey}-not-found`,
          };
        }
      }
    }

    if (parameter.domain && parameter.domain.dynamic) {
      ui.dynamic = true;
    }

    // Widget selection
    ui.propType = parameter.ui || parameter.propType || 'TextField';

    // Label
    ui.label = parameter.label || parameter.name;
    if (
      this.localizedData &&
      this.localizedData.getParameter(attributeName, parameter.id)
    ) {
      ui.label = this.localizedData.getParameter(attributeName, parameter.id);
    }

    // Help
    ui.help = parameter.help;
    if (
      this.localizedData &&
      this.localizedData.getHelp(attributeName, parameter.id)
    ) {
      ui.help = this.localizedData.getHelp(attributeName, parameter.id);
    }

    return ui;
  }

  // --------

  getUI(attributeName, parameterId) {
    if (!this.cache.ui[attributeName]) {
      this.cache.ui[attributeName] = {};
    }
    if (!this.cache.ui[attributeName][parameterId]) {
      const newUI = this.generateUI(
        attributeName,
        this.getParameter(attributeName, parameterId)
      );
      if (!newUI.dynamic) {
        this.cache.ui[attributeName][parameterId] = newUI;
      }
      return newUI;
    }
    return this.cache.ui[attributeName][parameterId];
  }

  // --------

  getData(attributeName, parameterId) {
    if (!this.activeViewName) {
      console.error('No active view');
      return {}; // FIXME
    }

    // Create active view container if not yet available
    if (!this.data[this.activeViewName]) {
      this.data[this.activeViewName] = [];
    }
    while (this.data[this.activeViewName].length <= this.activeViewIndex) {
      this.data[this.activeViewName].push({});
    }
    const containerData = this.data[this.activeViewName][this.activeViewIndex];

    // Fill attribute
    if (!containerData[attributeName]) {
      containerData[attributeName] = {};
    }

    if (!containerData[attributeName][parameterId]) {
      const paramDef = this.getParameter(attributeName, parameterId);
      if (paramDef.default !== undefined) {
        containerData[attributeName][parameterId] = {
          id: `${attributeName}.${parameterId}`,
          value: [].concat(clone(paramDef.default)),
        };
      } else if (paramDef.domain && paramDef.domain.external) {
        // external param (no default)
        let externalObj;

        if (this.external && this.external[paramDef.domain.external]) {
          externalObj = this.external[paramDef.domain.external];
        } else if (
          this.module.external &&
          this.module.external[paramDef.domain.external]
        ) {
          externalObj = this.module.external[paramDef.domain.external];
        } else {
          externalObj = {
            [`${paramDef.domain.external} not found`]: `${
              paramDef.domain.external
            }-not-found`,
          };
        }

        const val = externalObj[Object.keys(externalObj)[0]];

        containerData[attributeName][parameterId] = {
          id: `${attributeName}.${parameterId}`,
          value: [].concat(clone(val)),
        };
      } else if (paramDef.domain) {
        // const val = paramDef.domain[Object.keys(paramDef.domain)[0]];
        containerData[attributeName][parameterId] = {
          id: `${attributeName}.${parameterId}`,
          value: [].concat(clone(null)),
        };
      } else {
        containerData[attributeName][parameterId] = {
          id: `${attributeName}.${parameterId}`,
          value: [],
        };
      }
    }

    return containerData[attributeName][parameterId];
  }

  // --------

  getParameterData(attributeName, parameterId) {
    return {
      ui: this.getUI(attributeName, parameterId),
      data: this.getData(attributeName, parameterId),
      show: ALWAYS_SHOW,
    };
  }

  // --------

  getShowFunction(attributeName, orAttribute) {
    if (!this.cache.show[attributeName]) {
      this.cache.show[attributeName] = {};
    }

    if (!this.cache.show[attributeName][orAttribute]) {
      const attrList = [attributeName].concat(
        Object.keys(this.model.definitions[attributeName].children)
      );
      const boolExpr = this.model.definitions[attributeName].children[
        orAttribute
      ];
      const funcTemplate = [
        `
        var global = {};
        for (var attr in viewData) {
             global[attr] = {};
             for (var key in viewData[attr]) {
                 global[attr][key] = viewData[attr][key].value;
             }
        }
      `,
      ];

      attrList.forEach((attr) => {
        funcTemplate.push(
          `var ${attr.replace(/-|\./g, '')} = global['${attr}'];`
        ); // `attr1 = global.attr1,`
      });
      funcTemplate.push(`return ${boolExpr.replace(/ /g, '')};`);

      /* eslint-disable no-new-func */
      this.cache.show[attributeName][orAttribute] = new Function(
        'viewData',
        funcTemplate.join('\n')
      );
      /* eslint-enable no-new-function */
    }

    return this.cache.show[attributeName][orAttribute];
  }

  // --------

  getShowParamFunction(attributeName, boolExpr) {
    const funcTemplate = [];
    this.model.definitions[attributeName].parameters.forEach((param) => {
      if (!Array.isArray(param)) {
        funcTemplate.push(
          `var ${param.id.replace(
            /-|\./g,
            ''
          )} = viewData['${attributeName}']['${param.id}'].value`
        );
      }
    });

    funcTemplate.push(`return ${boolExpr};`);
    return new Function('viewData', funcTemplate.join('\n'));
  }
}
<|endoftext|>


<commit_before>var _ = require('underscore'),
    select = require('cheerio-select'),
    utils = require('../utils'),
    isTag = utils.isTag;

var find = exports.find = function(selector) {
  try {
    var elem = select(selector, [].slice.call(this.children()));
    return this.make(elem);
  } catch(e) {
    return this.make([]);
  }
};

var parent = exports.parent = function(elem) {
  if (this[0] && this[0].parent)
    return this.make(this[0].parent);
  else
    return this;
};

var next = exports.next = function() {
  var elem = this[0];
  while ((elem = elem.next)) if (isTag(elem)) return this.make(elem);
  return this.make([]);
};

var prev = exports.prev = function() {
  var elem = this[0];
  while ((elem = elem.prev)) if (isTag(elem)) return this.make(elem);
  return this.make([]);
};

var siblings = exports.siblings = function(elem) {
  return this.make(_.filter(
    this.parent() ? this.parent().children() : this.siblingsAndMe(),
    function(elem) { return isTag(elem) && elem !== this[0]; },
    this
  ));
};

var children = exports.children = function(selector) {

  var elems = _.reduce(this, function(memo, elem) {
    return memo.concat(_.filter(elem.children, isTag));
  }, []);

  if (selector === undefined) return this.make(elems);
  else if (_.isNumber(selector)) return this.make(elems[selector]);

  return this.make(elems).filter(selector);
};

var each = exports.each = function(fn) {
  var length = this.length,
      el, i;

  for (i = 0; i < length; ++i) {
    el = this[i];
    if (fn.call(this.make(el), i, el) === false) {
      break;
    }
  }

  return this;
};

var map = exports.map = function(fn) {
  return _.map(this, function(el, i) {
    return fn.call(this.make(el), i, el);
  }, this);
};

var filter = exports.filter = function(match) {
  var make = _.bind(this.make, this);
  return make(_.filter(this, _.isString(match) ?
    function(el) { return select(match, el)[0] === el; }
  : function(el, i) { return match.call(make(el), i, el); }
  ));
};

var first = exports.first = function() {
  return this[0] ? this.make(this[0]) : this;
};

var last = exports.last = function() {
  return this[0] ? this.make(this[this.length - 1]) : this;
};

// Reduce the set of matched elements to the one at the specified index.
var eq = exports.eq = function(i) {
  i = +i;
  if (i < 0) i = this.length + i;
  return this[i] ? this.make(this[i]) : this.make([]);
};

var slice = exports.slice = function() {
  return this.make([].slice.apply(this, arguments));
};
<commit_msg>simplify each<commit_after>var _ = require('underscore'),
    select = require('cheerio-select'),
    utils = require('../utils'),
    isTag = utils.isTag;

var find = exports.find = function(selector) {
  try {
    var elem = select(selector, [].slice.call(this.children()));
    return this.make(elem);
  } catch(e) {
    return this.make([]);
  }
};

var parent = exports.parent = function(elem) {
  if (this[0] && this[0].parent)
    return this.make(this[0].parent);
  else
    return this;
};

var next = exports.next = function() {
  var elem = this[0];
  while ((elem = elem.next)) if (isTag(elem)) return this.make(elem);
  return this.make([]);
};

var prev = exports.prev = function() {
  var elem = this[0];
  while ((elem = elem.prev)) if (isTag(elem)) return this.make(elem);
  return this.make([]);
};

var siblings = exports.siblings = function(elem) {
  return this.make(_.filter(
    this.parent() ? this.parent().children() : this.siblingsAndMe(),
    function(elem) { return isTag(elem) && elem !== this[0]; },
    this
  ));
};

var children = exports.children = function(selector) {

  var elems = _.reduce(this, function(memo, elem) {
    return memo.concat(_.filter(elem.children, isTag));
  }, []);

  if (selector === undefined) return this.make(elems);
  else if (_.isNumber(selector)) return this.make(elems[selector]);

  return this.make(elems).filter(selector);
};

var each = exports.each = function(fn) {
  var i = 0, len = this.length;
  while (i < len && fn.call(this.make(this[i]), i, this[i]) !== false) ++i;
  return this;
};

var map = exports.map = function(fn) {
  return _.map(this, function(el, i) {
    return fn.call(this.make(el), i, el);
  }, this);
};

var filter = exports.filter = function(match) {
  var make = _.bind(this.make, this);
  return make(_.filter(this, _.isString(match) ?
    function(el) { return select(match, el)[0] === el; }
  : function(el, i) { return match.call(make(el), i, el); }
  ));
};

var first = exports.first = function() {
  return this[0] ? this.make(this[0]) : this;
};

var last = exports.last = function() {
  return this[0] ? this.make(this[this.length - 1]) : this;
};

// Reduce the set of matched elements to the one at the specified index.
var eq = exports.eq = function(i) {
  i = +i;
  if (i < 0) i = this.length + i;
  return this[i] ? this.make(this[i]) : this.make([]);
};

var slice = exports.slice = function() {
  return this.make([].slice.apply(this, arguments));
};
<|endoftext|>


<commit_before>var express       = require('express'),
    Error404      = require('../errors').Error404,
    path          = require('path'),
    mkdirp        = require('mkdirp'),
    fs            = require('fs'),
    requireUser   = require('../middleware/route').requireUser,
    _             = require('underscore'),
    async         = require('async'),
    csv           = require('csv');

module.exports = function () {

  var app = express.createServer();

  app.schemas = {'person': {
      name: 'Person',
      model: 'Person',
      options: {
        name: {
          title: 'Name, birthday & similar',
          strict: true,
          suggestions: ['Full name', 'Title', 'First name', 'Last name', 'Middle name', 'Birthday', 'Nickname']
        },
        summary: {
          title: 'Summary',
          strict: true,
          suggestions: ['Summary']
        },
        position: {
          title: 'Position'
        },
        contact: {
          title: 'Contact Information',
          strict: false,
          suggestions: ['Address', 'Phone', 'Fax', 'Email', 'Skype']
        },
        links: {
          title: 'Link',
          strict: false,
          suggestions: ['Website', 'Wikipedia', 'Youtube', 'Twitter', 'Freebase' ,'RSS']
        },
        id: {
          title: 'ID',
          strict: false,
          suggestions: ['Facebook', 'Twitter']
        },
        data: {
          title: 'Other data',
          strict: false,
          suggestions: ['University', 'School', 'Gender']
        }
      }
  }};

  /*
   * Parses a csv from path and calls the callback with the parsed data.
   */
  app.parseCsv = function(path, callback) {
    var parsedData = {};

    csv()
      .fromPath(path)
      .on('data',function(data, index){
        parsedData[index] = data;
        //console.log('#'+index+' '+JSON.stringify(data));
      })
    .on('end',function(count){
      //console.log('Number of lines: '+count);
      callback(parsedData, null);
    })
    .on('error',function(err){
      console.log(err.message);
      callback(parsedData, err);
    });
  }

  /*
   * Converts mapping+attributes into easily accessible dict.
   * Take a look at the tests for an example.
   */
  app.zipIt = function(mappings, attributes) {
    var inverted = {};
    _.each(_.zip(mappings, attributes), function(item) {
      if (!item[0][0] || !item[0][1] || !item[0][2]) {
        return;
      }
      if (!inverted[item[0][1]]) {
        inverted[item[0][1]] = {};
      }
      inverted[item[0][1]][item[0][2]] = item[1];
    });

    return inverted;
  }

  /*
   * Writes the data from a form into the database according to the provided mapping.
   */
  app.doImport = function(popit, schema, mappings, data, updateCb, functionCallback) {
    /*
    console.log('schema:', schema);
    console.log('mappings:', mappings);
    console.log('popit:', popit);
    console.log('data:', data);
    //*/

    var Person = popit.model('Person'),
      Link = popit.model('Link'),
      Position = popit.model('Position'),
      Organisation = popit.model('Organisation'),
      ContactDetail = popit.model('ContactDetail'),
      positions = [], people = [], organisations = [];

    // async.forEach does not support objects, data is an object, not an array
    async.forEachSeries(Object.keys(data), function(key, forEachCallback) {
      var person, useable, res = [], attributes, name = '';

      attributes = data[key];

      useable = app.zipIt(mappings, attributes);

      if (useable.name['Title']) {
        name += useable.name['Title']+' ';
      }

      // either full or first name
      if (useable.name['First name']) {
        name += useable.name['First name'];
      } else {
        name += useable.name['Full name'];
      }

      if (useable.name['Middle name']) {
        name += ' '+ useable.name['Middle name'];
      }
      if (useable.name['Last name']) {
        name += ' '+ useable.name['Last name'];
      }

      // sometimes there are multiline entries in csv's
      if (name == '') {
        if (people.length > 0) {
          person = people.pop();
        } else {
          forEachCallback("Cannot pop person for multiline csv.");
          return;
        }
      } else {
        // create new person
        person = new Person({'name': name, 'contact_details' : [], 'data': {}, 'links': []}, false);
        person.set("ids", []);
      }

      if (useable.name["Birthdate"])
        person.set('birthDate', useable.name["Birthdate"]);

      async.parallel([
        function(callback) {
          _.each(useable['summary'], function(value, key) {
            if (value) {
              person.set('summary', value);
            }
          });
          callback();
        },
        function(callback) {
          _.each(useable['contact'], function(value, key) {
            if (value) {
              var cd = new ContactDetail({kind: key, value: value});
              person.contact_details.push(cd);
            }
          });
          callback();
        },
        function(callback) {
          _.each(useable['links'], function(value, key) {
            if (value) {
              var l = new Link({comment: key, url: value});
              person.links.push(l);
            }
          });
          callback();
        },
        function(callback) {
          // simple key value/ key array pairs
          //var s = person.get("data") || {}
          var s = person.data
          _.each(useable['data'], function(value, key) {
            if (value) {
              if (s[key]) {
                s[key].push(value)
              } else {
                s[key] = [value];
              }
            }
          });
          //person.set('data', s);
          callback();
        },
        function(callback) {
          // ids may get a little bit more information in the future
          var ids = person.get("ids") || {}
          _.each(useable['id'], function(value, key) {
            var i = {provider: key, id: value};
            ids.push(i);
            });
          person.set('ids', ids);
          callback();
        },
        function(callback) {
          if (useable['position']) {
            async.forEach(Object.keys(useable['position']), function(key, cb) {
              var value = useable['position'][key];
              if (value) {
                Organisation.findOne({name: value}, function(err, doc) {
                  if (err) console.log( err );
                  else {
                    // find organisation in array to avoid problems when saving
                    doc = doc || _.find(organisations, function(e) {return e.name == value});
                    if (!doc) {
                      doc = new Organisation({name: value});
                      organisations.push(doc);
                    }
                    var pos = new Position({
                      title: key,
                      organisation: doc,
                      person: person,
                    });
                    positions.push(pos);
                  }
                  cb(err);
                });
              } else {
                callback();
              }
            }, function(err) {
              callback(err);
            });
          } else {
            callback();
          }
        }],
        function(err, results) {

          /*
          console.log('useable:', useable);
          console.log('positions:', positions);
          console.log('organisation:', organisations);
          console.log('person:', person);
          //*/

          people.push(person);

          forEachCallback(err); //forEach callback
        }); // end of parallel

    }, function(err) {
      if (err) {
        functionCallback(err);
      }

      //console.log(people)

      var toBeSaved = _.union(people, positions, organisations);
      var total = toBeSaved.length;
      var progress = 0;

      // save people and positions to database
      // TODO make parallel, take care of slugs
      async.forEachSeries(toBeSaved, function(item, callback) {
        item.save(function (err) {
          progress++;
          updateCb(progress, total);
          callback(err);
        });
      }, function(err) {
        functionCallback(err, people, positions, organisations);
      });

    }); //end of forEach
  }

  app.mounted(function(parent) {

    var app = this;

    /*
     * Shows the migration/ index page
     */
    var showMigrationPage = function(req, res) {
      var query = req.popit.model('Migration').find().sort('created');

      query.exec(function(err, docs) {
        if (err) throw err;

        res.local('migration', docs);
        res.render('migration');
      });
    }

    /*
     * Shows the status page while the migration is running
     */
    var showStatusPage = function(req, res) {
      res.render('migration/status');
    }

    /*
     * Show mapping page for a migration
     */
    var showMappingPage = function(req, res) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        //console.log(doc, req.params.id);
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          res.local('migration', doc);
          res.local('schemas', app.schemas);
          res.local('_', _);
          res.render('migration/mapping');
        }
      });
    }

    /*
     * Shows the migration/ index page
     */
    app.get('/', requireUser, function (req,res) {
      if( ! res.local('error') ) res.local('error', '');

      showMigrationPage(req, res);
    });

    /*
     * Deletes all migrations from the database.
     * This does not mean the imported data is lost, it just
     * means that the migration object is deleted.
     */
    app.get('/clear', requireUser, function (req,res) {
      var query = req.popit.model('Migration').find();

      query.exec(function(err, docs) {
        if (err) throw err;

        docs.forEach( function (doc) {
          doc.remove(function(err) {
            if (err) throw err;
          });
        });

        res.redirect('/migration');
      });
    });

    /*
     * Creates a new migration object
     */
    var saveMigration = function(req, file, parsedData, callback) {
      var migration, header, data;

      header = parsedData[0];
      delete parsedData[0];
      data = parsedData;

      migration = new (req.popit.model('Migration'))({
        source: {
          name: file.name,
          mime_type: file.type,
          parsed: {
            header: header,
            data: data
          }
        },
        tracking: {
          progress: -1,
          total: -1,
          count: -1,
          err: []
        }
      });

      migration.save(function(err, obj) {
        callback(obj);
      });
    }

    /*
     * Handle upload and parse provided csv
     */
    var uploadPost = function(req, res, next) {
      //console.log(req.files);

      if ( !req.files || req.files.source.size == 0) {
        console.log("no file found");
        res.local( 'error', 'required');
        res.redirect('/migration');
        return;
      }

      var file = req.files.source;

      app.parseCsv(file.path, function(parsedData, err) {
        // TODO handle error
        saveMigration(req, file, parsedData, function(migration) {
          req.session.migrationFile = migration.id;
          res.redirect('/migration/mapping/' + migration.id);
        });
      });
    }

    var uploadGet = function(req,res) {
      res.redirect('/migration');
    }

    /*
     * Handle the import request.
     * This happens after the CSV parsing and the definition of the mapping.
     */
    var mappingPost = function (req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          var formData = req.body;
          var mappings = _.zip(formData["csv-attribute"], formData["db-attribute-class"], formData["db-attribute"])

          var schema = formData["db-schema"];

          app.doImport(req.popit, schema, mappings, doc.source.parsed.data,
            function(progress, total) {
            // update
            //console.log('saved ', progress + ' of ' +  total);
            doc.tracking.progress = progress;
            doc.tracking.total = total;
            doc.save();
          }, function(err, people) {
            // finished
            doc.tracking.err = err;
            if (people) {
              doc.tracking.count = people.length;
            } else{
              doc.tracking.count = -1;
            };
            doc.save();

            res.local('error', err);
            res.local('people', people);
          });

          res.redirect('/migration/status/'+req.params.id);
        }
      });
    }

    var statusGet = function(req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          showStatusPage(req, res);
        }
      });
    }

    app.get('/progress/:id', requireUser, function (req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {

          res.writeHead(200, {'Content-Type': 'application/json'});
          p = doc.tracking;
          // console.log(p);

          res.write(JSON.stringify(p));
          res.end();
        }
      });
    });

    app.get('/status/:id', requireUser, statusGet);

    app.post('/upload', requireUser, uploadPost);
    app.get('/upload', requireUser, uploadGet);

    app.post('/mapping/:id', mappingPost);
    app.get('/mapping/:id', showMappingPage);
  });

  return app;
};
<commit_msg>404 correctly if no mapping found<commit_after>var express       = require('express'),
    Error404      = require('../errors').Error404,
    path          = require('path'),
    mkdirp        = require('mkdirp'),
    fs            = require('fs'),
    requireUser   = require('../middleware/route').requireUser,
    _             = require('underscore'),
    async         = require('async'),
    csv           = require('csv');

module.exports = function () {

  var app = express.createServer();

  app.schemas = {'person': {
      name: 'Person',
      model: 'Person',
      options: {
        name: {
          title: 'Name, birthday & similar',
          strict: true,
          suggestions: ['Full name', 'Title', 'First name', 'Last name', 'Middle name', 'Birthday', 'Nickname']
        },
        summary: {
          title: 'Summary',
          strict: true,
          suggestions: ['Summary']
        },
        position: {
          title: 'Position'
        },
        contact: {
          title: 'Contact Information',
          strict: false,
          suggestions: ['Address', 'Phone', 'Fax', 'Email', 'Skype']
        },
        links: {
          title: 'Link',
          strict: false,
          suggestions: ['Website', 'Wikipedia', 'Youtube', 'Twitter', 'Freebase' ,'RSS']
        },
        id: {
          title: 'ID',
          strict: false,
          suggestions: ['Facebook', 'Twitter']
        },
        data: {
          title: 'Other data',
          strict: false,
          suggestions: ['University', 'School', 'Gender']
        }
      }
  }};

  /*
   * Parses a csv from path and calls the callback with the parsed data.
   */
  app.parseCsv = function(path, callback) {
    var parsedData = {};

    csv()
      .fromPath(path)
      .on('data',function(data, index){
        parsedData[index] = data;
        //console.log('#'+index+' '+JSON.stringify(data));
      })
    .on('end',function(count){
      //console.log('Number of lines: '+count);
      callback(parsedData, null);
    })
    .on('error',function(err){
      console.log(err.message);
      callback(parsedData, err);
    });
  }

  /*
   * Converts mapping+attributes into easily accessible dict.
   * Take a look at the tests for an example.
   */
  app.zipIt = function(mappings, attributes) {
    var inverted = {};
    _.each(_.zip(mappings, attributes), function(item) {
      if (!item[0][0] || !item[0][1] || !item[0][2]) {
        return;
      }
      if (!inverted[item[0][1]]) {
        inverted[item[0][1]] = {};
      }
      inverted[item[0][1]][item[0][2]] = item[1];
    });

    return inverted;
  }

  /*
   * Writes the data from a form into the database according to the provided mapping.
   */
  app.doImport = function(popit, schema, mappings, data, updateCb, functionCallback) {
    /*
    console.log('schema:', schema);
    console.log('mappings:', mappings);
    console.log('popit:', popit);
    console.log('data:', data);
    //*/

    var Person = popit.model('Person'),
      Link = popit.model('Link'),
      Position = popit.model('Position'),
      Organisation = popit.model('Organisation'),
      ContactDetail = popit.model('ContactDetail'),
      positions = [], people = [], organisations = [];

    // async.forEach does not support objects, data is an object, not an array
    async.forEachSeries(Object.keys(data), function(key, forEachCallback) {
      var person, useable, res = [], attributes, name = '';

      attributes = data[key];

      useable = app.zipIt(mappings, attributes);

      if (useable.name['Title']) {
        name += useable.name['Title']+' ';
      }

      // either full or first name
      if (useable.name['First name']) {
        name += useable.name['First name'];
      } else {
        name += useable.name['Full name'];
      }

      if (useable.name['Middle name']) {
        name += ' '+ useable.name['Middle name'];
      }
      if (useable.name['Last name']) {
        name += ' '+ useable.name['Last name'];
      }

      // sometimes there are multiline entries in csv's
      if (name == '') {
        if (people.length > 0) {
          person = people.pop();
        } else {
          forEachCallback("Cannot pop person for multiline csv.");
          return;
        }
      } else {
        // create new person
        person = new Person({'name': name, 'contact_details' : [], 'data': {}, 'links': []}, false);
        person.set("ids", []);
      }

      if (useable.name["Birthdate"])
        person.set('birthDate', useable.name["Birthdate"]);

      async.parallel([
        function(callback) {
          _.each(useable['summary'], function(value, key) {
            if (value) {
              person.set('summary', value);
            }
          });
          callback();
        },
        function(callback) {
          _.each(useable['contact'], function(value, key) {
            if (value) {
              var cd = new ContactDetail({kind: key, value: value});
              person.contact_details.push(cd);
            }
          });
          callback();
        },
        function(callback) {
          _.each(useable['links'], function(value, key) {
            if (value) {
              var l = new Link({comment: key, url: value});
              person.links.push(l);
            }
          });
          callback();
        },
        function(callback) {
          // simple key value/ key array pairs
          //var s = person.get("data") || {}
          var s = person.data
          _.each(useable['data'], function(value, key) {
            if (value) {
              if (s[key]) {
                s[key].push(value)
              } else {
                s[key] = [value];
              }
            }
          });
          //person.set('data', s);
          callback();
        },
        function(callback) {
          // ids may get a little bit more information in the future
          var ids = person.get("ids") || {}
          _.each(useable['id'], function(value, key) {
            var i = {provider: key, id: value};
            ids.push(i);
            });
          person.set('ids', ids);
          callback();
        },
        function(callback) {
          if (useable['position']) {
            async.forEach(Object.keys(useable['position']), function(key, cb) {
              var value = useable['position'][key];
              if (value) {
                Organisation.findOne({name: value}, function(err, doc) {
                  if (err) console.log( err );
                  else {
                    // find organisation in array to avoid problems when saving
                    doc = doc || _.find(organisations, function(e) {return e.name == value});
                    if (!doc) {
                      doc = new Organisation({name: value});
                      organisations.push(doc);
                    }
                    var pos = new Position({
                      title: key,
                      organisation: doc,
                      person: person,
                    });
                    positions.push(pos);
                  }
                  cb(err);
                });
              } else {
                callback();
              }
            }, function(err) {
              callback(err);
            });
          } else {
            callback();
          }
        }],
        function(err, results) {

          /*
          console.log('useable:', useable);
          console.log('positions:', positions);
          console.log('organisation:', organisations);
          console.log('person:', person);
          //*/

          people.push(person);

          forEachCallback(err); //forEach callback
        }); // end of parallel

    }, function(err) {
      if (err) {
        functionCallback(err);
      }

      //console.log(people)

      var toBeSaved = _.union(people, positions, organisations);
      var total = toBeSaved.length;
      var progress = 0;

      // save people and positions to database
      // TODO make parallel, take care of slugs
      async.forEachSeries(toBeSaved, function(item, callback) {
        item.save(function (err) {
          progress++;
          updateCb(progress, total);
          callback(err);
        });
      }, function(err) {
        functionCallback(err, people, positions, organisations);
      });

    }); //end of forEach
  }

  app.mounted(function(parent) {

    var app = this;

    /*
     * Shows the migration/ index page
     */
    var showMigrationPage = function(req, res) {
      var query = req.popit.model('Migration').find().sort('created');

      query.exec(function(err, docs) {
        if (err) throw err;

        res.local('migration', docs);
        res.render('migration');
      });
    }

    /*
     * Shows the status page while the migration is running
     */
    var showStatusPage = function(req, res) {
      res.render('migration/status');
    }

    /*
     * Show mapping page for a migration
     */
    var showMappingPage = function(req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        //console.log(doc, req.params.id);
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          res.local('migration', doc);
          res.local('schemas', app.schemas);
          res.local('_', _);
          res.render('migration/mapping');
        }
      });
    }

    /*
     * Shows the migration/ index page
     */
    app.get('/', requireUser, function (req,res) {
      if( ! res.local('error') ) res.local('error', '');

      showMigrationPage(req, res);
    });

    /*
     * Deletes all migrations from the database.
     * This does not mean the imported data is lost, it just
     * means that the migration object is deleted.
     */
    app.get('/clear', requireUser, function (req,res) {
      var query = req.popit.model('Migration').find();

      query.exec(function(err, docs) {
        if (err) throw err;

        docs.forEach( function (doc) {
          doc.remove(function(err) {
            if (err) throw err;
          });
        });

        res.redirect('/migration');
      });
    });

    /*
     * Creates a new migration object
     */
    var saveMigration = function(req, file, parsedData, callback) {
      var migration, header, data;

      header = parsedData[0];
      delete parsedData[0];
      data = parsedData;

      migration = new (req.popit.model('Migration'))({
        source: {
          name: file.name,
          mime_type: file.type,
          parsed: {
            header: header,
            data: data
          }
        },
        tracking: {
          progress: -1,
          total: -1,
          count: -1,
          err: []
        }
      });

      migration.save(function(err, obj) {
        callback(obj);
      });
    }

    /*
     * Handle upload and parse provided csv
     */
    var uploadPost = function(req, res, next) {
      //console.log(req.files);

      if ( !req.files || req.files.source.size == 0) {
        console.log("no file found");
        res.local( 'error', 'required');
        res.redirect('/migration');
        return;
      }

      var file = req.files.source;

      app.parseCsv(file.path, function(parsedData, err) {
        // TODO handle error
        saveMigration(req, file, parsedData, function(migration) {
          req.session.migrationFile = migration.id;
          res.redirect('/migration/mapping/' + migration.id);
        });
      });
    }

    var uploadGet = function(req,res) {
      res.redirect('/migration');
    }

    /*
     * Handle the import request.
     * This happens after the CSV parsing and the definition of the mapping.
     */
    var mappingPost = function (req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          var formData = req.body;
          var mappings = _.zip(formData["csv-attribute"], formData["db-attribute-class"], formData["db-attribute"])

          var schema = formData["db-schema"];

          app.doImport(req.popit, schema, mappings, doc.source.parsed.data,
            function(progress, total) {
            // update
            //console.log('saved ', progress + ' of ' +  total);
            doc.tracking.progress = progress;
            doc.tracking.total = total;
            doc.save();
          }, function(err, people) {
            // finished
            doc.tracking.err = err;
            if (people) {
              doc.tracking.count = people.length;
            } else{
              doc.tracking.count = -1;
            };
            doc.save();

            res.local('error', err);
            res.local('people', people);
          });

          res.redirect('/migration/status/'+req.params.id);
        }
      });
    }

    var statusGet = function(req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          showStatusPage(req, res);
        }
      });
    }

    app.get('/progress/:id', requireUser, function (req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {

          res.writeHead(200, {'Content-Type': 'application/json'});
          p = doc.tracking;
          // console.log(p);

          res.write(JSON.stringify(p));
          res.end();
        }
      });
    });

    app.get('/status/:id', requireUser, statusGet);

    app.post('/upload', requireUser, uploadPost);
    app.get('/upload', requireUser, uploadGet);

    app.post('/mapping/:id', mappingPost);
    app.get('/mapping/:id', showMappingPage);
  });

  return app;
};
<|endoftext|>