<commit_before>var express = require('express'),
	bodyParser = require('body-parser'),
    authenticate = require('azure-mobile-apps/src/express/middleware/authenticate'),
    authorize = require('azure-mobile-apps/src/express/middleware/authorize');

var sql = require('mssql');
var util = require('../util');

const queryTertulias = 'SELECT' +
' tr_id         AS id,' +
' tr_name       AS name,' +
' tr_subject    AS subject,' +
' ev_targetdate AS nextEventDate,' +
' lo_name       AS nextEventLocation,' +
' no_count      AS messages,' +
' nv_name       AS role ' +
' FROM Tertulias' +
' INNER JOIN Members ON mb_tertulia = tr_id' +
' INNER JOIN Enumvalues ON mb_role = nv_id' +
' INNER JOIN Users ON mb_user = us_id' +
' LEFT JOIN' +
' (SELECT * FROM' +
' (SELECT RANK() OVER(PARTITION BY ev_tertulia ORDER BY ev_targetdate DESC) AS "rank", * FROM Events' +
'  INNER JOIN Locations on ev_location = lo_id WHERE ev_targetdate > GETDATE()) AS a WHERE a.rank = 1) AS b' +
' ON ev_tertulia = tr_id' +
' LEFT JOIN (SELECT no_tertulia, count(*) AS no_count FROM Notifications WHERE no_id NOT IN' +
' (SELECT no_id FROM Notifications INNER JOIN Readnotifications ON rn_notification = no_id' +
' INNER JOIN Users ON rn_user = us_id WHERE us_sid = @sid)' +
' GROUP BY no_tertulia) AS c ON no_tertulia = tr_id' +
' WHERE tr_is_cancelled = 0 AND us_sid = @sid';

const queryPublicTertulias = 'SELECT' +
' TOP 25' +
' tr_id         AS id,' +
' tr_name       AS name,' +
' tr_subject    AS subject,' +
' lo_name       AS location' +
//' lo_latitude   AS latitude,' +
//' lo_longitude  AS longitude,' +
//' lo_geography.STDistance('POINT(38.7762 -9.171391)') AS Distance' +
' FROM Tertulias' +
' INNER JOIN Locations ON tr_location = lo_id' +
' WHERE tr_is_cancelled = 0' +
' AND tr_is_private = 0' +
' AND tr_id NOT IN (SELECT mb_tertulia FROM Tertulias INNER JOIN Members ON mb_tertulia = tr_id INNER JOIN Users ON mb_user = us_id WHERE us_sid = @sid)' +
' ORDER BY lo_geography.STDistance(\'POINT(38.7640613 -9.1123113)\')';

const queryTertuliaDetails = 'SELECT DISTINCT' +
	' tr_id                    AS id,' + // Tertulia
	' tr_name                  AS name,' +
	' tr_subject               AS subject,' +
	' lo_name                  AS location,' + // Location
	' lo_address               AS address,' +
	' lo_zip                   AS zip,' +
	' lo_city                  AS city,' +
	' lo_country               AS country,' +
	' lo_latitude              AS latitude,' +
	' lo_longitude             AS longitude,' +
	' sc_type                  AS scheduleId,' + // Schedule
	' _Schedule.nv_name        AS scheduleName,' +
	' _Schedule.nv_description AS scheduleDescription,' +
	' tr_is_private            AS private, ' +  // Private
	' _Member.nv_name          AS role' + // Role
' FROM Tertulias' +
' INNER JOIN Locations  ON tr_location = lo_id' +
' INNER JOIN Schedules  ON tr_schedule = sc_id' +
' INNER JOIN Members    ON mb_tertulia = tr_id' +
' INNER JOIN Users      ON mb_user = us_id' +
' INNER JOIN EnumValues AS _Member ON mb_role = _Member.nv_id' +
' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
' WHERE tr_is_cancelled = 0 AND (us_sid = @sid OR tr_is_private = 0)' +
' AND tr_id = @tertulia';

/* { 'SQL types': {
	'String': 'sql.NVarChar', 'Number': 'sql.Int', 'Boolean': 'sql.Bit', 'Date': 'sql.DateTime', 'Buffer': 'sql.VarBinary', 'sql.Table': 'sql.TVP'
} } */
module.exports = function (configuration) {
    var router = express.Router();

    router.get('/', (req, res, next) => {
		var route = '/tertulias';
		req['tertulias'] = {};
		req.tertulias['resultsTag'] = 'tertulias';
		req.tertulias['query'] = queryTertulias;
	    req.tertulias['paramsTypes'] = { 'sid': sql.NVarChar };
	    req.tertulias['paramsValues'] = { 'sid': req.azureMobile.user.id };
	    req.tertulias['jsonType'] = "array";
	    req.tertulias['links'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '" }, ' +
			'{ "rel": "create", "method": "POST", "href": "' + route + '" }, ' +
			'{ "rel": "searchPublic", "method": "GET", "href": "' + route + '/publicsearch" } ' +
		']';
	    req.tertulias['itemLinks'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
			'{ "rel": "update", "method": "PUT", "href": "' + route + '/:id" }, ' +
			'{ "rel": "delete", "method": "DELETE", "href": "' + route + '/:id" }, ' +
			'{ "rel": "unsubscribe", "method": "DELETE", "href": "' + route + '/:id/unsubscribe" } ' +
		']';
	    goGet(req, res, next);
	});

    router.get('/publicSearch', (req, res, next) => {
		console.log('in /publicsearch');
		var route = '/tertulias';
		req['tertulias'] = {};
		req.tertulias['resultsTag'] = 'tertulias';
		req.tertulias['query'] = queryPublicTertulias;
	    req.tertulias['paramsTypes'] = {
	    	'sid': sql.NVarChar
	    	, 'query': sql.NVarChar
	    	, 'latitude': sql.Int
	    	, 'longitude': sql.Int
	    };
	    var latitude = parseFloat(req.query.latitude);
	    var longitude = parseFloat(req.query.longitude);
	    req.tertulias['paramsValues'] = {
	    	'sid': req.azureMobile.user.id
	    	, 'query': req.query.query
	    	, 'latitude': parseFloat(req.query.latitude)
	    	, 'longitude': parseFloat(req.query.longitude)
	    };
	    req.tertulias['jsonType'] = "array";
	    req.tertulias['links'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '/publicSearch" }' +
		']';
	    req.tertulias['itemLinks'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
			'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/:id/subscribe" }' +
		']';
	    goGet(req, res, next);
	});

	router.get('/:tr_id', (req, res, next) => {
		console.log('in /:tr_id');
		var tr_id = req.params.tr_id;

		if (isNaN(tr_id))
			return next();

		var route = '/tertulias/' + tr_id;
		req['tertulias'] = {};
		req.tertulias['resultsTag'] = 'tertulia';
		req.tertulias['query'] = queryTertuliaDetails;
	    req.tertulias['paramsTypes'] = { 'sid': sql.NVarChar, 'tertulia': sql.Int };
	    req.tertulias['paramsValues'] = { 'sid': req.azureMobile.user.id, 'tertulia': tr_id };
	    req.tertulias['jsonType'] = "object";
	    req.tertulias['links'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '" }, ' +
			'{ "rel": "update", "method": "PATCH", "href": "' + route + '" }, ' +
			'{ "rel": "delete", "method": "DELETE", "href": "' + route + '" }, ' +
			'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/subscribe" }, ' +
			'{ "rel": "unsubscribe", "method": "DELETE", "href": "' + route + '/unsubscribe" } ' +
		']';
	    goGet(req, res, next);
	});

	router.post('/', (req, res, next) => {
		console.log('in /');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('name', sql.NVarChar(40), req.body.name)
			.input('subject', sql.NVarChar(80), req.body.subject)
			.input('locationName', sql.NVarChar(40), req.body.location)
			.input('locationAddress', sql.NVarChar(80), req.body.address)
			.input('locationZip', sql.NVarChar(40), req.body.zip)
			.input('locationCity', sql.NVarChar(40), req.body.city)
			.input('locationCountry', sql.NVarChar(40), req.body.country)
			.input('locationLatitude', sql.NVarChar(12), req.body.latitude)
			.input('locationLongitude', sql.NVarChar(12), req.body.longitude)
			.input('weekDay', sql.NVarChar(20), 'Tuesday')
			.input('weekNr', sql.Int, 1)
			.input('fromStart', sql.BIT, 1)
			.input('skip', sql.Int, 0)
			.input('isPrivate', sql.Int, req.body.isPrivate ? 1 : 0)
			.input('userSid', sql.NVarChar(40), req.azureMobile.user.id)
			.execute('sp_insertTertulia_MonthlyW_sid')
			.then((recordsets) => {
				if (recordsets.length == 0) {
					res.status(201)	// 201: Created
						.type('application/json')
						.json( { result: 'Ok' } );
					return next();
				} else {
					res.status(409)	// 409: Conflict, 422: Unprocessable Entity (WebDAV; RFC 4918)
						.type('application/json')
						.json( { result: 'Duplicate' } );
					return next('409');
				}
				next();
			})
			.catch(function(err) {
				next(err);
			});
		})
		.catch(function(err) {
			return next(err);
		});
	});

	router.post('/:tr_id/subscribe', (req, res, next) => {
		console.log('in /:tr_id/subscribe');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
			.input('tertulia', sql.Int, req.params.tr_id)
			.execute('spSubscribe')
			.then((recordset) => {
				console.log(recordset.returnValue);
				if (recordset.returnValue = 1) {
					res.sendStatus(200);
				} else {
					res.sendStatus(409);
				}
				return next();
			})
			.catch(function(err) {
				console.log('catch1');
				console.log(err);
				next(err);
			});
		})
		.catch(function(err) {
			console.log('catch');
			console.log(err);
			return next(err);
		});
	});

	var completeError = function(err, res) {
	    if (err) {
	        console.error(err);
	        if (res) res.sendStatus(500);
	    }
	};

	var goGet = function(req, res, next) {
		console.log('in GoGet');
		var query = req.tertulias.query;
		var resultsTag = req.tertulias.resultsTag;
	    var paramsTypes = req.tertulias.paramsTypes;
	    var paramsValues = req.tertulias.paramsValues;
	    var links = req.tertulias.links;
	    var itemLinks = req.tertulias.itemLinks;

		var connection = new sql.Connection(util.sqlConfiguration);
	    connection.connect(function(err) {
	        var preparedStatement = new sql.PreparedStatement(connection);
	        for (var key in paramsTypes)
	        	preparedStatement.input(key, paramsTypes[key]);
	        preparedStatement.prepare(query, function(err) {
	            if (err) {
	            	completeError(err, res);
	            	return next(err);
	            }
	            preparedStatement.execute(paramsValues,
	                function(err, recordset, affected) {
	                    if (err) {
	                    	completeError(err, res);
	                    	return next(err);
	                    }
	                    res.type('application/json');
                    	if (typeof itemLinks !== typeof undefined) {
		                    recordset.forEach(function(elem) {
		                    	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
                    		});
	                    };
	                    preparedStatement.unprepare();
	                    var results = {};
	                    if (req.tertulias.jsonType == "array")
	                    	results[resultsTag] = recordset;
	                    else
	                    	results[resultsTag] = recordset.length == 0 ? {} : recordset[0];
	                    results['links'] = JSON.parse(links);
	                    res.json(results);
	                    return next();
	                }
	            );
	        });
	    });
	};

    return router;

}
<commit_msg>wip save<commit_after>var express = require('express'),
	bodyParser = require('body-parser'),
    authenticate = require('azure-mobile-apps/src/express/middleware/authenticate'),
    authorize = require('azure-mobile-apps/src/express/middleware/authorize');

var sql = require('mssql');
var util = require('../util');

const queryTertulias = 'SELECT' +
' tr_id         AS id,' +
' tr_name       AS name,' +
' tr_subject    AS subject,' +
' ev_targetdate AS nextEventDate,' +
' lo_name       AS nextEventLocation,' +
' no_count      AS messages,' +
' nv_name       AS role ' +
' FROM Tertulias' +
' INNER JOIN Members ON mb_tertulia = tr_id' +
' INNER JOIN Enumvalues ON mb_role = nv_id' +
' INNER JOIN Users ON mb_user = us_id' +
' LEFT JOIN' +
' (SELECT * FROM' +
' (SELECT RANK() OVER(PARTITION BY ev_tertulia ORDER BY ev_targetdate DESC) AS "rank", * FROM Events' +
'  INNER JOIN Locations on ev_location = lo_id WHERE ev_targetdate > GETDATE()) AS a WHERE a.rank = 1) AS b' +
' ON ev_tertulia = tr_id' +
' LEFT JOIN (SELECT no_tertulia, count(*) AS no_count FROM Notifications WHERE no_id NOT IN' +
' (SELECT no_id FROM Notifications INNER JOIN Readnotifications ON rn_notification = no_id' +
' INNER JOIN Users ON rn_user = us_id WHERE us_sid = @sid)' +
' GROUP BY no_tertulia) AS c ON no_tertulia = tr_id' +
' WHERE tr_is_cancelled = 0 AND us_sid = @sid';

const queryPublicTertulias = 'SELECT' +
' TOP 25' +
' tr_id         AS id,' +
' tr_name       AS name,' +
' tr_subject    AS subject,' +
' lo_name       AS location' +
//' lo_latitude   AS latitude,' +
//' lo_longitude  AS longitude,' +
//' lo_geography.STDistance('POINT(38.7762 -9.171391)') AS Distance' +
' FROM Tertulias' +
' INNER JOIN Locations ON tr_location = lo_id' +
' WHERE tr_is_cancelled = 0' +
' AND tr_is_private = 0' +
' AND tr_id NOT IN (SELECT mb_tertulia FROM Tertulias INNER JOIN Members ON mb_tertulia = tr_id INNER JOIN Users ON mb_user = us_id WHERE us_sid = @sid)' +
' ORDER BY lo_geography.STDistance(\'POINT(38.7640613 -9.1123113)\')';

const queryTertuliaDetails = 'SELECT DISTINCT' +
	' tr_id                    AS id,' + // Tertulia
	' tr_name                  AS name,' +
	' tr_subject               AS subject,' +
	' lo_name                  AS location,' + // Location
	' lo_address               AS address,' +
	' lo_zip                   AS zip,' +
	' lo_city                  AS city,' +
	' lo_country               AS country,' +
	' lo_latitude              AS latitude,' +
	' lo_longitude             AS longitude,' +
	' sc_type                  AS scheduleId,' + // Schedule
	' _Schedule.nv_name        AS scheduleName,' +
	' _Schedule.nv_description AS scheduleDescription,' +
	' tr_is_private            AS private, ' +  // Private
	' _Member.nv_name          AS role' + // Role
' FROM Tertulias' +
' INNER JOIN Locations  ON tr_location = lo_id' +
' INNER JOIN Schedules  ON tr_schedule = sc_id' +
' INNER JOIN Members    ON mb_tertulia = tr_id' +
' INNER JOIN Users      ON mb_user = us_id' +
' INNER JOIN EnumValues AS _Member ON mb_role = _Member.nv_id' +
' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
' WHERE tr_is_cancelled = 0 AND (us_sid = @sid OR tr_is_private = 0)' +
' AND tr_id = @tertulia';

/* { 'SQL types': {
	'String': 'sql.NVarChar', 'Number': 'sql.Int', 'Boolean': 'sql.Bit', 'Date': 'sql.DateTime', 'Buffer': 'sql.VarBinary', 'sql.Table': 'sql.TVP'
} } */
module.exports = function (configuration) {
    var router = express.Router();

    router.get('/', (req, res, next) => {
		var route = '/tertulias';
		req['tertulias'] = {};
		req.tertulias['resultsTag'] = 'tertulias';
		req.tertulias['query'] = queryTertulias;
	    req.tertulias['paramsTypes'] = { 'sid': sql.NVarChar };
	    req.tertulias['paramsValues'] = { 'sid': req.azureMobile.user.id };
	    req.tertulias['jsonType'] = "array";
	    req.tertulias['links'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '" }, ' +
			'{ "rel": "create", "method": "POST", "href": "' + route + '" }, ' +
			'{ "rel": "searchPublic", "method": "GET", "href": "' + route + '/publicsearch" } ' +
		']';
	    req.tertulias['itemLinks'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
			'{ "rel": "update", "method": "PUT", "href": "' + route + '/:id" }, ' +
			'{ "rel": "delete", "method": "DELETE", "href": "' + route + '/:id" }, ' +
			'{ "rel": "unsubscribe", "method": "DELETE", "href": "' + route + '/:id/unsubscribe" } ' +
		']';
	    goGet(req, res, next);
	});

    router.get('/publicSearch', (req, res, next) => {
		console.log('in /publicsearch');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
	    	.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
	    	.input('query', sql.NVarChar, req.query.query)
	    	.input('latitude', sql.Int, parseFloat(req.query.latitude))
	    	.input('longitude', sql.Int, parseFloat(req.query.longitude))
	    	.query(queryPublicTertulias)
	    	.then(function(err, recordset, affected) {
	    		console.log('err: ' + err);
	    		console.log('affected: ' + affected);
	    		console.log('recordset:');
	    		console.log(recordset);
	    		if (err) {
                	completeError(err, res);
                	return next(err);
                }
                var links = '[ ' +
						'{ "rel": "self", "method": "GET", "href": "' + route + '/publicSearch" }' +
					']'
                var itemLinks = '[ ' +
            	    	'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
						'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/:id/subscribe" }' +
					']';
                res.type('application/json');
                recordset.forEach(function(elem) {
                	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
        		});
                preparedStatement.unprepare();
                var results = {};
                if (req.tertulias.jsonType == "array")
                	results[resultsTag] = recordset;
                else
                	results[resultsTag] = recordset.length == 0 ? {} : recordset[0];
                results['links'] = JSON.parse(links);
                res.json(results);
                return next();
            })
	    });
	});
/*
		var route = '/tertulias';
		req['tertulias'] = {};
		req.tertulias['resultsTag'] = 'tertulias';
		req.tertulias['query'] = queryPublicTertulias;
	    req.tertulias['paramsTypes'] = {
	    	'sid': sql.NVarChar
	    	, 'query': sql.NVarChar
	    	, 'latitude': sql.Int
	    	, 'longitude': sql.Int
	    };
	    var latitude = parseFloat(req.query.latitude);
	    var longitude = parseFloat(req.query.longitude);
	    req.tertulias['paramsValues'] = {
	    	'sid': req.azureMobile.user.id
	    	, 'query': req.query.query
	    	, 'latitude': parseFloat(req.query.latitude)
	    	, 'longitude': parseFloat(req.query.longitude)
	    };
	    req.tertulias['jsonType'] = "array";
	    req.tertulias['links'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '/publicSearch" }' +
		']';
	    req.tertulias['itemLinks'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '/:id" }, ' +
			'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/:id/subscribe" }' +
		']';
	    goGet(req, res, next);
	});
	*/

	router.get('/:tr_id', (req, res, next) => {
		console.log('in /:tr_id');
		var tr_id = req.params.tr_id;

		if (isNaN(tr_id))
			return next();

		var route = '/tertulias/' + tr_id;
		req['tertulias'] = {};
		req.tertulias['resultsTag'] = 'tertulia';
		req.tertulias['query'] = queryTertuliaDetails;
	    req.tertulias['paramsTypes'] = { 'sid': sql.NVarChar, 'tertulia': sql.Int };
	    req.tertulias['paramsValues'] = { 'sid': req.azureMobile.user.id, 'tertulia': tr_id };
	    req.tertulias['jsonType'] = "object";
	    req.tertulias['links'] = '[ ' +
			'{ "rel": "self", "method": "GET", "href": "' + route + '" }, ' +
			'{ "rel": "update", "method": "PATCH", "href": "' + route + '" }, ' +
			'{ "rel": "delete", "method": "DELETE", "href": "' + route + '" }, ' +
			'{ "rel": "subscribe", "method": "POST", "href": "' + route + '/subscribe" }, ' +
			'{ "rel": "unsubscribe", "method": "DELETE", "href": "' + route + '/unsubscribe" } ' +
		']';
	    goGet(req, res, next);
	});

	router.post('/', (req, res, next) => {
		console.log('in /');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('name', sql.NVarChar(40), req.body.name)
			.input('subject', sql.NVarChar(80), req.body.subject)
			.input('locationName', sql.NVarChar(40), req.body.location)
			.input('locationAddress', sql.NVarChar(80), req.body.address)
			.input('locationZip', sql.NVarChar(40), req.body.zip)
			.input('locationCity', sql.NVarChar(40), req.body.city)
			.input('locationCountry', sql.NVarChar(40), req.body.country)
			.input('locationLatitude', sql.NVarChar(12), req.body.latitude)
			.input('locationLongitude', sql.NVarChar(12), req.body.longitude)
			.input('weekDay', sql.NVarChar(20), 'Tuesday')
			.input('weekNr', sql.Int, 1)
			.input('fromStart', sql.BIT, 1)
			.input('skip', sql.Int, 0)
			.input('isPrivate', sql.Int, req.body.isPrivate ? 1 : 0)
			.input('userSid', sql.NVarChar(40), req.azureMobile.user.id)
			.execute('sp_insertTertulia_MonthlyW_sid')
			.then((recordsets) => {
				if (recordsets.length == 0) {
					res.status(201)	// 201: Created
						.type('application/json')
						.json( { result: 'Ok' } );
					return next();
				} else {
					res.status(409)	// 409: Conflict, 422: Unprocessable Entity (WebDAV; RFC 4918)
						.type('application/json')
						.json( { result: 'Duplicate' } );
					return next('409');
				}
				next();
			})
			.catch(function(err) {
				next(err);
			});
		})
		.catch(function(err) {
			return next(err);
		});
	});

	router.post('/:tr_id/subscribe', (req, res, next) => {
		console.log('in /:tr_id/subscribe');
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('sid', sql.NVarChar(40), req.azureMobile.user.id)
			.input('tertulia', sql.Int, req.params.tr_id)
			.execute('spSubscribe')
			.then((recordset) => {
				console.log(recordset.returnValue);
				if (recordset.returnValue = 1) {
					res.sendStatus(200);
				} else {
					res.sendStatus(409);
				}
				return next();
			})
			.catch(function(err) {
				console.log('catch1');
				console.log(err);
				next(err);
			});
		})
		.catch(function(err) {
			console.log('catch');
			console.log(err);
			return next(err);
		});
	});

	var completeError = function(err, res) {
	    if (err) {
	        console.error(err);
	        if (res) res.sendStatus(500);
	    }
	};

	var goGet = function(req, res, next) {
		console.log('in GoGet');
		var query = req.tertulias.query;
		var resultsTag = req.tertulias.resultsTag;
	    var paramsTypes = req.tertulias.paramsTypes;
	    var paramsValues = req.tertulias.paramsValues;
	    var links = req.tertulias.links;
	    var itemLinks = req.tertulias.itemLinks;

		var connection = new sql.Connection(util.sqlConfiguration);
	    connection.connect(function(err) {
	        var preparedStatement = new sql.PreparedStatement(connection);
	        for (var key in paramsTypes)
	        	preparedStatement.input(key, paramsTypes[key]);
	        preparedStatement.prepare(query, function(err) {
	            if (err) {
	            	completeError(err, res);
	            	return next(err);
	            }
	            preparedStatement.execute(paramsValues,
	                function(err, recordset, affected) {
	                    if (err) {
	                    	completeError(err, res);
	                    	return next(err);
	                    }
	                    res.type('application/json');
                    	if (typeof itemLinks !== typeof undefined) {
		                    recordset.forEach(function(elem) {
		                    	elem['links'] = JSON.parse(itemLinks.replace(/:id/g, elem.id));
                    		});
	                    };
	                    preparedStatement.unprepare();
	                    var results = {};
	                    if (req.tertulias.jsonType == "array")
	                    	results[resultsTag] = recordset;
	                    else
	                    	results[resultsTag] = recordset.length == 0 ? {} : recordset[0];
	                    results['links'] = JSON.parse(links);
	                    res.json(results);
	                    return next();
	                }
	            );
	        });
	    });
	};

    return router;

}
<|endoftext|>


<commit_before>/*global Rules Logger Utils FormUtil Notification JSONF Storage Testing createCurrentPopupInIframe Workflows Libs */
/* eslint complexity:0, max-nested-callbacks: [1,5] */
var lastMatchingRules = [];
var lastActiveTab = null;
var totalMatchesCount = 0;
var runWorkflowOrRule;
var optionSettings = null;

/*eslint-disable no-unused-vars*/
var testingMode = false;
/*eslint-enable no-unused-vars*/

// set the browser action badge
var setBadge = function(txt, tabId) {
  chrome.browserAction.setBadgeText({"text": txt, "tabId": tabId});
  chrome.browserAction.setBadgeBackgroundColor({"color": [0, 136, 255, 200], "tabId": tabId});
  Testing.setVar("browser-action-badge-text", txt, "Browser action badge text");
};

// Shows number of matches in the extension's icon
var refreshMatchCounter = function (tab, count) {
  var txt = chrome.i18n.getMessage("no_match_available");
  if (count && count > 0) {
    txt = count.toString();
  }
  setBadge(txt, tab.id);
};

// Testcode: Fill testing HTML with macthing rules
var reportMatchingRulesForTesting = function(matchingRules, lastMatchingWorkflows) {
  /*eslint-disable max-nested-callbacks*/
  var mRule = matchingRules.map(function (rule) {
    return rule.prettyPrint();
  }).join(",");
  /*eslint-enable max-nested-callbacks*/
  Testing.setVar("matching-rules-count", matchingRules.length, "Matching rule #");
  Testing.setVar("matching-rules-text", "[" + mRule + "]", "Matching rules JSON");

  // If there is only one match we need something in the testpage to click on
  if(matchingRules.length + lastMatchingWorkflows.length === 1) {
    Testing.setVar("popup-html", "
" + matchingRules[0].name + "
", "Popup HTML (one match)");
  }
};

// When the user changes a tab, search for matching rules for that url
// or matching rules for that content
var onTabReadyRules = function(tabId) {
  lastMatchingRules = [];

  // Clear popup HTML
  chrome.browserAction.setPopup({"tabId": tabId, "popup": ""});
  Logger.info("[bg.js] onTabReadyRules on Tab " + tabId);

  chrome.tabs.get(tabId, function (tab) {

    // return if the tab isn't active anymore
    if (!tab.active || tab.url.indexOf("chrome") > -1) {
      return;
    }

    lastActiveTab = tab;
    Logger.info("[bg.js] Setting active tab", tab);

    // This is a little bit complicated.
    // I wish the chromium API would implement Promises for all that.
    Rules.all().then(function (rules) {
      // First filter all rules that have content matchers
      var relevantRules = rules.filter(function (rule) {
        return typeof rule.content !== "undefined";
      });

      // Send these rules to the content script so it can return the matching
      // rules based on the regex and the pages content
      var message = { "action": "matchContent", "rules": JSONF.stringify(relevantRules)};
      chrome.tabs.sendMessage(tabId, message, function (matchingContentRulesIds) {
        var matchingContentRules;

        // If we found rules that match by content ...
        if(typeof matchingContentRulesIds !== "undefined") {
          // ... select rules that match those ids
          matchingContentRules = rules.filter(function (rule) {
            return matchingContentRulesIds.indexOf(rule.id) > -1;
          });

          // Add the rules to the rule that matches by url
          lastMatchingRules = lastMatchingRules.concat(matchingContentRules);
        } else {
          matchingContentRules = [];
        }
        Logger.info("[bg.js] Got " + matchingContentRules.length + " rules matching the content of the page");

        // Now match those rules that have a "url" matcher
        Rules.match(tab.url).then(function (matchingRules) {
          Logger.info("[bg.js] Got " + matchingRules.length + " rules matching the url of the page");

          // Concatenate matched rules by CONTENT and URL
          lastMatchingRules = lastMatchingRules.concat(matchingRules);

          // Save rules to localStorage for popup to load
          Rules.lastMatchingRules(lastMatchingRules);

          // Now find and save the matching workflows for those rules
          Workflows.matchesForRules(lastMatchingRules).then(function prMatchingWfs(matchingWfs) {
            Workflows.saveMatches(matchingWfs);

            // Show matches in badge
            totalMatchesCount = lastMatchingRules.length + matchingWfs.length;
            refreshMatchCounter(tab, totalMatchesCount);

            // TESTING
            if(!Utils.isLiveExtension()) {
              reportMatchingRulesForTesting(lastMatchingRules, matchingWfs);
            }

            // No matches? Multiple Matches? Show popup when the user clicks on the icon
            // A single match should just fill the form (see below)
            if (totalMatchesCount !== 1 || optionSettings.alwaysShowPopup) {
              chrome.browserAction.setPopup({"tabId": tab.id, "popup": "html/popup.html"});
              if (!Utils.isLiveExtension()) {
                createCurrentPopupInIframe(tab.id);
              }
            } else if (lastMatchingRules[0].autorun === true) {
              // If the rule is marked as "autorun", execute the rule if only
              // one was found
              Logger.info("[bj.js] Rule is set to autorun");
              FormUtil.applyRule(lastMatchingRules[0], lastActiveTab);
            }
          });
        });

      });
    });
  });
};

// Load running workflow storage
// and run the next step
var onTabReadyWorkflow = function() {
  return new Promise(function (resolve) {
    Storage.load(Utils.keys.runningWorkflow).then(function prOnTabReadyWf(runningWorkflow) {
      // No running workflow?
      if(typeof runningWorkflow === "undefined" || !lastActiveTab) {
        resolve({status: "not_running", runRule: true});
        return;
      }

      // End of workflow reached
      if(runningWorkflow.currentStep >= runningWorkflow.steps.length) {
        FormUtil.displayMessage("Workflow finished!", lastActiveTab);
        Storage.delete(Utils.keys.runningWorkflow);

        // Search for matching rules and workflows
        // to fill the icon again
        runWorkflowOrRule(lastActiveTab.id);

        resolve({status: "finished", runRule: false});
        return;
      }

      // load rule for workflow step
      var ruleNameToRun = runningWorkflow.steps[runningWorkflow.currentStep];
      Logger.info("[background.js] Using workflow step # " + (runningWorkflow.currentStep + 1) + " (" + ruleNameToRun + ")");
      setBadge("#" + (runningWorkflow.currentStep + 1), lastActiveTab.id);

      Rules.findByName(ruleNameToRun).then(function prExecWfStep(rule) {
        if(typeof rule === "undefined") {
          // report not found rule in options, cancel workflow
          FormUtil.displayMessage("Workflow error: rule not found!", lastActiveTab);
          Storage.delete(Utils.keys.runningWorkflow);

          // Search for matching rules and workflows
          runWorkflowOrRule(lastActiveTab.id);

          resolve({status: "rule_not_found", runRule: false});
        } else {
          // Should a screenshot be taken?
          if(runningWorkflow.flags && runningWorkflow.flags.screenshot === true) {
            Logger.info("[background.js] setting rule.screenshot = true because Wf config said so");
            rule.screenshot = true;
          }

          // Fill with this rule
          FormUtil.displayMessage("Workflow step " + (runningWorkflow.currentStep + 1) + "/" + runningWorkflow.steps.length, lastActiveTab);
          FormUtil.applyRule(rule, lastActiveTab);

          // Save workflow state so we can continue even after a page reload
          Storage.save({
            currentStep: runningWorkflow.currentStep + 1,
            steps: runningWorkflow.steps,
            flags: runningWorkflow.flags
          }, Utils.keys.runningWorkflow).then(function () {
            resolve({status: "running_workflow", runRule: false});
          });
        }
      });

      // Running workflow! Don't run normal rules.
      resolve({status: "running_workflow", runRule: false});
    });
  });
};

// Searches for workflows or rules to run
// Workflows steps are then run and subsequent matching rules are ignored
runWorkflowOrRule = function (tabId) {
  // First check (and run) workflows
  return onTabReadyWorkflow().then(function prOnTabReadyWf(workflowStatus) {
    // If a workflow step has been run, don't run rules
    if(workflowStatus.runRule) {
      onTabReadyRules(tabId);
    }
  });
};

// Generates a filename that is safe for disc storage
var generateFilename = function(metadata) {
  var ruleNameAsFilename = metadata.name.replace(/[^a-z0-9-_]/gi, '_') + ".jpg";
  return "fof-screenshot-" + metadata.ruleId.replace(/([0-9]+)-([0-9]+)/, "tab-$1-rule-$2-field-") + metadata.fieldIndex + "_" + ruleNameAsFilename;
};

// saves a screenshot to localStorage
/*
var saveScreenshot = function(ruleMetadata, screenshotDataUri) {
  // Load the screenshots saved so far and add the new one
  // TODO: chrome.storage.local.getBytesInUse(null, function(b) { console.error(b); });
  // Only save if memory sufficient -> ask for permission for "unlimitedStorage"
  Storage.load(Utils.keys.screenshots).then(function modifyScreenshots(screenshots) {
    screenshots = screenshots || {};
    var saveAs = ruleMetadata.ruleId + "_" + ruleMetadata.fieldIndex;

    screenshots[saveAs] = {
      ruleName: ruleMetadata.name,
      dataUri: screenshotDataUri
    };

    Storage.save(screenshots, Utils.keys.screenshots).then(function() {
      Logger.info("[bg.js] Saved a screenshot as '" + saveAs + "' (" + ruleMetadata.name + ")");
    });
  });
};
*/

// Takes screenshot of a window
// and downloads it to disk
var takeScreenshot = function(windowId, ruleMetadata, potentialFilename) {
  var quality = optionSettings.jpegQuality || 60;
  var fName;

  // force download of the image
  if(typeof potentialFilename === "string") {
    // use user defined name
    fName = potentialFilename.replace(/[^a-z0-9-_]/gi, '_') + ".jpg";
  } else if(ruleMetadata) {
    // use generated name
    fName = generateFilename(ruleMetadata);
  } else {
    return;
  }

  chrome.tabs.captureVisibleTab(windowId, { format: "jpeg", quality: quality}, function(screenshotDataUri) {
    Utils.downloadImage(screenshotDataUri, fName);
    // save the image to localStorage
    //TODO: scale image then -> (FS, 2015-09-27)saveScreenshot(ruleMetadata, screenshotDataUri);
  });
};

// Fires when a tab becomes active (https://developer.chrome.com/extensions/tabs#event-onActivated)
chrome.tabs.onActivated.addListener(function (activeInfo) {
  runWorkflowOrRule(activeInfo.tabId);
});

// Fires when the URL changes (https://developer.chrome.com/extensions/tabs#event-onUpdated)
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo) {
  if(changeInfo.status && changeInfo.status === "complete") {
    runWorkflowOrRule(tabId);
  }
});

// This event will only fire if NO POPUP is set
// This is the case when only one rule matches
chrome.browserAction.onClicked.addListener(function (){
  FormUtil.applyRule(lastMatchingRules[0], lastActiveTab);
});

// Listen for messages from other background/popup scripts
chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {

  Logger.info("[bj.js] Received message " + JSONF.stringify(message));

  // From popup.js:
  // This receives the index of the rule to apply when there is more than one match
  if (message.action === "fillWithRule") {
    Logger.info("[bg.js] called by popup.js with rule index " + message.index + ", id = " + message.id);
    // Find the rule by id
    var rules = lastMatchingRules.filter(function (rule) {
      return rule.id === message.id;
    });
    FormUtil.applyRule(rules[0], lastActiveTab);
    sendResponse(true);
  }

  // From popup.js:
  // Apply a workflow starting at the first step / rule
  if (message.action === "fillWithWorkflow") {
    // Load previously saved matching workflows
    Workflows.findById(message.id).then(function prLoadMatches(matchingWf) {
      // Now save the steps of that workflow to the storage and
      // mark the current running workflow
      Storage.save({ currentStep: 0, steps: matchingWf.steps, flags: matchingWf.flags }, Utils.keys.runningWorkflow).then(function () {
        onTabReadyWorkflow();
      });
    });
    sendResponse(true);
  }

  // Open an intern URL (aka. options).
  // Callable by content script that otherwise isn't allowed to open intern urls.
  if(message.action === "openIntern" && message.url) {
    Logger.info("[bg.js] received 'openIntern' with url '" + message.url + "'");
    chrome.tabs.create({ "active": true, "url": message.url });
  }

  // Display a notification to the user that the extract has finished
  if(message.action === "extractFinishedNotification") {
    Logger.info("[bg.js] received 'extractFinishedNotification'");
    Notification.create("Extracted your form. Click here to check the options panel for more info.", null, Utils.openOptions);
  }

  // Return the last active tab id
  if(message.action === "lastActiveTabId" && lastActiveTab !== null) {
    Logger.info("[bg.js] received 'lastActiveTabId'. Sending tabId " + lastActiveTab.id);
    sendResponse(lastActiveTab.id);
  }

  // received from options.js to reload Libs
  if(message.action === "reloadLibs" && lastActiveTab !== null) {
    // Why reload libs? If the user changes a tab containing a library definition
    // we must update it before the user executes a rule
    // Otherwise the new function won't be found
    // This is only useful for library functions used in before functions since those are
    // evaluated in the context of the background page
    Libs.import();
  }

  // Set local version of settings based on what is set in option.js
  // This is called from options.js whenever settings are changed (or initially loaded)
  if(message.action === "setSettings" && message.message) {
    optionSettings = message.message;
    Logger.info("[bg.js] Settings set to " + JSONF.stringify(optionSettings));
  }
});

// REMOVE START
// Messages from content.js
chrome.runtime.onConnect.addListener(function (port) {
  port.onMessage.addListener(function(message) {
    if(message.action === "log" && message.message) {
      Logger.store(message.message);
    }
  });
});
// REMOVE END

// Listen for messages from content.js
chrome.runtime.onMessage.addListener(function (message) {
  // REMOVE START
  if(message.action === "log" && message.message) {
    Logger.store(message.message);
  }
  // REMOVE END

  // The content page (form_filler.js) requests a screenshot to be taken
  // the message.flag can be the filename or true/false
  if(message.action === "takeScreenshot" && typeof message.flag !== "undefined") {
    Logger.info("[bg.js] Request from content.js to take a screenshot of windowId " + lastActiveTab.windowId);
    takeScreenshot(lastActiveTab.windowId, message.value, message.flag);
  }
});

// When this file loads, load and store settings
Storage.load(Utils.keys.settings).then(function(settings) {
  if(typeof settings === "undefined") {
    settings = Utils.defaultSettings;
  }
  optionSettings = settings;
});

// Fires when the extension is install or updated
chrome.runtime.onInstalled.addListener(function (details) {
  Logger.info("[bg.js] runtime.onInstalled fired");

  // Called on very first install
  if (details.reason === "install") {
    Notification.create(chrome.i18n.getMessage("first_install_notification"), null, function () {
      Utils.openOptions("#help");
    });
  }

  // Check if tabs are saved or we start from scratch
  Storage.load(Utils.keys.tabs).then(function (tabSettings) {
    // No tab settings found, create one
    if(typeof tabSettings === "undefined") {
      Logger.info("[bg.js] Creating default tab setting");
      Storage.save([{
        "id": 1,
        "name": chrome.i18n.getMessage("tabs_default_name")
      }], Utils.keys.tabs);
    }
  });

  // remove log entries
  Logger.delete();

  // Check if there are notifications to display
  if(Utils.version.indexOf(".") > -1) {
    Notification.forVersion(Utils.version);
  }
});

<commit_msg>remove saveScreenshot<commit_after>/*global Rules Logger Utils FormUtil Notification JSONF Storage Testing createCurrentPopupInIframe Workflows Libs */
/* eslint complexity:0, max-nested-callbacks: [1,5] */
var lastMatchingRules = [];
var lastActiveTab = null;
var totalMatchesCount = 0;
var runWorkflowOrRule;
var optionSettings = null;

/*eslint-disable no-unused-vars*/
var testingMode = false;
/*eslint-enable no-unused-vars*/

// set the browser action badge
var setBadge = function(txt, tabId) {
  chrome.browserAction.setBadgeText({"text": txt, "tabId": tabId});
  chrome.browserAction.setBadgeBackgroundColor({"color": [0, 136, 255, 200], "tabId": tabId});
  Testing.setVar("browser-action-badge-text", txt, "Browser action badge text");
};

// Shows number of matches in the extension's icon
var refreshMatchCounter = function (tab, count) {
  var txt = chrome.i18n.getMessage("no_match_available");
  if (count && count > 0) {
    txt = count.toString();
  }
  setBadge(txt, tab.id);
};

// Testcode: Fill testing HTML with macthing rules
var reportMatchingRulesForTesting = function(matchingRules, lastMatchingWorkflows) {
  /*eslint-disable max-nested-callbacks*/
  var mRule = matchingRules.map(function (rule) {
    return rule.prettyPrint();
  }).join(",");
  /*eslint-enable max-nested-callbacks*/
  Testing.setVar("matching-rules-count", matchingRules.length, "Matching rule #");
  Testing.setVar("matching-rules-text", "[" + mRule + "]", "Matching rules JSON");

  // If there is only one match we need something in the testpage to click on
  if(matchingRules.length + lastMatchingWorkflows.length === 1) {
    Testing.setVar("popup-html", "
" + matchingRules[0].name + "
", "Popup HTML (one match)");
  }
};

// When the user changes a tab, search for matching rules for that url
// or matching rules for that content
var onTabReadyRules = function(tabId) {
  lastMatchingRules = [];

  // Clear popup HTML
  chrome.browserAction.setPopup({"tabId": tabId, "popup": ""});
  Logger.info("[bg.js] onTabReadyRules on Tab " + tabId);

  chrome.tabs.get(tabId, function (tab) {

    // return if the tab isn't active anymore
    if (!tab.active || tab.url.indexOf("chrome") > -1) {
      return;
    }

    lastActiveTab = tab;
    Logger.info("[bg.js] Setting active tab", tab);

    // This is a little bit complicated.
    // I wish the chromium API would implement Promises for all that.
    Rules.all().then(function (rules) {
      // First filter all rules that have content matchers
      var relevantRules = rules.filter(function (rule) {
        return typeof rule.content !== "undefined";
      });

      // Send these rules to the content script so it can return the matching
      // rules based on the regex and the pages content
      var message = { "action": "matchContent", "rules": JSONF.stringify(relevantRules)};
      chrome.tabs.sendMessage(tabId, message, function (matchingContentRulesIds) {
        var matchingContentRules;

        // If we found rules that match by content ...
        if(typeof matchingContentRulesIds !== "undefined") {
          // ... select rules that match those ids
          matchingContentRules = rules.filter(function (rule) {
            return matchingContentRulesIds.indexOf(rule.id) > -1;
          });

          // Add the rules to the rule that matches by url
          lastMatchingRules = lastMatchingRules.concat(matchingContentRules);
        } else {
          matchingContentRules = [];
        }
        Logger.info("[bg.js] Got " + matchingContentRules.length + " rules matching the content of the page");

        // Now match those rules that have a "url" matcher
        Rules.match(tab.url).then(function (matchingRules) {
          Logger.info("[bg.js] Got " + matchingRules.length + " rules matching the url of the page");

          // Concatenate matched rules by CONTENT and URL
          lastMatchingRules = lastMatchingRules.concat(matchingRules);

          // Save rules to localStorage for popup to load
          Rules.lastMatchingRules(lastMatchingRules);

          // Now find and save the matching workflows for those rules
          Workflows.matchesForRules(lastMatchingRules).then(function prMatchingWfs(matchingWfs) {
            Workflows.saveMatches(matchingWfs);

            // Show matches in badge
            totalMatchesCount = lastMatchingRules.length + matchingWfs.length;
            refreshMatchCounter(tab, totalMatchesCount);

            // TESTING
            if(!Utils.isLiveExtension()) {
              reportMatchingRulesForTesting(lastMatchingRules, matchingWfs);
            }

            // No matches? Multiple Matches? Show popup when the user clicks on the icon
            // A single match should just fill the form (see below)
            if (totalMatchesCount !== 1 || optionSettings.alwaysShowPopup) {
              chrome.browserAction.setPopup({"tabId": tab.id, "popup": "html/popup.html"});
              if (!Utils.isLiveExtension()) {
                createCurrentPopupInIframe(tab.id);
              }
            } else if (lastMatchingRules[0].autorun === true) {
              // If the rule is marked as "autorun", execute the rule if only
              // one was found
              Logger.info("[bj.js] Rule is set to autorun");
              FormUtil.applyRule(lastMatchingRules[0], lastActiveTab);
            }
          });
        });

      });
    });
  });
};

// Load running workflow storage
// and run the next step
var onTabReadyWorkflow = function() {
  return new Promise(function (resolve) {
    Storage.load(Utils.keys.runningWorkflow).then(function prOnTabReadyWf(runningWorkflow) {
      // No running workflow?
      if(typeof runningWorkflow === "undefined" || !lastActiveTab) {
        resolve({status: "not_running", runRule: true});
        return;
      }

      // End of workflow reached
      if(runningWorkflow.currentStep >= runningWorkflow.steps.length) {
        FormUtil.displayMessage("Workflow finished!", lastActiveTab);
        Storage.delete(Utils.keys.runningWorkflow);

        // Search for matching rules and workflows
        // to fill the icon again
        runWorkflowOrRule(lastActiveTab.id);

        resolve({status: "finished", runRule: false});
        return;
      }

      // load rule for workflow step
      var ruleNameToRun = runningWorkflow.steps[runningWorkflow.currentStep];
      Logger.info("[background.js] Using workflow step # " + (runningWorkflow.currentStep + 1) + " (" + ruleNameToRun + ")");
      setBadge("#" + (runningWorkflow.currentStep + 1), lastActiveTab.id);

      Rules.findByName(ruleNameToRun).then(function prExecWfStep(rule) {
        if(typeof rule === "undefined") {
          // report not found rule in options, cancel workflow
          FormUtil.displayMessage("Workflow error: rule not found!", lastActiveTab);
          Storage.delete(Utils.keys.runningWorkflow);

          // Search for matching rules and workflows
          runWorkflowOrRule(lastActiveTab.id);

          resolve({status: "rule_not_found", runRule: false});
        } else {
          // Should a screenshot be taken?
          if(runningWorkflow.flags && runningWorkflow.flags.screenshot === true) {
            Logger.info("[background.js] setting rule.screenshot = true because Wf config said so");
            rule.screenshot = true;
          }

          // Fill with this rule
          FormUtil.displayMessage("Workflow step " + (runningWorkflow.currentStep + 1) + "/" + runningWorkflow.steps.length, lastActiveTab);
          FormUtil.applyRule(rule, lastActiveTab);

          // Save workflow state so we can continue even after a page reload
          Storage.save({
            currentStep: runningWorkflow.currentStep + 1,
            steps: runningWorkflow.steps,
            flags: runningWorkflow.flags
          }, Utils.keys.runningWorkflow).then(function () {
            resolve({status: "running_workflow", runRule: false});
          });
        }
      });

      // Running workflow! Don't run normal rules.
      resolve({status: "running_workflow", runRule: false});
    });
  });
};

// Searches for workflows or rules to run
// Workflows steps are then run and subsequent matching rules are ignored
runWorkflowOrRule = function (tabId) {
  // First check (and run) workflows
  return onTabReadyWorkflow().then(function prOnTabReadyWf(workflowStatus) {
    // If a workflow step has been run, don't run rules
    if(workflowStatus.runRule) {
      onTabReadyRules(tabId);
    }
  });
};

// Generates a filename that is safe for disc storage
var generateFilename = function(metadata) {
  var ruleNameAsFilename = metadata.name.replace(/[^a-z0-9-_]/gi, '_') + ".jpg";
  return "fof-screenshot-" + metadata.ruleId.replace(/([0-9]+)-([0-9]+)/, "tab-$1-rule-$2-field-") + metadata.fieldIndex + "_" + ruleNameAsFilename;
};

// Takes screenshot of a window
// and downloads it to disk
var takeScreenshot = function(windowId, ruleMetadata, potentialFilename) {
  var quality = optionSettings.jpegQuality || 60;
  var fName;

  // force download of the image
  if(typeof potentialFilename === "string") {
    // use user defined name
    fName = potentialFilename.replace(/[^a-z0-9-_]/gi, '_') + ".jpg";
  } else if(ruleMetadata) {
    // use generated name
    fName = generateFilename(ruleMetadata);
  } else {
    return;
  }

  chrome.tabs.captureVisibleTab(windowId, { format: "jpeg", quality: quality}, function(screenshotDataUri) {
    Utils.downloadImage(screenshotDataUri, fName);
  });
};

// Fires when a tab becomes active (https://developer.chrome.com/extensions/tabs#event-onActivated)
chrome.tabs.onActivated.addListener(function (activeInfo) {
  runWorkflowOrRule(activeInfo.tabId);
});

// Fires when the URL changes (https://developer.chrome.com/extensions/tabs#event-onUpdated)
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo) {
  if(changeInfo.status && changeInfo.status === "complete") {
    runWorkflowOrRule(tabId);
  }
});

// This event will only fire if NO POPUP is set
// This is the case when only one rule matches
chrome.browserAction.onClicked.addListener(function (){
  FormUtil.applyRule(lastMatchingRules[0], lastActiveTab);
});

// Listen for messages from other background/popup scripts
chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {

  Logger.info("[bj.js] Received message " + JSONF.stringify(message));

  // From popup.js:
  // This receives the index of the rule to apply when there is more than one match
  if (message.action === "fillWithRule") {
    Logger.info("[bg.js] called by popup.js with rule index " + message.index + ", id = " + message.id);
    // Find the rule by id
    var rules = lastMatchingRules.filter(function (rule) {
      return rule.id === message.id;
    });
    FormUtil.applyRule(rules[0], lastActiveTab);
    sendResponse(true);
  }

  // From popup.js:
  // Apply a workflow starting at the first step / rule
  if (message.action === "fillWithWorkflow") {
    // Load previously saved matching workflows
    Workflows.findById(message.id).then(function prLoadMatches(matchingWf) {
      // Now save the steps of that workflow to the storage and
      // mark the current running workflow
      Storage.save({ currentStep: 0, steps: matchingWf.steps, flags: matchingWf.flags }, Utils.keys.runningWorkflow).then(function () {
        onTabReadyWorkflow();
      });
    });
    sendResponse(true);
  }

  // Open an intern URL (aka. options).
  // Callable by content script that otherwise isn't allowed to open intern urls.
  if(message.action === "openIntern" && message.url) {
    Logger.info("[bg.js] received 'openIntern' with url '" + message.url + "'");
    chrome.tabs.create({ "active": true, "url": message.url });
  }

  // Display a notification to the user that the extract has finished
  if(message.action === "extractFinishedNotification") {
    Logger.info("[bg.js] received 'extractFinishedNotification'");
    Notification.create("Extracted your form. Click here to check the options panel for more info.", null, Utils.openOptions);
  }

  // Return the last active tab id
  if(message.action === "lastActiveTabId" && lastActiveTab !== null) {
    Logger.info("[bg.js] received 'lastActiveTabId'. Sending tabId " + lastActiveTab.id);
    sendResponse(lastActiveTab.id);
  }

  // received from options.js to reload Libs
  if(message.action === "reloadLibs" && lastActiveTab !== null) {
    // Why reload libs? If the user changes a tab containing a library definition
    // we must update it before the user executes a rule
    // Otherwise the new function won't be found
    // This is only useful for library functions used in before functions since those are
    // evaluated in the context of the background page
    Libs.import();
  }

  // Set local version of settings based on what is set in option.js
  // This is called from options.js whenever settings are changed (or initially loaded)
  if(message.action === "setSettings" && message.message) {
    optionSettings = message.message;
    Logger.info("[bg.js] Settings set to " + JSONF.stringify(optionSettings));
  }
});

// REMOVE START
// Messages from content.js
chrome.runtime.onConnect.addListener(function (port) {
  port.onMessage.addListener(function(message) {
    if(message.action === "log" && message.message) {
      Logger.store(message.message);
    }
  });
});
// REMOVE END

// Listen for messages from content.js
chrome.runtime.onMessage.addListener(function (message) {
  // REMOVE START
  if(message.action === "log" && message.message) {
    Logger.store(message.message);
  }
  // REMOVE END

  // The content page (form_filler.js) requests a screenshot to be taken
  // the message.flag can be the filename or true/false
  if(message.action === "takeScreenshot" && typeof message.flag !== "undefined") {
    Logger.info("[bg.js] Request from content.js to take a screenshot of windowId " + lastActiveTab.windowId);
    takeScreenshot(lastActiveTab.windowId, message.value, message.flag);
  }
});

// When this file loads, load and store settings
Storage.load(Utils.keys.settings).then(function(settings) {
  if(typeof settings === "undefined") {
    settings = Utils.defaultSettings;
  }
  optionSettings = settings;
});

// Fires when the extension is install or updated
chrome.runtime.onInstalled.addListener(function (details) {
  Logger.info("[bg.js] runtime.onInstalled fired");

  // Called on very first install
  if (details.reason === "install") {
    Notification.create(chrome.i18n.getMessage("first_install_notification"), null, function () {
      Utils.openOptions("#help");
    });
  }

  // Check if tabs are saved or we start from scratch
  Storage.load(Utils.keys.tabs).then(function (tabSettings) {
    // No tab settings found, create one
    if(typeof tabSettings === "undefined") {
      Logger.info("[bg.js] Creating default tab setting");
      Storage.save([{
        "id": 1,
        "name": chrome.i18n.getMessage("tabs_default_name")
      }], Utils.keys.tabs);
    }
  });

  // remove log entries
  Logger.delete();

  // Check if there are notifications to display
  if(Utils.version.indexOf(".") > -1) {
    Notification.forVersion(Utils.version);
  }
});

<|endoftext|>


<commit_before>

function Display(canvas, bgCanvas, map, robot, filter) {
	this.canvas = canvas;
	this.bgCanvas = bgCanvas;
	this.map = map;
	this.robot = robot;
	this.filter = filter;
}

Display.prototype.update = function () {

}

Display.prototype.updateCanvas() {

}
<commit_msg>deleted unused file<commit_after><|endoftext|>


<commit_before>'use strict';

// Declare app level module which depends on views, and components
angular.module('myApp', [
  'ngRoute',
  'vTable'
]).
config(['$routeProvider', function($routeProvider) {
  $routeProvider
    .when('/vTableSample', {
      templateUrl: 'sample/vTableSample/vTableSample.html',
      controller: 'vTableSampleCtrl'
    });

  $routeProvider.otherwise('/vTableSample');
}]);<commit_msg>add default route<commit_after>'use strict';

// Declare app level module which depends on views, and components
angular.module('myApp', [
  'ngRoute',
  'vTable'
]).
config(['$routeProvider', function($routeProvider) {
  $routeProvider
    .when('/vTableSample', {
      templateUrl: 'sample/vTableSample/vTableSample.html',
      controller: 'vTableSampleCtrl'
    }).otherwise({
      redirectTo: '/vTableSample'
    });
}]);<|endoftext|>


<commit_before>module.exports = {
  reportTypes: {
    appointments: {
      title: 'Appointments',
      children: [
        {
          value: 'futureappointments',
          label: 'Upcoming VA Appointments',
        },
        {
          value: 'pastappointments',
          label: 'VA Appointment History from the last 2 years',
        },
      ],
    },
    medications: {
      title: 'Medications (includes allergy information)',
      children: [
        {
          value: 'prescriptions',
          label: 'VA Medication History',
        },
        {
          value: 'medications',
          label: 'Self-Reported Medications and Supplements',
        },
      ],
    },
    labs: {
      title: 'Labs and Tests',
      children: [
        {
          value: 'labsandtests',
          label: 'VA Laboratory Results',
          hold: 3,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. VA lab results will be available 3 calendar days after they’ve been verified.',
        },
        {
          value: 'vapathology',
          label: 'VA Pathology Reports',
          hold: 14,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. VA pathology reports will be available 14 calendar days after they’ve been completed. Studies done at a non-VA facility may not be available or may not include an interpretation.',
        },
        {
          value: 'varadiology',
          label: 'VA Radiology Reports',
          hold: 3,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. VA radiology reports will be available 3 calendar days after they’ve been completed. Studies done at a non-VA facility may not be available or may not include an interpretation.',
        },
        {
          value: 'vaekg',
          label: 'History of Electrocardiograms (EKGs) performed at VA facilities',
        },
        {
          value: 'vachemlabs',
          label: 'Self-Reported Labs and Tests',
        },
      ],
    },
    ehr: {
      title: 'VA Electronic Health Record History and Wellness Reminders',
      children: [
        {
          value: 'vaproblemlist',
          label: 'VA Problem List',
          hold: 3,
          holdExplanation: 'Your VA problem list contains active health problems that your VA health care team is helping you manage. This information will be available 3 days after it’s been entered.',
        },
        {
          value: 'vaadmissionsanddischarges',
          label: 'VA Admissions and Discharges',
          hold: 3,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. Discharge summaries will be available 3 days after they’re completed.',
        },
        {
          value: 'vaprogressnotes',
          label: 'VA Notes starting from Jan 01, 2013',
          hold: 3,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. VA notes starting from Jan 1, 2013 will be available 3 days after they’ve been completed and signed by all required members of your VA health care team.',
        },
        {
          value: 'wellness',
          label: 'VA Wellness Reminders',
        },
      ],
    },
    allergies: {
      title: 'Allergies',
      children: [
        {
          value: 'vaallergies',
          label: 'VA Allergies',
        },
        {
          value: 'seiallergies',
          label: 'Self-Reported Allergies',
        },
      ],
    },
    immunizations: {
      title: 'Immunizations',
      children: [
        {
          value: 'vaimmunizations',
          label: 'VA Immunizations',
        },
        {
          value: 'seiimmunizations',
          label: 'Self-Reported Immunizations',
        },
      ],
    },
    vitals: {
      title: 'Vitals and Readings',
      children: [
        {
          value: 'vitalsandreadings',
          label: 'VA Vitals and Readings',
        },
        {
          value: 'vahth',
          label: 'Self-Reported Vitals and Readings',
        },
      ],
    },
    selfReported: {
      title: 'Self-Reported Health History',
      children: [
        {
          value: 'medicalevents',
          label: 'Medical Events',
        },
        {
          value: 'familyhealthhistory',
          label: 'Family Health History',
        },
        {
          value: 'militaryhealthhistory',
          label: 'Military Health History',
        },
        {
          value: 'treatmentfacilities',
          label: 'Treatment Facilities',
        },
        {
          value: 'healthcareproviders',
          label: 'Health Care Providers',
        },
      ],
    },
    food: {
      title: 'Food and Activity Journals',
      children: [
        {
          value: 'seiactivityjournal',
          label: 'Self-Reported Activity Journal',
        },
        {
          value: 'seifoodjournal',
          label: 'Self-Reported Food Journal',
        },
      ],
    },
    goals: {
      title: 'Goals',
      children: [
        {
          value: 'seimygoalscurrent',
          label: 'Self-Reported Current Goals',
        },
        {
          value: 'seimygoalscompleted',
          label: 'Self-Reported Completed Goals',
        },
      ],
    },
    demographics: {
      title: 'Demographics and Health Insurance',
      children: [
        {
          value: 'vademographics',
          label: 'VA Demographics from VA Treating Facilities in the last 3 years',
        },
        {
          value: 'seidemographics',
          label: 'Self-Reported Demographics',
        },
        {
          value: 'healthinsurance',
          label: 'Self-Reported Health Insurance',
        },
      ],
    },
    dod: {
      title: 'Department of Defense',
      children: [
        {
          value: 'dodmilitaryservice',
          label: 'Information from the Department of Defense',
        },
      ]
    },
  },

  errorCodes: {
    accountcreation: [
      'MHVAC1',
    ]
  }
};
<commit_msg>VA appointment health records copy change. (#6304)<commit_after>module.exports = {
  reportTypes: {
    appointments: {
      title: 'Appointments',
      children: [
        {
          value: 'futureappointments',
          label: 'Upcoming VA appointments',
        },
        {
          value: 'pastappointments',
          label: 'VA appointment history (limited to the past 2 years)',
        },
      ],
    },
    medications: {
      title: 'Medications (includes allergy information)',
      children: [
        {
          value: 'prescriptions',
          label: 'VA Medication History',
        },
        {
          value: 'medications',
          label: 'Self-Reported Medications and Supplements',
        },
      ],
    },
    labs: {
      title: 'Labs and Tests',
      children: [
        {
          value: 'labsandtests',
          label: 'VA Laboratory Results',
          hold: 3,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. VA lab results will be available 3 calendar days after they’ve been verified.',
        },
        {
          value: 'vapathology',
          label: 'VA Pathology Reports',
          hold: 14,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. VA pathology reports will be available 14 calendar days after they’ve been completed. Studies done at a non-VA facility may not be available or may not include an interpretation.',
        },
        {
          value: 'varadiology',
          label: 'VA Radiology Reports',
          hold: 3,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. VA radiology reports will be available 3 calendar days after they’ve been completed. Studies done at a non-VA facility may not be available or may not include an interpretation.',
        },
        {
          value: 'vaekg',
          label: 'History of Electrocardiograms (EKGs) performed at VA facilities',
        },
        {
          value: 'vachemlabs',
          label: 'Self-Reported Labs and Tests',
        },
      ],
    },
    ehr: {
      title: 'VA Electronic Health Record History and Wellness Reminders',
      children: [
        {
          value: 'vaproblemlist',
          label: 'VA Problem List',
          hold: 3,
          holdExplanation: 'Your VA problem list contains active health problems that your VA health care team is helping you manage. This information will be available 3 days after it’s been entered.',
        },
        {
          value: 'vaadmissionsanddischarges',
          label: 'VA Admissions and Discharges',
          hold: 3,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. Discharge summaries will be available 3 days after they’re completed.',
        },
        {
          value: 'vaprogressnotes',
          label: 'VA Notes starting from Jan 01, 2013',
          hold: 3,
          holdExplanation: 'Some information may need to be reviewed by a member of your VA health care team before it’s added to your record. VA notes starting from Jan 1, 2013 will be available 3 days after they’ve been completed and signed by all required members of your VA health care team.',
        },
        {
          value: 'wellness',
          label: 'VA Wellness Reminders',
        },
      ],
    },
    allergies: {
      title: 'Allergies',
      children: [
        {
          value: 'vaallergies',
          label: 'VA Allergies',
        },
        {
          value: 'seiallergies',
          label: 'Self-Reported Allergies',
        },
      ],
    },
    immunizations: {
      title: 'Immunizations',
      children: [
        {
          value: 'vaimmunizations',
          label: 'VA Immunizations',
        },
        {
          value: 'seiimmunizations',
          label: 'Self-Reported Immunizations',
        },
      ],
    },
    vitals: {
      title: 'Vitals and Readings',
      children: [
        {
          value: 'vitalsandreadings',
          label: 'VA Vitals and Readings',
        },
        {
          value: 'vahth',
          label: 'Self-Reported Vitals and Readings',
        },
      ],
    },
    selfReported: {
      title: 'Self-Reported Health History',
      children: [
        {
          value: 'medicalevents',
          label: 'Medical Events',
        },
        {
          value: 'familyhealthhistory',
          label: 'Family Health History',
        },
        {
          value: 'militaryhealthhistory',
          label: 'Military Health History',
        },
        {
          value: 'treatmentfacilities',
          label: 'Treatment Facilities',
        },
        {
          value: 'healthcareproviders',
          label: 'Health Care Providers',
        },
      ],
    },
    food: {
      title: 'Food and Activity Journals',
      children: [
        {
          value: 'seiactivityjournal',
          label: 'Self-Reported Activity Journal',
        },
        {
          value: 'seifoodjournal',
          label: 'Self-Reported Food Journal',
        },
      ],
    },
    goals: {
      title: 'Goals',
      children: [
        {
          value: 'seimygoalscurrent',
          label: 'Self-Reported Current Goals',
        },
        {
          value: 'seimygoalscompleted',
          label: 'Self-Reported Completed Goals',
        },
      ],
    },
    demographics: {
      title: 'Demographics and Health Insurance',
      children: [
        {
          value: 'vademographics',
          label: 'VA Demographics from VA Treating Facilities in the last 3 years',
        },
        {
          value: 'seidemographics',
          label: 'Self-Reported Demographics',
        },
        {
          value: 'healthinsurance',
          label: 'Self-Reported Health Insurance',
        },
      ],
    },
    dod: {
      title: 'Department of Defense',
      children: [
        {
          value: 'dodmilitaryservice',
          label: 'Information from the Department of Defense',
        },
      ]
    },
  },

  errorCodes: {
    accountcreation: [
      'MHVAC1',
    ]
  }
};
<|endoftext|>


<commit_before>/*
Copyright (C) 2016 andrea rota <a@xelera.eu>

The core D3 collapsible tree code is Copyright (C) Mike Bostock 2016,
distributed under the GNU GPLv3 and published here:
https://gist.github.com/mbostock/4339083

This file is part of Pattrn - http://pattrn.co/.

Pattrn is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Pattrn is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with Pattrn.  If not, see <http://www.gnu.org/licenses/>.
*/

import {
  is_defined
} from '../utils/is_defined.js';
import {
  add_node_id_to_tree_nodes,
  list_tree_variable
} from '../pattrn_data.js';

var d3 = require('d3');

/**
 * handle tree charts. currently just a stub.
 */
export function pattrn_tree_chart(index, dataset, chart_settings, pattrn_objects, tree_data) {
  /**
   * Parameters passed in and defaults
   */
  // default from legacy code; originally hardcoded in each code snippet: 300, except line_chart_03 (150)
  var chart_width = chart_settings.width || chart_settings.scatterWidth || 300;
  // default from legacy code, defined as chartHeight within the main consume_table() function
  var chart_height = chart_settings.height || 350;
  var chart_transition_duration = chart_settings.transition_duration || 750;

  // transition from legacy: assign first to scope variable - just use the
  // chart_settings.fields.field_name var when refactoring
  // var tree_field_name_X = chart_settings.fields.field_name;

  var tree_chart_0X_title = document.getElementById(chart_settings.elements.title);
  tree_chart_0X_title.innerHTML = "Events by " + chart_settings.fields.field_title;
  var tree_chart_0X_chartTitle = document.getElementById(chart_settings.elements.chart_title).innerHTML = "Events by " + chart_settings.fields.field_title;

  var width = chart_width;
  var height = chart_height;
  var maxLabel = 150;
  var duration = chart_transition_duration;
  var radius = 5;

  var i = 0;
  var root;

  // crossfilter dimension and group
  window.tree_dimension = pattrn_objects.xf.dimension((d) => {
    return is_defined(d[tree_data.field_name.id]) ? d[tree_data.field_name.id] : 0;
  });
  // @x-technical-debt: allow for scenarios where a count is defined (rather than just returning 1 for counts as in this first iteration)
  window.tree_group = tree_dimension.group().reduceSum((d) => {
    return 1;
  });

  var tree = d3.layout.tree()
    .size([height, width]);

  var diagonal = d3.svg.diagonal()
    .projection(function(d) {
      return [d.y, d.x];
    });

  var svg = d3.select(chart_settings.elements.d3_chart).append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(" + maxLabel + ",0)");

  root = tree_data.tree_data;
  root.x0 = height / 2;
  root.y0 = 0;

  // initialize tree by setting each node's selected attribute to true
  root = walk_tree((node) => {
    node.selected = true;
    return node;
  }, root, ['children']);

  root.children.forEach(collapse);

  update(root);

  /**
   * update chart when crossfilter is updated
   * @x-technical-debt: refactor to use D3v4
   */
  pattrn_objects.dispatch.on('filter', () => {
    update(root);
  });

  function update(source) {
    // calculate size of nodes
    root = walk_tree(
      (node) => {
        // @x-technical-debt: check first that xf item exists, before trying to read its value field
        var node_size = window.tree_group.all().find((item) => {
          return item.key === node.mid;
        }).value;
        var children = node.children || node._children;

        var children_size = 0;

        if (Array.isArray(children)) {
          children_size = children.reduce((p, c) => {
            return c.selected ? p + c.size : p;
          }, 0);
        }

        node.size = node.selected ? node_size + children_size : children_size;

        return node;
      },
      root, ['children', '_children']
    );

    // Compute the new tree layout.
    var nodes = tree.nodes(root).reverse();
    var links = tree.links(nodes);

    // Normalize for fixed-depth.
    nodes.forEach(function(d) {
      d.y = d.depth * maxLabel;
    });

    // Update the nodes…
    svg.selectAll("g.node").remove();
    var node = svg.selectAll("g.node")
      .data(nodes, function(d) {
        return d.id || (d.id = ++i);
      });

    // Enter any new nodes at the parent's previous position.
    var nodeEnter = node.enter()
      .append("g")
      .attr("class", "node")
      .attr("transform", function(d) {
        return "translate(" + source.y0 + "," + source.x0 + ")";
      })
      .on("click", click);

    nodeEnter.append("circle")
      .attr("r", 0)
      .style("fill", (d) => {
        return d._children ? "lightsteelblue" : "lightgrey";
      })
      .style("stroke", (d) => {
        return d.selected ? "#ffcc00" : "lightgrey";
      });

    nodeEnter.append("text")
      .attr("x", function(d) {
        var spacing = computeRadius(d) + 5;
        return d.children || d._children ? -spacing : spacing;
      })
      .attr("dy", "3")
      .attr("text-anchor", function(d) {
        return d.children || d._children ? "end" : "start";
      })
      .text(function(d) {
        return d.size && d.selected ? `${d.name} (${d.size})` : `${d.name}`;
      })
      .style("fill-opacity", 0);

    // Transition nodes to their new position.
    var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) {
        return "translate(" + d.y + "," + d.x + ")";
      });

    nodeUpdate.select("circle")
      .attr("r", function(d) {
        return radius;
      })
      .style("fill", function(d) {
        return d._children ? "steelblue" : "lightgrey";
      })
      .style("stroke", (d) => {
        return d.selected ? "#ffcc00" : "lightgrey";
      });

    nodeUpdate.select("text").style("fill-opacity", 1);

    // Transition exiting nodes to the parent's new position.
    var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) {
        return "translate(" + source.y + "," + source.x + ")";
      })
      .remove();

    nodeExit.select("circle").attr("r", 0);
    nodeExit.select("text").style("fill-opacity", 0);

    // Update the links…
    var link = svg.selectAll("path.link")
      .data(links, function(d) {
        return d.target.id;
      });

    // Enter any new links at the parent's previous position.
    link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", function(d) {
        var o = {
          x: source.x0,
          y: source.y0
        };
        return diagonal({
          source: o,
          target: o
        });
      });

    // Transition links to their new position.
    link.transition()
      .duration(duration)
      .attr("d", diagonal);

    // Transition exiting nodes to the parent's new position.
    link.exit().transition()
      .duration(duration)
      .attr("d", function(d) {
        var o = {
          x: source.x,
          y: source.y
        };
        return diagonal({
          source: o,
          target: o
        });
      })
      .remove();

    // Stash the old positions for transition.
    nodes.forEach(function(d) {
      d.x0 = d.x;
      d.y0 = d.y;
    });
  }

  function computeRadius(d) {
    if (d.children || d._children) return radius + (radius * nbEndNodes(d) / 10);
    else return radius;
  }

  /**
   * Dispatch click events: if ctrl key is pressed, user wants to toggle
   * data; if ctrl key is not pressed, user wants to toggle visibility
   * of subtree
   */
  function click(d) {
    if (event.ctrlKey) {
      toggle_data(d);
    } else {
      toggle_visibility(d);
    }
  }

  function toggle_visibility(d) {
    if (d.children) {
      d._children = d.children;
      d.children = null;
    } else {
      d.children = d._children;
      d._children = null;
    }
    update(d);
  }

  function toggle_data(d) {
    d = walk_tree(
      (node) => {
        node.selected = !node.selected;
        return node;
      },
      d, ['children', '_children']
    );

    update(d);
  }
}

/**
 * For a given node of a tree, count events associated directly with the node,
 * plus all events of all its descendents.
 * Rather than traversing the whole subtree of descendents for each node, we
 * rely on the counts of a node's immediate children and sum their sum to
 * the node's own count.
 */
function countEventsIncludingDescendants(node) {

}

function nbEndNodes(n) {
  var nb = 0;
  if (n.children) {
    n.children.forEach(function(c) {
      nb += nbEndNodes(c);
    });
  } else if (n._children) {
    n._children.forEach(function(c) {
      nb += nbEndNodes(c);
    });
  } else nb++;

  return nb;
}

function collapse(d) {
  if (d.children) {
    d._children = d.children;
    d._children.forEach(collapse);
    d.children = null;
  }
}

/**
 * recursive reduce
 * used to build the tree walker which calculates size of subtrees
 */
function rec_reduce(fn, base, children_members) {
  var children_member = children_members.filter((member) => {
    return is_defined(base[member]) && base[member].length;
  });

  if (children_member.length === 0) {
    return fn(base);
  } else {
    return base[children_member[0]].map((item) => {
      return rec_reduce(fn, item, children_members);
    });
  }
}

/**
 * recursive tree walker
 */
function walk_tree(fn, base, children_members) {
  var children;
  var children_member = children_members.filter((member) => {
    return is_defined(base[member]) && base[member].length;
  });

  if (children_member.length === 0) {
    return fn(base);
  } else {
    base[children_member[0]] = base[children_member[0]].map((item) => {
      return walk_tree(fn, item, children_members);
    });
    return fn(base);
  }
}

function get_node_size(node) {
  var xf_node = window.tree_group.all().find(function(item) {
    return item.key === node.mid;
  });
  return is_defined(xf_node) ? xf_node.value : null;
}

function flatten(ary) {
  var ret = [];

  // Avoid treating strings as arrays - if this not an array, just return an empty array
  if (!Array.isArray(ary)) return ret;

  for (var i = 0; i < ary.length; i++) {
    if (Array.isArray(ary[i])) {
      ret = ret.concat(flatten(ary[i]));
    } else {
      ret.push(ary[i]);
    }
  }
  return ret;
}
<commit_msg>use selected nodes on tree for crossfilter<commit_after>/*
Copyright (C) 2016 andrea rota <a@xelera.eu>

The core D3 collapsible tree code is Copyright (C) Mike Bostock 2016,
distributed under the GNU GPLv3 and published here:
https://gist.github.com/mbostock/4339083

This file is part of Pattrn - http://pattrn.co/.

Pattrn is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Pattrn is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with Pattrn.  If not, see <http://www.gnu.org/licenses/>.
*/

import {
  is_defined
} from '../utils/is_defined.js';
import {
  add_node_id_to_tree_nodes,
  list_tree_variable
} from '../pattrn_data.js';

var d3 = require('d3');

/**
 * handle tree charts. currently just a stub.
 */
export function pattrn_tree_chart(index, dataset, chart_settings, pattrn_objects, tree_data) {
  /**
   * Parameters passed in and defaults
   */
  // default from legacy code; originally hardcoded in each code snippet: 300, except line_chart_03 (150)
  var chart_width = chart_settings.width || chart_settings.scatterWidth || 300;
  // default from legacy code, defined as chartHeight within the main consume_table() function
  var chart_height = chart_settings.height || 350;
  var chart_transition_duration = chart_settings.transition_duration || 750;

  // transition from legacy: assign first to scope variable - just use the
  // chart_settings.fields.field_name var when refactoring
  // var tree_field_name_X = chart_settings.fields.field_name;

  var tree_chart_0X_title = document.getElementById(chart_settings.elements.title);
  tree_chart_0X_title.innerHTML = "Events by " + chart_settings.fields.field_title;
  var tree_chart_0X_chartTitle = document.getElementById(chart_settings.elements.chart_title).innerHTML = "Events by " + chart_settings.fields.field_title;

  var width = chart_width;
  var height = chart_height;
  var maxLabel = 150;
  var duration = chart_transition_duration;
  var radius = 5;

  var i = 0;
  var root;

  // crossfilter dimension and group
  window.tree_dimension = pattrn_objects.xf.dimension((d) => {
    return is_defined(d[tree_data.field_name.id]) ? d[tree_data.field_name.id] : 0;
  });
  // @x-technical-debt: allow for scenarios where a count is defined (rather than just returning 1 for counts as in this first iteration)
  window.tree_group = tree_dimension.group().reduceSum((d) => {
    return 1;
  });

  var tree = d3.layout.tree()
    .size([height, width]);

  var diagonal = d3.svg.diagonal()
    .projection(function(d) {
      return [d.y, d.x];
    });

  var svg = d3.select(chart_settings.elements.d3_chart).append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(" + maxLabel + ",0)");

  root = tree_data.tree_data;
  root.x0 = height / 2;
  root.y0 = 0;

  // initialize tree by setting each node's selected attribute to true
  root = walk_tree((node) => {
    node.selected = true;
    return node;
  }, root, ['children']);

  root.children.forEach(collapse);

  update(root);

  /**
   * update chart when crossfilter is updated
   * @x-technical-debt: refactor to use D3v4
   */
  pattrn_objects.dispatch.on('filter', () => {
    update(root);
  });

  function update(source) {
    // calculate size of nodes
    root = walk_tree(
      (node) => {
        // @x-technical-debt: check first that xf item exists, before trying to read its value field
        var node_size = window.tree_group.all().find((item) => {
          return item.key === node.mid;
        }).value;
        var children = node.children || node._children;

        var children_size = 0;

        if (Array.isArray(children)) {
          children_size = children.reduce((p, c) => {
            return c.selected ? p + c.size : p;
          }, 0);
        }

        node.size = node.selected ? node_size + children_size : children_size;

        return node;
      },
      root, ['children', '_children']
    );

    // Compute the new tree layout.
    var nodes = tree.nodes(root).reverse();
    var links = tree.links(nodes);

    // Normalize for fixed-depth.
    nodes.forEach(function(d) {
      d.y = d.depth * maxLabel;
    });

    // Update the nodes…
    svg.selectAll("g.node").remove();
    var node = svg.selectAll("g.node")
      .data(nodes, function(d) {
        return d.id || (d.id = ++i);
      });

    // Enter any new nodes at the parent's previous position.
    var nodeEnter = node.enter()
      .append("g")
      .attr("class", "node")
      .attr("transform", function(d) {
        return "translate(" + source.y0 + "," + source.x0 + ")";
      })
      .on("click", click);

    nodeEnter.append("circle")
      .attr("r", 0)
      .style("fill", (d) => {
        return d._children ? "lightsteelblue" : "lightgrey";
      })
      .style("stroke", (d) => {
        return d.selected ? "#ffcc00" : "lightgrey";
      });

    nodeEnter.append("text")
      .attr("x", function(d) {
        var spacing = computeRadius(d) + 5;
        return d.children || d._children ? -spacing : spacing;
      })
      .attr("dy", "3")
      .attr("text-anchor", function(d) {
        return d.children || d._children ? "end" : "start";
      })
      .text(function(d) {
        return d.size && d.selected ? `${d.name} (${d.size})` : `${d.name}`;
      })
      .style("fill-opacity", 0);

    // Transition nodes to their new position.
    var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) {
        return "translate(" + d.y + "," + d.x + ")";
      });

    nodeUpdate.select("circle")
      .attr("r", function(d) {
        return radius;
      })
      .style("fill", function(d) {
        return d._children ? "steelblue" : "lightgrey";
      })
      .style("stroke", (d) => {
        return d.selected ? "#ffcc00" : "lightgrey";
      });

    nodeUpdate.select("text").style("fill-opacity", 1);

    // Transition exiting nodes to the parent's new position.
    var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) {
        return "translate(" + source.y + "," + source.x + ")";
      })
      .remove();

    nodeExit.select("circle").attr("r", 0);
    nodeExit.select("text").style("fill-opacity", 0);

    // Update the links…
    var link = svg.selectAll("path.link")
      .data(links, function(d) {
        return d.target.id;
      });

    // Enter any new links at the parent's previous position.
    link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", function(d) {
        var o = {
          x: source.x0,
          y: source.y0
        };
        return diagonal({
          source: o,
          target: o
        });
      });

    // Transition links to their new position.
    link.transition()
      .duration(duration)
      .attr("d", diagonal);

    // Transition exiting nodes to the parent's new position.
    link.exit().transition()
      .duration(duration)
      .attr("d", function(d) {
        var o = {
          x: source.x,
          y: source.y
        };
        return diagonal({
          source: o,
          target: o
        });
      })
      .remove();

    // apply filter to dimension: only cases whose mid is amongst those
    // currently selected
    var selected_nodes_mids = flatten(
      rec_reduce(
        (item) => {
          return item.selected ? item.mid : null;
        },
        root,
        [ '_children', 'children' ])
      );

    window.tree_dimension.filter((data_row) => {
      var filtered = selected_nodes_mids.indexOf(data_row) >= 0;
      return filtered;
    });

    // Stash the old positions for transition.
    nodes.forEach(function(d) {
      d.x0 = d.x;
      d.y0 = d.y;
    });
  }

  function computeRadius(d) {
    if (d.children || d._children) return radius + (radius * nbEndNodes(d) / 10);
    else return radius;
  }

  /**
   * Dispatch click events: if ctrl key is pressed, user wants to toggle
   * data; if ctrl key is not pressed, user wants to toggle visibility
   * of subtree
   */
  function click(d) {
    if (event.ctrlKey) {
      toggle_data(d);
    } else {
      toggle_visibility(d);
    }
  }

  function toggle_visibility(d) {
    if (d.children) {
      d._children = d.children;
      d.children = null;
    } else {
      d.children = d._children;
      d._children = null;
    }
    update(d);
  }

  function toggle_data(d) {
    d = walk_tree(
      (node) => {
        node.selected = !node.selected;
        return node;
      },
      d, ['children', '_children']
    );

    update(d);
  }
}

/**
 * For a given node of a tree, count events associated directly with the node,
 * plus all events of all its descendents.
 * Rather than traversing the whole subtree of descendents for each node, we
 * rely on the counts of a node's immediate children and sum their sum to
 * the node's own count.
 */
function countEventsIncludingDescendants(node) {

}

function nbEndNodes(n) {
  var nb = 0;
  if (n.children) {
    n.children.forEach(function(c) {
      nb += nbEndNodes(c);
    });
  } else if (n._children) {
    n._children.forEach(function(c) {
      nb += nbEndNodes(c);
    });
  } else nb++;

  return nb;
}

function collapse(d) {
  if (d.children) {
    d._children = d.children;
    d._children.forEach(collapse);
    d.children = null;
  }
}

/**
 * recursive reduce
 * used to build the tree walker which calculates size of subtrees
 */
function rec_reduce(fn, base, children_members) {
  var children_member = children_members.filter((member) => {
    return is_defined(base[member]) && base[member].length;
  });

  if (children_member.length === 0) {
    return fn(base);
  } else {
    return base[children_member[0]].map((item) => {
      return rec_reduce(fn, item, children_members);
    });
  }
}

/**
 * recursive tree walker
 */
function walk_tree(fn, base, children_members) {
  var children;
  var children_member = children_members.filter((member) => {
    return is_defined(base[member]) && base[member].length;
  });

  if (children_member.length === 0) {
    return fn(base);
  } else {
    base[children_member[0]] = base[children_member[0]].map((item) => {
      return walk_tree(fn, item, children_members);
    });
    return fn(base);
  }
}

function get_node_size(node) {
  var xf_node = window.tree_group.all().find(function(item) {
    return item.key === node.mid;
  });
  return is_defined(xf_node) ? xf_node.value : null;
}

function flatten(ary) {
  var ret = [];

  // Avoid treating strings as arrays - if this not an array, just return an empty array
  if (!Array.isArray(ary)) return ret;

  for (var i = 0; i < ary.length; i++) {
    if (Array.isArray(ary[i])) {
      ret = ret.concat(flatten(ary[i]));
    } else {
      ret.push(ary[i]);
    }
  }
  return ret;
}
<|endoftext|>