<commit_before>/*
 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
 *
 * Starts up an HTTP server and adds a route where a server allocation can be
 * requested. It validates the request, then passes it on to an algorithm
 * pipeline to filter down the servers to the most suitable server to fulfill
 * the request.
 */

var assert = require('assert');
var restify = require('restify');
var Logger = require('bunyan');
var util = require('util');
var path = require('path');
var algorithms = require('./algorithms');

var UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
var ALGORITHMS_PATH = './algorithms/';
var DEFAULT_ALGORITHMS = [ 'hard-filter-min-ram', 'hard-filter-running',
                           'hard-filter-setup', 'hard-filter-reserved',
                           'soft-filter-recent-servers', 'sort-2adic',
                           'pick-weighted-random' ];

/*
 * HTTP constructor
 */
function HTTP(options) {
    this.config = options;
    this.algorithms = [];
    this.log = null;
    this.server = null;

    this._init();
}

/*
 * HTTP init code. Connects HTTP routes, loads algorithm pipeline, and starts up
 * HTTP server.
 *
 * Will throw exception when config is bad
 */
HTTP.prototype._init =
function () {
    var self = this;

    self.log = new Logger({
        name: 'dapi',
        level: self.config.logLevel,
        serializers: {
            err: Logger.stdSerializers.err,
            req: Logger.stdSerializers.req,
            res: restify.bunyan.serializers.response
        }
    });

    self.server = restify.createServer({
        name: 'Designation API',
        log: self.log,
        version: self.config.version,
        serverName: 'SmartDataCenter',
        accept: ['text/plain',
                 'application/json',
                 'text/html',
                 'image/png',
                 'text/css'],
        contentWriters: {
            'text/plain': function (obj) {
                if (!obj)
                    return '';
                if (typeof (obj) === 'string')
                    return obj;
                return JSON.stringify(obj, null, 2);
            }
        }
    });

    self.server.on('after', restify.auditLogger({log: self.log, body: true}));
    self.server.on('uncaughtException', function (req, res, route, error) {
        req.log.info({
            err: error,
            url: req.url,
            params: req.params
        });

        res.send(new restify.InternalError('Internal Server Error'));
    });

    self._loadAlgorithms();
    self._setMiddleware();
    self._setRoutes();
};

/*
 * Sets custom middlewares to use for the API
 */
HTTP.prototype._setMiddleware =
function () {
    this.server.use(restify.acceptParser(this.server.acceptable));
    this.server.use(restify.bodyParser());
    this.server.use(restify.queryParser());
};

/*
 * Sets all routes for the HTTP server
 */
HTTP.prototype._setRoutes =
function () {
    var self = this;

    function addProxies(req, res, next) {
        req.config = self.config;
        req.algorithms = self.algorithms;

        return next();
    }

    var before = [
        addProxies
    ];

    self.server.post({ path: '/allocation', name: 'Allocation'},
                     before,
                     self._validateVmPayload,
                     self._validateServers,
                     self._allocate);
};

/*
 * Loads all algorithms listed in the config file. If no algorithms is listed
 * then a default algorithm pipeline will be used.
 */
HTTP.prototype._loadAlgorithms =
function () {
    var self = this;

    var algorithmFiles = self.config.algorithms;
    if (!algorithmFiles) {
        self.log.info('No algorithms listed, using defaults');
        algorithmFiles = DEFAULT_ALGORITHMS;
    }

    for (var i = 0; i < algorithmFiles.length; i++) {
        var fileName = algorithmFiles[i];
        var algorithm = self._loadAlgorithm(fileName);

        if (algorithm)
            self.algorithms.push(algorithm);
    }

    if (!self.algorithms.length) {
        self.log.error('No valid algorithms found');
        process.exit(1);
    } else {
        var algoNames = self.algorithms.map(function (algo) {
            return algo.name;
        });

        self.log.info('Loaded the following algorithms: ', algoNames);
    }
};

/*
 * Load an algorithm from a file. Return the algorithm if valid.
 */
HTTP.prototype._loadAlgorithm =
function (fileName) {
    var self = this;
    var algoPath = ALGORITHMS_PATH + fileName;

    try {
        var algorithm = require(algoPath);
    } catch (e) {
        if (e.code !== 'MODULE_NOT_FOUND')
            throw (e);

        self.log.error('Algorithm not found: ', algoPath);
        return null;
    }

    if (!algorithm.run || typeof (algorithm.run) !== 'function') {
        /* JSSTYLED */
        self.log.error("Algorithm '%s' does not have a run function", fileName);
        return null;
    } else if (!algorithm.name || typeof (algorithm.name) != 'string') {
        /* JSSTYLED */
        self.log.error("Algorithm '%s' does not have a name", fileName);
        return null;
    } else {
        /* JSSTYLED */
        self.log.info("Algorithm '%s' has been loaded", fileName);
        return algorithm;
    }
};

/*
 * Validates the 'vm' payload parameter for correct JSON required properties
 */
HTTP.prototype._validateVmPayload =
function (req, res, next) {
    req.log.trace('validateVmPayload start');

    var vm = req.params.vm;

    if (!vm)
        return next(new restify.MissingParameterError('"vm" is required'));

    var msg = validateVm(vm);
    if (msg)
        return next(new restify.InvalidArgumentError(msg));

    req.vm = vm;
    return next();
};

/*
 * Validates the servers parameter for correct JSON required properties
 */
HTTP.prototype._validateServers =
function (req, res, next) {
    req.log.trace('validateServers start');

    var validServers = [];
    var servers = req.params.servers;

    if (!servers) {
        return next(
            new restify.MissingParameterError('"servers" are required'));
    }

    if (!Array.isArray(servers)) {
        return next(
        new restify.MissingParameterError('"servers" input is not an array'));
    }

    if (servers.length === 0) {
        return next(
            new restify.MissingParameterError('"servers" array is empty'));
    }

    for (var i = 0; i < servers.length; i++) {
        var server = servers[i];
        var msg = validateServer(server);
        if (!msg) {
            validServers.push(server);
        } else {
            return next(new restify.InvalidArgumentError(msg));
        }
    }

    if (!validServers.length) {
        return next(
            new restify.InvalidArgumentError('No valid "servers" found'));
    }

    req.servers = validServers;
    return next();
};

/*
 * Given the request details, returns an appropriate server which the VM should
 * be allocated to.
 */
HTTP.prototype._allocate =
function (req, res, next) {
    req.log.trace('Allocation start');

    var vm = req.params.vm;
    var server = algorithms.allocate(req.log, req.algorithms, req.servers,
                                     vm.ram, vm.nic_tags);

    if (!server) {
        return next(
            new restify.
                InvalidArgumentError('No allocatable servers found'));
    }

    res.send(server);
    return next();
};

/*
 * Validates if server is a valid server object. An example of a simplified
 * server object:
 *
 * uuid: 564d89c4-e368-1a0b-564f-b8a33f47c134
 * hostname: headnode
 * memory_total_bytes: 2943930368
 * memory_available_bytes: 1544368128
 * reserved: true
 * cpucores: 2
 * os: 20120319T230411Z
 * cpuvirtualization: none
 * status: running
 * headnode: true
 */
function validateServer(server) {
    if (typeof (server) !== 'object')
        return 'Server object is an invalid type';

    if (typeof (server.uuid) !== 'string')
        return 'Server UUID is not a string';

    if (!UUID_RE.test(server.uuid))
        return 'Server UUID ' + server.uuid + ' is not a valid UUID';

    if (typeof (server['memory_available_bytes']) !== 'number')
        return 'Server ' + server.uuid +
               ' memory_available_bytes is not a number';

    if (typeof (server['memory_total_bytes']) !== 'number')
        return 'Server ' + server.uuid + ' memory_total_bytes is not a number';

    if (typeof (server['reserved']) !== 'boolean')
        return 'Server ' + server.uuid + ' "reserved" is not a boolean';

    if (typeof (server['setup']) !== 'boolean')
        return 'Server ' + server.uuid + ' "setup" is not a boolean';

    return null;
}

/*
 * Validates if a vm is a valid vm object.
 * Pretty simple validation at the moment: just check for ram.
 */
function validateVm(vm) {
    if (typeof (vm) !== 'object')
        return 'VM object is an invalid type';

    if (typeof (vm.ram) !== 'number')
        return 'VM "ram" is not a number';

    return null;
}

/*
 * Starts listening on the port given specified by config.api.port.
 */
HTTP.prototype.listen =
function (callback) {
    var self = this;

    self.server.listen(this.config.api.port, '0.0.0.0', function () {
        self.log.info({ url: self.server.url },
                      '%s listening', self.server.name);

        if (callback)
            callback();

        return;
    });
};

module.exports = HTTP;
<commit_msg>DAPI-70 - disable hard failure on failed server validation.<commit_after>/*
 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
 *
 * Starts up an HTTP server and adds a route where a server allocation can be
 * requested. It validates the request, then passes it on to an algorithm
 * pipeline to filter down the servers to the most suitable server to fulfill
 * the request.
 */

var assert = require('assert');
var restify = require('restify');
var Logger = require('bunyan');
var util = require('util');
var path = require('path');
var algorithms = require('./algorithms');

var UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
var ALGORITHMS_PATH = './algorithms/';
var DEFAULT_ALGORITHMS = [ 'hard-filter-min-ram', 'hard-filter-running',
                           'hard-filter-setup', 'hard-filter-reserved',
                           'soft-filter-recent-servers', 'sort-2adic',
                           'pick-weighted-random' ];

/*
 * HTTP constructor
 */
function HTTP(options) {
    this.config = options;
    this.algorithms = [];
    this.log = null;
    this.server = null;

    this._init();
}

/*
 * HTTP init code. Connects HTTP routes, loads algorithm pipeline, and starts up
 * HTTP server.
 *
 * Will throw exception when config is bad
 */
HTTP.prototype._init =
function () {
    var self = this;

    self.log = new Logger({
        name: 'dapi',
        level: self.config.logLevel,
        serializers: {
            err: Logger.stdSerializers.err,
            req: Logger.stdSerializers.req,
            res: restify.bunyan.serializers.response
        }
    });

    self.server = restify.createServer({
        name: 'Designation API',
        log: self.log,
        version: self.config.version,
        serverName: 'SmartDataCenter',
        accept: ['text/plain',
                 'application/json',
                 'text/html',
                 'image/png',
                 'text/css'],
        contentWriters: {
            'text/plain': function (obj) {
                if (!obj)
                    return '';
                if (typeof (obj) === 'string')
                    return obj;
                return JSON.stringify(obj, null, 2);
            }
        }
    });

    self.server.on('after', restify.auditLogger({log: self.log, body: true}));
    self.server.on('uncaughtException', function (req, res, route, error) {
        req.log.info({
            err: error,
            url: req.url,
            params: req.params
        });

        res.send(new restify.InternalError('Internal Server Error'));
    });

    self._loadAlgorithms();
    self._setMiddleware();
    self._setRoutes();
};

/*
 * Sets custom middlewares to use for the API
 */
HTTP.prototype._setMiddleware =
function () {
    this.server.use(restify.acceptParser(this.server.acceptable));
    this.server.use(restify.bodyParser());
    this.server.use(restify.queryParser());
};

/*
 * Sets all routes for the HTTP server
 */
HTTP.prototype._setRoutes =
function () {
    var self = this;

    function addProxies(req, res, next) {
        req.config = self.config;
        req.algorithms = self.algorithms;

        return next();
    }

    var before = [
        addProxies
    ];

    self.server.post({ path: '/allocation', name: 'Allocation'},
                     before,
                     self._validateVmPayload,
                     self._validateServers,
                     self._allocate);
};

/*
 * Loads all algorithms listed in the config file. If no algorithms is listed
 * then a default algorithm pipeline will be used.
 */
HTTP.prototype._loadAlgorithms =
function () {
    var self = this;

    var algorithmFiles = self.config.algorithms;
    if (!algorithmFiles) {
        self.log.info('No algorithms listed, using defaults');
        algorithmFiles = DEFAULT_ALGORITHMS;
    }

    for (var i = 0; i < algorithmFiles.length; i++) {
        var fileName = algorithmFiles[i];
        var algorithm = self._loadAlgorithm(fileName);

        if (algorithm)
            self.algorithms.push(algorithm);
    }

    if (!self.algorithms.length) {
        self.log.error('No valid algorithms found');
        process.exit(1);
    } else {
        var algoNames = self.algorithms.map(function (algo) {
            return algo.name;
        });

        self.log.info('Loaded the following algorithms: ', algoNames);
    }
};

/*
 * Load an algorithm from a file. Return the algorithm if valid.
 */
HTTP.prototype._loadAlgorithm =
function (fileName) {
    var self = this;
    var algoPath = ALGORITHMS_PATH + fileName;

    try {
        var algorithm = require(algoPath);
    } catch (e) {
        if (e.code !== 'MODULE_NOT_FOUND')
            throw (e);

        self.log.error('Algorithm not found: ', algoPath);
        return null;
    }

    if (!algorithm.run || typeof (algorithm.run) !== 'function') {
        /* JSSTYLED */
        self.log.error("Algorithm '%s' does not have a run function", fileName);
        return null;
    } else if (!algorithm.name || typeof (algorithm.name) != 'string') {
        /* JSSTYLED */
        self.log.error("Algorithm '%s' does not have a name", fileName);
        return null;
    } else {
        /* JSSTYLED */
        self.log.info("Algorithm '%s' has been loaded", fileName);
        return algorithm;
    }
};

/*
 * Validates the 'vm' payload parameter for correct JSON required properties
 */
HTTP.prototype._validateVmPayload =
function (req, res, next) {
    req.log.trace('validateVmPayload start');

    var vm = req.params.vm;

    if (!vm)
        return next(new restify.MissingParameterError('"vm" is required'));

    var msg = validateVm(vm);
    if (msg)
        return next(new restify.InvalidArgumentError(msg));

    req.vm = vm;
    return next();
};

/*
 * Validates the servers parameter for correct JSON required properties
 */
HTTP.prototype._validateServers =
function (req, res, next) {
    req.log.trace('validateServers start');

    var validServers = [];
    var servers = req.params.servers;

    if (!servers) {
        return next(
            new restify.MissingParameterError('"servers" are required'));
    }

    if (!Array.isArray(servers)) {
        return next(
        new restify.MissingParameterError('"servers" input is not an array'));
    }

    if (servers.length === 0) {
        return next(
            new restify.MissingParameterError('"servers" array is empty'));
    }

    for (var i = 0; i < servers.length; i++) {
        var server = servers[i];
        var msg = validateServer(server);
        if (!msg) {
            validServers.push(server);
//        } else {
//            return next(new restify.InvalidArgumentError(msg));
        }
    }

    if (!validServers.length) {
        return next(
            new restify.InvalidArgumentError('No valid "servers" found'));
    }

    req.servers = validServers;
    return next();
};

/*
 * Given the request details, returns an appropriate server which the VM should
 * be allocated to.
 */
HTTP.prototype._allocate =
function (req, res, next) {
    req.log.trace('Allocation start');

    var vm = req.params.vm;
    var server = algorithms.allocate(req.log, req.algorithms, req.servers,
                                     vm.ram, vm.nic_tags);

    if (!server) {
        return next(
            new restify.
                InvalidArgumentError('No allocatable servers found'));
    }

    res.send(server);
    return next();
};

/*
 * Validates if server is a valid server object. An example of a simplified
 * server object:
 *
 * uuid: 564d89c4-e368-1a0b-564f-b8a33f47c134
 * hostname: headnode
 * memory_total_bytes: 2943930368
 * memory_available_bytes: 1544368128
 * reserved: true
 * cpucores: 2
 * os: 20120319T230411Z
 * cpuvirtualization: none
 * status: running
 * headnode: true
 */
function validateServer(server) {
    if (typeof (server) !== 'object')
        return 'Server object is an invalid type';

    if (typeof (server.uuid) !== 'string')
        return 'Server UUID is not a string';

    if (!UUID_RE.test(server.uuid))
        return 'Server UUID ' + server.uuid + ' is not a valid UUID';

    if (typeof (server['memory_available_bytes']) !== 'number')
        return 'Server ' + server.uuid +
               ' memory_available_bytes is not a number';

    if (typeof (server['memory_total_bytes']) !== 'number')
        return 'Server ' + server.uuid + ' memory_total_bytes is not a number';

    if (typeof (server['reserved']) !== 'boolean')
        return 'Server ' + server.uuid + ' "reserved" is not a boolean';

    if (typeof (server['setup']) !== 'boolean')
        return 'Server ' + server.uuid + ' "setup" is not a boolean';

    return null;
}

/*
 * Validates if a vm is a valid vm object.
 * Pretty simple validation at the moment: just check for ram.
 */
function validateVm(vm) {
    if (typeof (vm) !== 'object')
        return 'VM object is an invalid type';

    if (typeof (vm.ram) !== 'number')
        return 'VM "ram" is not a number';

    return null;
}

/*
 * Starts listening on the port given specified by config.api.port.
 */
HTTP.prototype.listen =
function (callback) {
    var self = this;

    self.server.listen(this.config.api.port, '0.0.0.0', function () {
        self.log.info({ url: self.server.url },
                      '%s listening', self.server.name);

        if (callback)
            callback();

        return;
    });
};

module.exports = HTTP;
<|endoftext|>


<commit_before>colors = [
	['AliceBlue', '#F0F8FF'],
	['AntiqueWhite', '#FAEBD7'],
	['Aqua', '#00FFFF'],
	['Aquamarine', '#7FFFD4'],
	['Azure', '#F0FFFF'],
	['Beige', '#F5F5DC'],
	['Bisque', '#FFE4C4'],
	['Black', '#000000'],
	['BlanchedAlmond', '#FFEBCD'],
	['Blue', '#0000FF'],
	['BlueViolet', '#8A2BE2'],
	['Brown', '#A52A2A'],
	['BurlyWood', '#DEB887'],
	['CadetBlue', '#5F9EA0'],
	['Chartreuse', '#7FFF00'],
	['Chocolate', '#D2691E'],
	['Coral', '#FF7F50'],
	['CornflowerBlue', '#6495ED'],
	['Cornsilk', '#FFF8DC'],
	['Crimson', '#DC143C'],
	['Cyan', '#00FFFF'],
	['DarkBlue', '#00008B'],
	['DarkCyan', '#008B8B'],
	['DarkGoldenRod', '#B8860B'],
	['DarkGrey', '#A9A9A9'],
	['DarkGreen', '#006400'],
	['DarkKhaki', '#BDB76B'],
	['DarkMagenta', '#8B008B'],
	['DarkOliveGreen', '#556B2F'],
	['DarkOrange', '#FF8C00'],
	['DarkOrchid', '#9932CC'],
	['DarkRed', '#8B0000'],
	['DarkSalmon', '#E9967A'],
	['DarkSeaGreen', '#8FBC8F'],
	['DarkSlateBlue', '#483D8B'],
	['DarkSlateGrey', '#2F4F4F'],
	['DarkTurquoise', '#00CED1'],
	['DarkViolet', '#9400D3'],
	['DeepPink', '#FF1493'],
	['DeepSkyBlue', '#00BFFF'],
	['DimGrey', '#696969'],
	['DodgerBlue', '#1E90FF'],
	['FireBrick', '#B22222'],
	['FloralWhite', '#FFFAF0'],
	['ForestGreen', '#228B22'],
	['Fuchsia', '#FF00FF'],
	['Gainsboro', '#DCDCDC'],
	['GhostWhite', '#F8F8FF'],
	['Gold', '#FFD700'],
	['GoldenRod', '#DAA520'],
	['Grey', '#808080'],
	['Green', '#008000'],
	['GreenYellow', '#ADFF2F'],
	['HoneyDew', '#F0FFF0'],
	['HotPink', '#FF69B4'],
	['IndianRed', '#CD5C5C'],
	['Indigo', '#4B0082'],
	['Ivory', '#FFFFF0'],
	['Khaki', '#F0E68C'],
	['Lavender', '#E6E6FA'],
	['LavenderBlush', '#FFF0F5'],
	['LawnGreen', '#7CFC00'],
	['LemonChiffon', '#FFFACD'],
	['LightBlue', '#ADD8E6'],
	['LightCoral', '#F08080'],
	['LightCyan', '#E0FFFF'],
	['LightGoldenRodYellow', '#FAFAD2'],
	['LightGrey', '#D3D3D3'],
	['LightGreen', '#90EE90'],
	['LightPink', '#FFB6C1'],
	['LightSalmon', '#FFA07A'],
	['LightSeaGreen', '#20B2AA'],
	['LightSkyBlue', '#87CEFA'],
	['LightSlateGrey', '#778899'],
	['LightSteelBlue', '#B0C4DE'],
	['LightYellow', '#FFFFE0'],
	['Lime', '#00FF00'],
	['LimeGreen', '#32CD32'],
	['Linen', '#FAF0E6'],
	['Magenta', '#FF00FF'],
	['Maroon', '#800000'],
	['MediumAquaMarine', '#66CDAA'],
	['MediumBlue', '#0000CD'],
	['MediumOrchid', '#BA55D3'],
	['MediumPurple', '#9370DB'],
	['MediumSeaGreen', '#3CB371'],
	['MediumSlateBlue', '#7B68EE'],
	['MediumSpringGreen', '#00FA9A'],
	['MediumTurquoise', '#48D1CC'],
	['MediumVioletRed', '#C71585'],
	['MidnightBlue', '#191970'],
	['MintCream', '#F5FFFA'],
	['MistyRose', '#FFE4E1'],
	['Moccasin', '#FFE4B5'],
	['NavajoWhite', '#FFDEAD'],
	['Navy', '#000080'],
	['OldLace', '#FDF5E6'],
	['Olive', '#808000'],
	['OliveDrab', '#6B8E23'],
	['Orange', '#FFA500'],
	['OrangeRed', '#FF4500'],
	['Orchid', '#DA70D6'],
	['PaleGoldenRod', '#EEE8AA'],
	['PaleGreen', '#98FB98'],
	['PaleTurquoise', '#AFEEEE'],
	['PaleVioletRed', '#DB7093'],
	['PapayaWhip', '#FFEFD5'],
	['PeachPuff', '#FFDAB9'],
	['Peru', '#CD853F'],
	['Pink', '#FFC0CB'],
	['Plum', '#DDA0DD'],
	['PowderBlue', '#B0E0E6'],
	['Purple', '#800080'],
	['RebeccaPurple', '#663399'],
	['Red', '#FF0000'],
	['RosyBrown', '#BC8F8F'],
	['RoyalBlue', '#4169E1'],
	['SaddleBrown', '#8B4513'],
	['Salmon', '#FA8072'],
	['SandyBrown', '#F4A460'],
	['SeaGreen', '#2E8B57'],
	['SeaShell', '#FFF5EE'],
	['Sienna', '#A0522D'],
	['Silver', '#C0C0C0'],
	['SkyBlue', '#87CEEB'],
	['SlateBlue', '#6A5ACD'],
	['SlateGrey', '#708090'],
	['Snow', '#FFFAFA'],
	['SpringGreen', '#00FF7F'],
	['SteelBlue', '#4682B4'],
	['Tan', '#D2B48C'],
	['Teal', '#008080'],
	['Thistle', '#D8BFD8'],
	['Tomato', '#FF6347'],
	['Turquoise', '#40E0D0'],
	['Violet', '#EE82EE'],
	['Wheat', '#F5DEB3'],
	['White', '#FFFFFF'],
	['WhiteSmoke', '#F5F5F5'],
	['Yellow', '#FFFF00'],
	['YellowGreen', '#9ACD32']
].map(function (color) {
    return {name: color[0], hex: color[1], rgb: hexToRGB(color[1])};
});

function hexToRGB(hex) {
    return {
        r: parseInt(hex.substring(1, 3), 16),
        g: parseInt(hex.substring(3, 5), 16),
        b: parseInt(hex.substring(5, 7), 16)
    };
}

function addEventHandler(obj, evt, handler) {
    if(obj.addEventListener) {
        // W3C method
        obj.addEventListener(evt, handler, false);
    } else if(obj.attachEvent) {
        // IE method.
        obj.attachEvent('on'+evt, function(e) {
            handler(e || window.event);
        });
    } else {
        // Old school method.
        obj['on'+evt] = handler;
    }
}

function nearestColor(color, colors) {
    var distance,
        minDistance = Infinity,
        rgb,
        nearest;

    for (var i = 0; i < colors.length; ++i) {
        rgb = colors[i].rgb;
        distance = Math.sqrt(
            Math.pow(color.r - rgb.r, 2) +
            Math.pow(color.g - rgb.g, 2) +
            Math.pow(color.b - rgb.b, 2)
        );

        if (distance < minDistance) {
            minDistance = distance;
            nearest = colors[i];
        }
    }

    return nearest;
}

function hrefFromColors(color1, color2) {
    return ('//' + color1.name + (color1 != color2 && color2.hex != 'transparent' ? ' /to_' + color2.name : '')).replace('Transparent', '').replace('// /', '/');
}

var pixels1 = [];
var pixels2 = [];

if(window.FileReader) {
    addEventHandler(window, 'load', function() {
        var frames = [];
        for (var i = 1; i <= 2; ++i) {
            var frame = {
                canvas: document.createElement('canvas'),
                drop: document.getElementsByClassName('dropZone')[i - 1],
                result: document.getElementById('result' + i),
                pixels: window['pixels' + i]
            };
            frame['ctx'] = frame.canvas.getContext('2d');
            frames.push(frame);
        };
        var button = document.getElementById('generateButton');

        function cancel(e) {
            if (e.preventDefault) {
                e.preventDefault();
            }
            if (e.stopPropagation) {
                e.stopPropagation();
            }
            return false;
        }

        function noDrag(e) {
            for (var i = 0; i < 2; ++i) {
                frames[i].drop.className = 'dropZone ' + (i == 0 ? 'left' : 'right');
            }
            return cancel(e);
        }

        function dragHint(e) {
            if (e.dataTransfer.files.length == 1 && e.dataTransfer.files[0].type.startsWith('image/')) {
                for (var i = 0; i < 2; ++i) {
                    frames[i].drop.className = 'dropZone dragHint ' + (i == 0 ? 'left' : 'right');
                }
            }
            return cancel(e);
        }

        addEventHandler(window, 'drop', noDrag);
        addEventHandler(window, 'dragover', dragHint);
        addEventHandler(window, 'dragenter', dragHint);
        addEventHandler(window, 'dragleave', noDrag);

        for (var i = 0; i < 2; ++i) (function () {

            var frameNr = i;
            var frame = frames[frameNr];
            addEventHandler(frame.drop, 'dragexit', dragHint);
            addEventHandler(frame.drop, 'dragleave', dragHint);

            addEventHandler(frame.drop, 'dragover', function (e) {
                cancel(e);
                if (e.dataTransfer.files.length == 1 && e.dataTransfer.files[0].type.startsWith('image/')) {
                    frame.drop.className = 'dropZone acceptDrag ' + (frameNr == 0 ? 'left' : 'right');
                    return true;
                }
                return false;
            });

            addEventHandler(frame.drop, 'drop', function (e) {
                var file = e.dataTransfer.files[0];

                if (e.dataTransfer.files.length == 1 && e.dataTransfer.files[0].type.startsWith('image/')) {
                    var reader = new FileReader();
                    reader.onload = function(e) {
                        // Render thumbnail.
                        frame.result.setAttribute('src', e.target.result);

                        var image = new Image();
                        image.onload = function () {
                            frame.ctx.clearRect(0, 0, image.width, image.height);
                            frame.ctx.drawImage(image, 0, 0);
                            var pixelData = frame.ctx.getImageData(0, 0, image.width, image.height).data;
                            var i = 0;
                            frame.pixels = [];
                            for (var y = 0; y < image.height; ++y) {
                                var line = [];
                                for (var x = 0; x < image.width; ++x) {
                                    line.push(pixelData[i+3] > 128 && {r: pixelData[i], g: pixelData[i+1], b: pixelData[i+2]});
                                    i += 4;
                                }
                                frame.pixels.push(line);
                            }
                            button.disabled = false;
                        };
                        image.src = e.target.result;
                    };
                }
                reader.readAsDataURL(file);
                return noDrag(e);
            });
        })();

        addEventHandler(button, 'click', function () {
            var md = '#####s\n#####s\n#####s\n#####s\n#####s\n####s\n\n';
            var html = '

s

s

s

s

s

s
'
            for (var y = 0; y < Math.max(frames[0].pixels.length, frames[1].pixels.length); ++y) {
                var line1 = frames[0].pixels[y] || [];
                var line2 = frames[1].pixels[y] || [];
                var currentTag = ''
                var currentColor1 = '';
                var currentColor2 = '';
                var currentHTMLTag = '';
                html += '
';
                for (var x = 0; x < Math.max(line1.length, line2.length); ++x) {
                    var pixel1 = line1[x];
                    var pixel2 = line2[x];
                    var color1 = pixel1 ? nearestColor(pixel1, colors) : {name: 'Transparent', hex: 'transparent'};
                    var color2 = pixel2 ? nearestColor(pixel2, colors) : {name: 'Transparent', hex: 'transparent'};

                    if (color1.name != color2.name) {
                        console.log('diff');
                    }

                    if (currentColor1.name == color1.name && currentColor2.name == color2.name) {
                        currentTag += '..';
                        currentHTMLTag += '..';
                    } else {
                        if (currentTag) {
                            md += currentTag + '](' + hrefFromColors(currentColor1, currentColor2) + ')';
                            html += currentHTMLTag + '';
                        }
                        currentTag = '[..';
                        currentHTMLTag = '..';
                        currentColor1 = color1;
                        currentColor2 = color2;
                    }
                }
                if (!(currentColor1.name == 'Transparent' && currentColor1.name == 'Transparent')) {
                    md += currentTag + '](' + hrefFromColors(currentColor1, currentColor2) + ')\n\n';
                    html += currentHTMLTag + '';
                }
                md += '\n\n';
                html += '
';
            }
            md += '####e'
            html += '
e

';
            document.getElementById('htmlResult').innerHTML = html;
            document.getElementById('mdResult').innerHTML = md;
        });

        button.disabled = true;
    });
} else {
    alert('Your browser does not support the HTML5 FileReader.');
}<commit_msg>fix when first frame is transparent<commit_after>colors = [
	['AliceBlue', '#F0F8FF'],
	['AntiqueWhite', '#FAEBD7'],
	['Aqua', '#00FFFF'],
	['Aquamarine', '#7FFFD4'],
	['Azure', '#F0FFFF'],
	['Beige', '#F5F5DC'],
	['Bisque', '#FFE4C4'],
	['Black', '#000000'],
	['BlanchedAlmond', '#FFEBCD'],
	['Blue', '#0000FF'],
	['BlueViolet', '#8A2BE2'],
	['Brown', '#A52A2A'],
	['BurlyWood', '#DEB887'],
	['CadetBlue', '#5F9EA0'],
	['Chartreuse', '#7FFF00'],
	['Chocolate', '#D2691E'],
	['Coral', '#FF7F50'],
	['CornflowerBlue', '#6495ED'],
	['Cornsilk', '#FFF8DC'],
	['Crimson', '#DC143C'],
	['Cyan', '#00FFFF'],
	['DarkBlue', '#00008B'],
	['DarkCyan', '#008B8B'],
	['DarkGoldenRod', '#B8860B'],
	['DarkGrey', '#A9A9A9'],
	['DarkGreen', '#006400'],
	['DarkKhaki', '#BDB76B'],
	['DarkMagenta', '#8B008B'],
	['DarkOliveGreen', '#556B2F'],
	['DarkOrange', '#FF8C00'],
	['DarkOrchid', '#9932CC'],
	['DarkRed', '#8B0000'],
	['DarkSalmon', '#E9967A'],
	['DarkSeaGreen', '#8FBC8F'],
	['DarkSlateBlue', '#483D8B'],
	['DarkSlateGrey', '#2F4F4F'],
	['DarkTurquoise', '#00CED1'],
	['DarkViolet', '#9400D3'],
	['DeepPink', '#FF1493'],
	['DeepSkyBlue', '#00BFFF'],
	['DimGrey', '#696969'],
	['DodgerBlue', '#1E90FF'],
	['FireBrick', '#B22222'],
	['FloralWhite', '#FFFAF0'],
	['ForestGreen', '#228B22'],
	['Fuchsia', '#FF00FF'],
	['Gainsboro', '#DCDCDC'],
	['GhostWhite', '#F8F8FF'],
	['Gold', '#FFD700'],
	['GoldenRod', '#DAA520'],
	['Grey', '#808080'],
	['Green', '#008000'],
	['GreenYellow', '#ADFF2F'],
	['HoneyDew', '#F0FFF0'],
	['HotPink', '#FF69B4'],
	['IndianRed', '#CD5C5C'],
	['Indigo', '#4B0082'],
	['Ivory', '#FFFFF0'],
	['Khaki', '#F0E68C'],
	['Lavender', '#E6E6FA'],
	['LavenderBlush', '#FFF0F5'],
	['LawnGreen', '#7CFC00'],
	['LemonChiffon', '#FFFACD'],
	['LightBlue', '#ADD8E6'],
	['LightCoral', '#F08080'],
	['LightCyan', '#E0FFFF'],
	['LightGoldenRodYellow', '#FAFAD2'],
	['LightGrey', '#D3D3D3'],
	['LightGreen', '#90EE90'],
	['LightPink', '#FFB6C1'],
	['LightSalmon', '#FFA07A'],
	['LightSeaGreen', '#20B2AA'],
	['LightSkyBlue', '#87CEFA'],
	['LightSlateGrey', '#778899'],
	['LightSteelBlue', '#B0C4DE'],
	['LightYellow', '#FFFFE0'],
	['Lime', '#00FF00'],
	['LimeGreen', '#32CD32'],
	['Linen', '#FAF0E6'],
	['Magenta', '#FF00FF'],
	['Maroon', '#800000'],
	['MediumAquaMarine', '#66CDAA'],
	['MediumBlue', '#0000CD'],
	['MediumOrchid', '#BA55D3'],
	['MediumPurple', '#9370DB'],
	['MediumSeaGreen', '#3CB371'],
	['MediumSlateBlue', '#7B68EE'],
	['MediumSpringGreen', '#00FA9A'],
	['MediumTurquoise', '#48D1CC'],
	['MediumVioletRed', '#C71585'],
	['MidnightBlue', '#191970'],
	['MintCream', '#F5FFFA'],
	['MistyRose', '#FFE4E1'],
	['Moccasin', '#FFE4B5'],
	['NavajoWhite', '#FFDEAD'],
	['Navy', '#000080'],
	['OldLace', '#FDF5E6'],
	['Olive', '#808000'],
	['OliveDrab', '#6B8E23'],
	['Orange', '#FFA500'],
	['OrangeRed', '#FF4500'],
	['Orchid', '#DA70D6'],
	['PaleGoldenRod', '#EEE8AA'],
	['PaleGreen', '#98FB98'],
	['PaleTurquoise', '#AFEEEE'],
	['PaleVioletRed', '#DB7093'],
	['PapayaWhip', '#FFEFD5'],
	['PeachPuff', '#FFDAB9'],
	['Peru', '#CD853F'],
	['Pink', '#FFC0CB'],
	['Plum', '#DDA0DD'],
	['PowderBlue', '#B0E0E6'],
	['Purple', '#800080'],
	['RebeccaPurple', '#663399'],
	['Red', '#FF0000'],
	['RosyBrown', '#BC8F8F'],
	['RoyalBlue', '#4169E1'],
	['SaddleBrown', '#8B4513'],
	['Salmon', '#FA8072'],
	['SandyBrown', '#F4A460'],
	['SeaGreen', '#2E8B57'],
	['SeaShell', '#FFF5EE'],
	['Sienna', '#A0522D'],
	['Silver', '#C0C0C0'],
	['SkyBlue', '#87CEEB'],
	['SlateBlue', '#6A5ACD'],
	['SlateGrey', '#708090'],
	['Snow', '#FFFAFA'],
	['SpringGreen', '#00FF7F'],
	['SteelBlue', '#4682B4'],
	['Tan', '#D2B48C'],
	['Teal', '#008080'],
	['Thistle', '#D8BFD8'],
	['Tomato', '#FF6347'],
	['Turquoise', '#40E0D0'],
	['Violet', '#EE82EE'],
	['Wheat', '#F5DEB3'],
	['White', '#FFFFFF'],
	['WhiteSmoke', '#F5F5F5'],
	['Yellow', '#FFFF00'],
	['YellowGreen', '#9ACD32']
].map(function (color) {
    return {name: color[0], hex: color[1], rgb: hexToRGB(color[1])};
});

function hexToRGB(hex) {
    return {
        r: parseInt(hex.substring(1, 3), 16),
        g: parseInt(hex.substring(3, 5), 16),
        b: parseInt(hex.substring(5, 7), 16)
    };
}

function addEventHandler(obj, evt, handler) {
    if(obj.addEventListener) {
        // W3C method
        obj.addEventListener(evt, handler, false);
    } else if(obj.attachEvent) {
        // IE method.
        obj.attachEvent('on'+evt, function(e) {
            handler(e || window.event);
        });
    } else {
        // Old school method.
        obj['on'+evt] = handler;
    }
}

function nearestColor(color, colors) {
    var distance,
        minDistance = Infinity,
        rgb,
        nearest;

    for (var i = 0; i < colors.length; ++i) {
        rgb = colors[i].rgb;
        distance = Math.sqrt(
            Math.pow(color.r - rgb.r, 2) +
            Math.pow(color.g - rgb.g, 2) +
            Math.pow(color.b - rgb.b, 2)
        );

        if (distance < minDistance) {
            minDistance = distance;
            nearest = colors[i];
        }
    }

    return nearest;
}

function hrefFromColors(color1, color2) {
    return ('//' + color1.name + (color1 != color2 && color2.hex != 'transparent' ? ' /to_' + color2.name : '')).replace('Transparent', '').replace('// /', '//');
}

var pixels1 = [];
var pixels2 = [];

if(window.FileReader) {
    addEventHandler(window, 'load', function() {
        var frames = [];
        for (var i = 1; i <= 2; ++i) {
            var frame = {
                canvas: document.createElement('canvas'),
                drop: document.getElementsByClassName('dropZone')[i - 1],
                result: document.getElementById('result' + i),
                pixels: window['pixels' + i]
            };
            frame['ctx'] = frame.canvas.getContext('2d');
            frames.push(frame);
        };
        var button = document.getElementById('generateButton');

        function cancel(e) {
            if (e.preventDefault) {
                e.preventDefault();
            }
            if (e.stopPropagation) {
                e.stopPropagation();
            }
            return false;
        }

        function noDrag(e) {
            for (var i = 0; i < 2; ++i) {
                frames[i].drop.className = 'dropZone ' + (i == 0 ? 'left' : 'right');
            }
            return cancel(e);
        }

        function dragHint(e) {
            if (e.dataTransfer.files.length == 1 && e.dataTransfer.files[0].type.startsWith('image/')) {
                for (var i = 0; i < 2; ++i) {
                    frames[i].drop.className = 'dropZone dragHint ' + (i == 0 ? 'left' : 'right');
                }
            }
            return cancel(e);
        }

        addEventHandler(window, 'drop', noDrag);
        addEventHandler(window, 'dragover', dragHint);
        addEventHandler(window, 'dragenter', dragHint);
        addEventHandler(window, 'dragleave', noDrag);

        for (var i = 0; i < 2; ++i) (function () {

            var frameNr = i;
            var frame = frames[frameNr];
            addEventHandler(frame.drop, 'dragexit', dragHint);
            addEventHandler(frame.drop, 'dragleave', dragHint);

            addEventHandler(frame.drop, 'dragover', function (e) {
                cancel(e);
                if (e.dataTransfer.files.length == 1 && e.dataTransfer.files[0].type.startsWith('image/')) {
                    frame.drop.className = 'dropZone acceptDrag ' + (frameNr == 0 ? 'left' : 'right');
                    return true;
                }
                return false;
            });

            addEventHandler(frame.drop, 'drop', function (e) {
                var file = e.dataTransfer.files[0];

                if (e.dataTransfer.files.length == 1 && e.dataTransfer.files[0].type.startsWith('image/')) {
                    var reader = new FileReader();
                    reader.onload = function(e) {
                        // Render thumbnail.
                        frame.result.setAttribute('src', e.target.result);

                        var image = new Image();
                        image.onload = function () {
                            frame.ctx.clearRect(0, 0, image.width, image.height);
                            frame.ctx.drawImage(image, 0, 0);
                            var pixelData = frame.ctx.getImageData(0, 0, image.width, image.height).data;
                            var i = 0;
                            frame.pixels = [];
                            for (var y = 0; y < image.height; ++y) {
                                var line = [];
                                for (var x = 0; x < image.width; ++x) {
                                    line.push(pixelData[i+3] > 128 && {r: pixelData[i], g: pixelData[i+1], b: pixelData[i+2]});
                                    i += 4;
                                }
                                frame.pixels.push(line);
                            }
                            button.disabled = false;
                        };
                        image.src = e.target.result;
                    };
                }
                reader.readAsDataURL(file);
                return noDrag(e);
            });
        })();

        addEventHandler(button, 'click', function () {
            var md = '#####s\n#####s\n#####s\n#####s\n#####s\n####s\n\n';
            var html = '

s

s

s

s

s

s
'
            for (var y = 0; y < Math.max(frames[0].pixels.length, frames[1].pixels.length); ++y) {
                var line1 = frames[0].pixels[y] || [];
                var line2 = frames[1].pixels[y] || [];
                var currentTag = ''
                var currentColor1 = '';
                var currentColor2 = '';
                var currentHTMLTag = '';
                html += '
';
                for (var x = 0; x < Math.max(line1.length, line2.length); ++x) {
                    var pixel1 = line1[x];
                    var pixel2 = line2[x];
                    var color1 = pixel1 ? nearestColor(pixel1, colors) : {name: 'Transparent', hex: 'transparent'};
                    var color2 = pixel2 ? nearestColor(pixel2, colors) : {name: 'Transparent', hex: 'transparent'};

                    if (color1.name != color2.name) {
                        console.log('diff');
                    }

                    if (currentColor1.name == color1.name && currentColor2.name == color2.name) {
                        currentTag += '..';
                        currentHTMLTag += '..';
                    } else {
                        if (currentTag) {
                            md += currentTag + '](' + hrefFromColors(currentColor1, currentColor2) + ')';
                            html += currentHTMLTag + '';
                        }
                        currentTag = '[..';
                        currentHTMLTag = '..';
                        currentColor1 = color1;
                        currentColor2 = color2;
                    }
                }
                if (!(currentColor1.name == 'Transparent' && currentColor1.name == 'Transparent')) {
                    md += currentTag + '](' + hrefFromColors(currentColor1, currentColor2) + ')\n\n';
                    html += currentHTMLTag + '';
                }
                md += '\n\n';
                html += '
';
            }
            md += '####e'
            html += '
e

';
            document.getElementById('htmlResult').innerHTML = html;
            document.getElementById('mdResult').innerHTML = md;
        });

        button.disabled = true;
    });
} else {
    alert('Your browser does not support the HTML5 FileReader.');
}<|endoftext|>


<commit_before>'use strict'
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var css = csjs`
  .li_tv {
    list-style-type: none;
    -webkit-margin-before: 0px;
    -webkit-margin-after: 0px;
    -webkit-margin-start: 0px;
    -webkit-margin-end: 0px;
    -webkit-padding-start: 0px;
    margin-left: 10px;
  }
  .ul_tv {
    list-style-type: none;
    -webkit-margin-before: 0px;
    -webkit-margin-after: 0px;
    -webkit-margin-start: 0px;
    -webkit-margin-end: 0px;
    -webkit-padding-start: 0px;
    margin-left: 10px;
  }
  .caret_tv {
    margin-top: 3px;
    width: 10px;
  }
`

var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager

/**
 * TreeView
 *  - extendable by specifying custom `extractData` and `formatSelf` function
 *  - trigger `nodeClick` and `leafClick`
 */
class TreeView {

  constructor (opts) {
    this.event = new EventManager()
    this.extractData = opts.extractData || this.extractDataDefault
    this.formatSelf = opts.formatSelf || this.formatSelfDefault
    this.view = null
    this.nodeIsExpanded = {}
  }

  render (json) {
    var view = this.renderProperties(json, false)
    if (!this.view) {
      this.view = view
    }
    return view
  }

  update (json) {
    if (this.view) {
      yo.update(this.view, this.render(json))
    }
  }

  renderObject (item, parent, key, expand, keyPath) {
    var data = this.extractData(item, parent, key)
    var children = (data.children || []).map((child, index) => {
      return this.renderObject(child.value, data, child.key, expand, keyPath + '/' + child.key)
    })
    return this.formatData(key, data, children, expand, keyPath)
  }

  renderProperties (json, expand) {
    var children = Object.keys(json).map((innerkey) => {
      return this.renderObject(json[innerkey], json, innerkey, expand, innerkey)
    })
    return yo`<ul class="${css.ul_tv}">${children}</ul>`
  }

  formatData (key, data, children, expand, keyPath) {
    var self = this
    var li = yo`<li class=${css.li_tv}></li>`
    var caret = yo`<div class="fa fa-caret-right" class=${css.caret_tv}></div>`
    var label = yo`

        ${caret}
        ${self.formatSelf(key, data, li)}

`
    li.appendChild(label)
    if (data.children) {
      var isExpanded = self.nodeIsExpanded[keyPath]
      var list = yo`<ul class=${css.ul_tv}>${children}</ul>`
      list.style.display = isExpanded !== undefined ? (isExpanded ? 'block' : 'none') : (expand ? 'block' : 'none')
      caret.className = list.style.display === 'none' ? 'fa fa-caret-right' : 'fa fa-caret-down'
      label.onclick = function () {
        caret.className = caret.className === 'fa fa-caret-right' ? 'fa fa-caret-down' : 'fa fa-caret-right'
        list.style.display = list.style.display === 'none' ? 'block' : 'none'
        self.nodeIsExpanded[keyPath] = list.style.display === 'block'
        self.event.trigger('nodeClick', [keyPath, list])
      }
      li.appendChild(list)
    } else {
      caret.style.visibility = 'hidden'
    }
    return li
  }

  formatSelfDefault (key, data) {
    return yo`<label>${key}: ${data.self}</label>`
  }

  extractDataDefault (item, parent, key) {
    var ret = {}
    if (item instanceof Array) {
      ret.children = item.map((item, index) => {
        return {key: index, value: item}
      })
      ret.self = 'Array'
      ret.isNode = true
      ret.isLeaf = false
    } else if (item instanceof Object) {
      ret.children = Object.keys(item).map((key) => {
        return {key: key, value: item[key]}
      })
      ret.self = 'Object'
      ret.isNode = true
      ret.isLeaf = false
    } else {
      ret.self = item
      ret.children = []
      ret.isNode = false
      ret.isLeaf = true
    }
    return ret
  }
}

module.exports = TreeView
<commit_msg>retrieve node at specific path<commit_after>'use strict'
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var css = csjs`
  .li_tv {
    list-style-type: none;
    -webkit-margin-before: 0px;
    -webkit-margin-after: 0px;
    -webkit-margin-start: 0px;
    -webkit-margin-end: 0px;
    -webkit-padding-start: 0px;
    margin-left: 10px;
  }
  .ul_tv {
    list-style-type: none;
    -webkit-margin-before: 0px;
    -webkit-margin-after: 0px;
    -webkit-margin-start: 0px;
    -webkit-margin-end: 0px;
    -webkit-padding-start: 0px;
    margin-left: 10px;
  }
  .caret_tv {
    margin-top: 3px;
    width: 10px;
  }
`

var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager

/**
 * TreeView
 *  - extendable by specifying custom `extractData` and `formatSelf` function
 *  - trigger `nodeClick` and `leafClick`
 */
class TreeView {

  constructor (opts) {
    this.event = new EventManager()
    this.extractData = opts.extractData || this.extractDataDefault
    this.formatSelf = opts.formatSelf || this.formatSelfDefault
    this.view = null
    this.nodeIsExpanded = {}
    this.nodes = {}
  }

  render (json) {
    var view = this.renderProperties(json, false)
    if (!this.view) {
      this.view = view
    }
    return view
  }

  update (json) {
    if (this.view) {
      yo.update(this.view, this.render(json))
    }
  }

  renderObject (item, parent, key, expand, keyPath) {
    var data = this.extractData(item, parent, key)
    var children = (data.children || []).map((child, index) => {
      return this.renderObject(child.value, data, child.key, expand, keyPath + '/' + child.key)
    })
    return this.formatData(key, data, children, expand, keyPath)
  }

  renderProperties (json, expand) {
    var children = Object.keys(json).map((innerkey) => {
      return this.renderObject(json[innerkey], json, innerkey, expand, innerkey)
    })
    return yo`<ul class="${css.ul_tv}">${children}</ul>`
  }

  formatData (key, data, children, expand, keyPath) {
    var self = this
    var li = yo`<li class=${css.li_tv}></li>`
    var caret = yo`<div class="fa fa-caret-right" class=${css.caret_tv}></div>`
    var label = yo`

        ${caret}
        ${self.formatSelf(key, data, li)}

`
    li.appendChild(label)
    if (data.children) {
      var isExpanded = self.nodeIsExpanded[keyPath]
      var list = yo`<ul class=${css.ul_tv}>${children}</ul>`
      this.nodes[keyPath] = list
      list.style.display = isExpanded !== undefined ? (isExpanded ? 'block' : 'none') : (expand ? 'block' : 'none')
      caret.className = list.style.display === 'none' ? 'fa fa-caret-right' : 'fa fa-caret-down'
      label.onclick = function () {
        caret.className = caret.className === 'fa fa-caret-right' ? 'fa fa-caret-down' : 'fa fa-caret-right'
        list.style.display = list.style.display === 'none' ? 'block' : 'none'
        self.nodeIsExpanded[keyPath] = list.style.display === 'block'
        self.event.trigger('nodeClick', [keyPath, list])
      }
      li.appendChild(list)
    } else {
      caret.style.visibility = 'hidden'
    }
    return li
  }

  nodeAt (path) {
    return this.nodes[path]
  }

  formatSelfDefault (key, data) {
    return yo`<label>${key}: ${data.self}</label>`
  }

  extractDataDefault (item, parent, key) {
    var ret = {}
    if (item instanceof Array) {
      ret.children = item.map((item, index) => {
        return {key: index, value: item}
      })
      ret.self = 'Array'
      ret.isNode = true
      ret.isLeaf = false
    } else if (item instanceof Object) {
      ret.children = Object.keys(item).map((key) => {
        return {key: key, value: item[key]}
      })
      ret.self = 'Object'
      ret.isNode = true
      ret.isLeaf = false
    } else {
      ret.self = item
      ret.children = []
      ret.isNode = false
      ret.isLeaf = true
    }
    return ret
  }
}

module.exports = TreeView
<|endoftext|>


<commit_before>var reportInterface = new function() {

    // public

    this.addSortHeaders = function(domId, reportState)
    {
        // sort
        _.each($(format("#{0} tr.header td", domId)), function(e, i) {
            if (reportState.sortRowIndex == i && reportState.sortDirection == "down")
                $(e).append('');
            else
                $(e).append('');

            if (reportState.sortRowIndex == i && reportState.sortDirection == "up")
                $(e).append('');
            else
                $(e).append('');
        });

        $(".sortup").unbind().click(function(e) {

            e.preventDefault();

            reportState.sortRowIndex = $(this).parent().index();
            reportState.sortDirection = "up";

            if (reportState.useExpandCollapse)
                reportState.drawData(reportBuilder.sortExpandedData(reportState.serverData, reportState.dimensionsY, reportState.sortRowIndex, reportState.sortDirection, reportState.expandedCells));
            else
                reportState.drawData(reportBuilder.sortData(reportState.serverData, reportState.sortRowIndex, reportState.sortDirection));
        });

        $(".sortdown").unbind().click(function(e) {

            e.preventDefault();

            reportState.sortRowIndex = $(this).parent().index();
            reportState.sortDirection = "down";

            if (reportState.useExpandCollapse)
                reportState.drawData(reportBuilder.sortExpandedData(reportState.serverData, reportState.dimensionsY, reportState.sortRowIndex, reportState.sortDirection, reportState.expandedCells));
            else
                reportState.drawData(reportBuilder.sortData(reportState.serverData, reportState.sortRowIndex, reportState.sortDirection));
        });
    };

    // adds expand/collapse all to dimension headers and hookup click handlers
    this.addExpandCollapseHeaders = function(domId, reportState)
    {
        _.each(reportState.dimensionsY, function(e, i) {

            var noExpandedCells = $(format("a.expandDimension.{0}", e)).length;
            var noCollapsedCells = $(format("a.collapseDimension.{0}", e)).length;

            var header = $(format("#{0} tr.header td:eq({1})", domId, i));

            if (noExpandedCells > 0)
                header.append('');
            else if (noCollapsedCells > 0)
                header.append('');
        });

        this.hookupExpandCollapseAll(domId, reportState);
    };

    // domId
    // allYaxisValues the result of getAllYAxisValues
    // data [{ type: "row|subtotal|grandtotal", values: [{ sortValue: "", displayValue: "", sortValueType: "string" }] ]
    // yAxis [{ id: '', name: ''}, ...]
    // xAxis [{ id: '', name: '', calculate: ... }, ...]
    // xAxisRestrictions [{ name: '', visible: false }, ...]
    // xAxisDimValues [value1, value2, ...]
    //
    this.drawTable = function(domId, reportState, allYaxisValues, data, yAxis, xAxis, xAxisRestrictions, xAxisDimValues)
    {
        var dataTable = $(format("#{0}", domId));
        var dataTableHTML = "";

        var extraHeaders = _.filter(xAxisRestrictions, function(e) { return e.visible; }).length;

        dataTableHTML += "
";

        for (var i = 0; i < extraHeaders; ++i) {
            var extraRow = [];
            _.each(yAxis, function() { extraRow.push(""); });

            _.each(xAxis, function(x, xi) {
                var visibleHeaders = _.filter(xAxisRestrictions[xi], function(e) { return e.visible; });
                var hasHeaders = visibleHeaders.length >= (extraHeaders - i);

                _.each(x, function() {
                    if (hasHeaders)
                        extraRow.push(visibleHeaders[extraHeaders - i - 1].name);
                    else
                        extraRow.push("");
                });
            });

            dataTableHTML += headerRowToHTML(extraRow, true);
        }

        // normal headers
        var yNames = _.map(yAxis, function(e) { return e.name; });
        var xNames = _.map(xAxis, function(e) { return e.name; });

        // x-axis dimension
        if (xAxisDimValues != null && xAxisDimValues.length > 0) {
            var extraRow = [];
            _.each(yAxis, function() { extraRow.push(""); });

            var allXNames = [];

            _.each(xAxisDimValues, function(xAxisDimValue) {

                extraRow.push(xAxisDimValue);
                for (var i = 1; i < xNames.length; ++i)
                    extraRow.push(xAxisDimValue);

                _.each(xNames, function(xName) {
                    allXNames.push(xName);
                });
            });

            dataTableHTML += headerRowToHTML(extraRow, true);
            dataTableHTML += headerRowToHTML(_.flatten([yNames, allXNames]));
        }
        else {
            dataTableHTML += headerRowToHTML(_.flatten([yNames, xNames]));
        }

        dataTableHTML += "
";

        // values

        var lastDimValues = _.map(yAxis, function() { return { displayValue: "" }; });

        var noDimensions = yNames.length;

        var formatFunctions = {};

        var xAxisCount = 1;
        if (xAxisDimValues != null && xAxisDimValues.length > 0)
            xAxisCount = xAxisDimValues.length;

        for (var xAxisIndex = 0; xAxisIndex < xAxisCount; ++xAxisIndex) {
            _.each(xAxis, function(xAxisSpec) {
                if (xAxisSpec.calculate)
                    formatFunctions[noDimensions + i] = xAxisSpec.calculate;
                noDimensions += 1;
            });
        }

        _.each(data, function(row, i) {

	    var unmodifiedYaxis = _.take(row.values, yAxis.length);
            var values = _.map(row.values, function(e) { return cloneObj(e); });

            var foundSubtotal = false;

            _.each(yAxis, function(e, i) {
                if (reportState.useExpandCollapse && lastDimValues[i].displayValue == values[i].displayValue && row.type == "row")
                    values[i].displayValue = "";

                if (row.type == "subtotal" && values[i].displayValue == "" && !foundSubtotal) {
                    values[i].displayValue = "Sub total";
                    foundSubtotal = true;
                }
            });

            dataTableHTML += reportInterface.rowToHTML(values, row.type, formatFunctions, yAxis, row.values, reportState);

            lastDimValues = unmodifiedYaxis;
        });

        dataTable.html(dataTableHTML);

        if (reportState.useExpandCollapse)
            hookupExpandCollapse(domId, reportState);
    };

    this.hookupExpandCollapseAll = function(domId, reportState)
    {
        $(format("#{0} a.expandAll", domId)).unbind().click(function(e) {

            e.preventDefault();

            var td = $(this).closest("td");
            var dimId = reportState.dimensionsY[td[0].cellIndex];

            var cellsToExpand = $(format("a.expandDimension.{0}", dimId));

            _.each(cellsToExpand, function(e, i) {
                var expandValue = $(e).data("expandcollapse");

                if (reportState.expandedCells[dimId] != null)
                    reportState.expandedCells[dimId].push(expandValue);
                else
                    reportState.expandedCells[dimId] = [expandValue];

                $(e).removeClass("expandDimension").addClass("collapseDimension");
            });

            reportState.drawNewData();
        });

        $(format("#{0} img.collapseAll", domId)).unbind().click(function(e) {
            var td = $(this).closest("td");
            var dimId = reportState.dimensionsY[td[0].cellIndex];

            var cellsToCollapse = $(format("a.collapseDimension.{0}", dimId));

            _.each(cellsToCollapse, function(e, i) {

                e.preventDefault();

                var td = $(e).closest("td");
                var value = td.text();

		        // FIXME: maybe get the sort value instead of the display value as td.text() is

                // collapse multiple
                // we have encoded the parent values in the collapse img tag
                // so we need to remove these as well
                var cells = _.map($(format("img.collapseDimension[data-expandcollapse*='{0}']", dimId + ':' + value)), function(e2) { return $(e2).data('expandcollapse'); });

                _.each(reportState.expandedCells, function(tempValues, expandedDim) {
                    _.each(cells, function(cell) {
                        reportState.expandedCells[expandedDim] = _.without(reportState.expandedCells[expandedDim], cell);
                        if (reportState.expandedCells[expandedDim].length == 0) {
                            delete reportState.expandedCells[expandedDim];
                            return false; // break
                        }
                    });
                });

                $(e).removeClass("collapseDimension").addClass("expandDimension");
            });

            reportState.drawNewData();
        });
    };

    // adds horizontal split lines to the table, can take any number of column indexes
    this.addSplits = function(domId)
    {
        for (var i = 0; i < arguments.length; ++i) {
            this.addSplit(domId, arguments[i]);
        }
    };

    // adds a horizontal split line to the table, can take any number of column indexes
    this.addSplit = function(domId, colIndex)
    {
        $(format("table#{0} td:nth-child({1})", domId, colIndex)).css("border-right", "1.5px solid #ccc");
    };

    this.rowToHTML = function(values, rowType, formatFunctions, yAxis, startValues, reportState)
    {
        var valuesHTML = "";

        _.each(values, function(e, i) {

            var dimValue = e.displayValue == '' ? ' ' : e.displayValue;

	        // this is yAxis length - 1 because we can't expand the last dim anyway
            if (reportState.useExpandCollapse && i < (yAxis.length - 1) && dimValue != ' ')
            {
                if (rowType != 'row') {
                    valuesHTML += format("{0}	", dimValue);
                    return;
                }

                var lookupKey = "";
                for (var j = 0; j <= i; ++j) {
                    if (j != i)
                        lookupKey += yAxis[j].id + ":" + startValues[j].sortValue + "|";
                    else
                        lookupKey += yAxis[j].id + ":" + startValues[j].sortValue;
                }

		        var dimId = reportState.dimensionsY[i];

                if (dimId in reportState.expandedCells) {
                    if (_.contains(reportState.expandedCells[dimId], lookupKey)) {
                        valuesHTML += format("{1}	", lookupKey, dimValue, dimId);
                        return;
                    }
                }

                valuesHTML += format("{1}	", lookupKey, dimValue, dimId);
            }
            else
	        {
                if (i <= yAxis.length - 1) {
                    valuesHTML += format("{0}	", dimValue);
                }
                else {
		            // FIXME: formatfloat shit
                    if (formatFunctions && formatFunctions[i])
                        valuesHTML += format("{0}	", formatFloat(formatFunctions[i](dimValue)));
                    else
                        valuesHTML += format("{0}	", formatFloat(dimValue));
                }
            }
        });

        if (rowType != 'row')
            return format("
{1}
", rowType, valuesHTML);
        else
            return format("
{0}
", valuesHTML);
    };

    // private

    // adds click handler to expand/collapse buttons in the table
    function hookupExpandCollapse(domId, reportState)
    {
        $(format("#{0} .expandDimension", domId)).click(function(e) {

            e.preventDefault();

            var td = $(this).closest("td");
            var dimId = reportState.dimensionsY[td[0].cellIndex];

            var expandValue = $(this).data("expandcollapse");

            if (dimId in reportState.expandedCells)
                reportState.expandedCells[dimId].push(expandValue);
            else
                reportState.expandedCells[dimId] = [expandValue];

            // we don't need data-expandcollapse as we draw anyway
            $(this).removeClass("expandDimension").addClass("collapseDimension");

            reportState.drawNewData();
        });

        $(format("#{0} .collapseDimension", domId)).click(function(e) {

            e.preventDefault();

            var td = $(this).closest("td");
            var dimId = reportState.dimensionsY[td[0].cellIndex];

	        // FIXME: we need to use sortValue!
            var value = td.text();

	        // FIXME: maybe get the sort value instead of the display value as td.text() is

            // collapse multiple
            // we have encoded the parent values in the collapse img tag
            // so we need to remove these as well
            var cells = _.map($(format("img.collapseDimension[data-expandcollapse*='{0}']", dimId + ':' + value)), function(e) { return $(e).data('expandcollapse'); });

            _.each(reportState.expandedCells, function(tempValues, expandedDim) {
                _.each(cells, function(cell) {
                    reportState.expandedCells[expandedDim] = _.without(reportState.expandedCells[expandedDim], cell);
                    if (reportState.expandedCells[expandedDim].length == 0) {
                        delete reportState.expandedCells[expandedDim];
                        return false; // break
                    }
                });
            });

            // we don't need data-expandcollapse as we draw anyway
            $(this).removeClass("collapseDimension").addClass("expandDimension");

            reportState.drawNewData();
        });
    };

    function headerRowToHTML(values, top)
    {
        var valuesHTML = "";

        var lastValue = "æøå";
        var valuesInARow = 1;
        var waitingToWrite = "";

        _.each(values, function(e, i) {

            if (lastValue != e) {
                if (waitingToWrite != "") {
                    valuesHTML += format(waitingToWrite, valuesInARow);
                    waitingToWrite = "" + e + "	";
                }
                else
                    waitingToWrite = "" + e + "	";
                valuesInARow = 1;
            } else
                valuesInARow += 1;

            lastValue = e;
        });

        if (waitingToWrite != "")
            valuesHTML += format(waitingToWrite, valuesInARow);

        if (top)
            return format("
{0}
", valuesHTML);
        else
            return format("
{0}
", valuesHTML);
    }
};
<commit_msg>Don't allow sorting an un-expanded dimension<commit_after>var reportInterface = new function() {

    // public

    this.addSortHeaders = function(domId, reportState)
    {
        // sort
        _.each($(format("#{0} tr.header td", domId)), function(e, i) {

            // if dim, then it needs to be expanded
            if (reportState.useExpandCollapse &&
                i < reportState.dimensionsY.length &&
                i > _.keys(reportState.expandedCells).length)
                return; // continue

            if (reportState.sortRowIndex == i && reportState.sortDirection == "down")
                $(e).append('');
            else
                $(e).append('');

            if (reportState.sortRowIndex == i && reportState.sortDirection == "up")
                $(e).append('');
            else
                $(e).append('');
        });

        $(".sortup").unbind().click(function(e) {

            e.preventDefault();

            reportState.sortRowIndex = $(this).parent().index();
            reportState.sortDirection = "up";

            if (reportState.useExpandCollapse)
                reportState.drawData(reportBuilder.sortExpandedData(reportState.serverData, reportState.dimensionsY, reportState.sortRowIndex, reportState.sortDirection, reportState.expandedCells));
            else
                reportState.drawData(reportBuilder.sortData(reportState.serverData, reportState.sortRowIndex, reportState.sortDirection));
        });

        $(".sortdown").unbind().click(function(e) {

            e.preventDefault();

            reportState.sortRowIndex = $(this).parent().index();
            reportState.sortDirection = "down";

            if (reportState.useExpandCollapse)
                reportState.drawData(reportBuilder.sortExpandedData(reportState.serverData, reportState.dimensionsY, reportState.sortRowIndex, reportState.sortDirection, reportState.expandedCells));
            else
                reportState.drawData(reportBuilder.sortData(reportState.serverData, reportState.sortRowIndex, reportState.sortDirection));
        });
    };

    // adds expand/collapse all to dimension headers and hookup click handlers
    this.addExpandCollapseHeaders = function(domId, reportState)
    {
        _.each(reportState.dimensionsY, function(e, i) {

            var noExpandedCells = $(format("a.expandDimension.{0}", e)).length;
            var noCollapsedCells = $(format("a.collapseDimension.{0}", e)).length;

            var header = $(format("#{0} tr.header td:eq({1})", domId, i));

            if (noExpandedCells > 0)
                header.append('');
            else if (noCollapsedCells > 0)
                header.append('');
        });

        this.hookupExpandCollapseAll(domId, reportState);
    };

    // domId
    // allYaxisValues the result of getAllYAxisValues
    // data [{ type: "row|subtotal|grandtotal", values: [{ sortValue: "", displayValue: "", sortValueType: "string" }] ]
    // yAxis [{ id: '', name: ''}, ...]
    // xAxis [{ id: '', name: '', calculate: ... }, ...]
    // xAxisRestrictions [{ name: '', visible: false }, ...]
    // xAxisDimValues [value1, value2, ...]
    //
    this.drawTable = function(domId, reportState, allYaxisValues, data, yAxis, xAxis, xAxisRestrictions, xAxisDimValues)
    {
        var dataTable = $(format("#{0}", domId));
        var dataTableHTML = "";

        var extraHeaders = _.filter(xAxisRestrictions, function(e) { return e.visible; }).length;

        dataTableHTML += "
";

        for (var i = 0; i < extraHeaders; ++i) {
            var extraRow = [];
            _.each(yAxis, function() { extraRow.push(""); });

            _.each(xAxis, function(x, xi) {
                var visibleHeaders = _.filter(xAxisRestrictions[xi], function(e) { return e.visible; });
                var hasHeaders = visibleHeaders.length >= (extraHeaders - i);

                _.each(x, function() {
                    if (hasHeaders)
                        extraRow.push(visibleHeaders[extraHeaders - i - 1].name);
                    else
                        extraRow.push("");
                });
            });

            dataTableHTML += headerRowToHTML(extraRow, true);
        }

        // normal headers
        var yNames = _.map(yAxis, function(e) { return e.name; });
        var xNames = _.map(xAxis, function(e) { return e.name; });

        // x-axis dimension
        if (xAxisDimValues != null && xAxisDimValues.length > 0) {
            var extraRow = [];
            _.each(yAxis, function() { extraRow.push(""); });

            var allXNames = [];

            _.each(xAxisDimValues, function(xAxisDimValue) {

                extraRow.push(xAxisDimValue);
                for (var i = 1; i < xNames.length; ++i)
                    extraRow.push(xAxisDimValue);

                _.each(xNames, function(xName) {
                    allXNames.push(xName);
                });
            });

            dataTableHTML += headerRowToHTML(extraRow, true);
            dataTableHTML += headerRowToHTML(_.flatten([yNames, allXNames]));
        }
        else {
            dataTableHTML += headerRowToHTML(_.flatten([yNames, xNames]));
        }

        dataTableHTML += "
";

        // values

        var lastDimValues = _.map(yAxis, function() { return { displayValue: "" }; });

        var noDimensions = yNames.length;

        var formatFunctions = {};

        var xAxisCount = 1;
        if (xAxisDimValues != null && xAxisDimValues.length > 0)
            xAxisCount = xAxisDimValues.length;

        for (var xAxisIndex = 0; xAxisIndex < xAxisCount; ++xAxisIndex) {
            _.each(xAxis, function(xAxisSpec) {
                if (xAxisSpec.calculate)
                    formatFunctions[noDimensions + i] = xAxisSpec.calculate;
                noDimensions += 1;
            });
        }

        _.each(data, function(row, i) {

	    var unmodifiedYaxis = _.take(row.values, yAxis.length);
            var values = _.map(row.values, function(e) { return cloneObj(e); });

            var foundSubtotal = false;

            _.each(yAxis, function(e, i) {
                if (reportState.useExpandCollapse && lastDimValues[i].displayValue == values[i].displayValue && row.type == "row")
                    values[i].displayValue = "";

                if (row.type == "subtotal" && values[i].displayValue == "" && !foundSubtotal) {
                    values[i].displayValue = "Sub total";
                    foundSubtotal = true;
                }
            });

            dataTableHTML += reportInterface.rowToHTML(values, row.type, formatFunctions, yAxis, row.values, reportState);

            lastDimValues = unmodifiedYaxis;
        });

        dataTable.html(dataTableHTML);

        if (reportState.useExpandCollapse)
            hookupExpandCollapse(domId, reportState);
    };

    this.hookupExpandCollapseAll = function(domId, reportState)
    {
        $(format("#{0} a.expandAll", domId)).unbind().click(function(e) {

            e.preventDefault();

            var td = $(this).closest("td");
            var dimId = reportState.dimensionsY[td[0].cellIndex];

            var cellsToExpand = $(format("a.expandDimension.{0}", dimId));

            _.each(cellsToExpand, function(e, i) {
                var expandValue = $(e).data("expandcollapse");

                if (reportState.expandedCells[dimId] != null)
                    reportState.expandedCells[dimId].push(expandValue);
                else
                    reportState.expandedCells[dimId] = [expandValue];

                $(e).removeClass("expandDimension").addClass("collapseDimension");
            });

            reportState.drawNewData();
        });

        $(format("#{0} img.collapseAll", domId)).unbind().click(function(e) {
            var td = $(this).closest("td");
            var dimId = reportState.dimensionsY[td[0].cellIndex];

            var cellsToCollapse = $(format("a.collapseDimension.{0}", dimId));

            _.each(cellsToCollapse, function(e, i) {

                e.preventDefault();

                var td = $(e).closest("td");
                var value = td.text();

		        // FIXME: maybe get the sort value instead of the display value as td.text() is

                // collapse multiple
                // we have encoded the parent values in the collapse img tag
                // so we need to remove these as well
                var cells = _.map($(format("img.collapseDimension[data-expandcollapse*='{0}']", dimId + ':' + value)), function(e2) { return $(e2).data('expandcollapse'); });

                _.each(reportState.expandedCells, function(tempValues, expandedDim) {
                    _.each(cells, function(cell) {
                        reportState.expandedCells[expandedDim] = _.without(reportState.expandedCells[expandedDim], cell);
                        if (reportState.expandedCells[expandedDim].length == 0) {
                            delete reportState.expandedCells[expandedDim];
                            return false; // break
                        }
                    });
                });

                $(e).removeClass("collapseDimension").addClass("expandDimension");
            });

            reportState.drawNewData();
        });
    };

    // adds horizontal split lines to the table, can take any number of column indexes
    this.addSplits = function(domId)
    {
        for (var i = 0; i < arguments.length; ++i) {
            this.addSplit(domId, arguments[i]);
        }
    };

    // adds a horizontal split line to the table, can take any number of column indexes
    this.addSplit = function(domId, colIndex)
    {
        $(format("table#{0} td:nth-child({1})", domId, colIndex)).css("border-right", "1.5px solid #ccc");
    };

    this.rowToHTML = function(values, rowType, formatFunctions, yAxis, startValues, reportState)
    {
        var valuesHTML = "";

        _.each(values, function(e, i) {

            var dimValue = e.displayValue == '' ? ' ' : e.displayValue;

	        // this is yAxis length - 1 because we can't expand the last dim anyway
            if (reportState.useExpandCollapse && i < (yAxis.length - 1) && dimValue != ' ')
            {
                if (rowType != 'row') {
                    valuesHTML += format("{0}	", dimValue);
                    return;
                }

                var lookupKey = "";
                for (var j = 0; j <= i; ++j) {
                    if (j != i)
                        lookupKey += yAxis[j].id + ":" + startValues[j].sortValue + "|";
                    else
                        lookupKey += yAxis[j].id + ":" + startValues[j].sortValue;
                }

		        var dimId = reportState.dimensionsY[i];

                if (dimId in reportState.expandedCells) {
                    if (_.contains(reportState.expandedCells[dimId], lookupKey)) {
                        valuesHTML += format("{1}	", lookupKey, dimValue, dimId);
                        return;
                    }
                }

                valuesHTML += format("{1}	", lookupKey, dimValue, dimId);
            }
            else
	        {
                if (i <= yAxis.length - 1) {
                    valuesHTML += format("{0}	", dimValue);
                }
                else {
		            // FIXME: formatfloat shit
                    if (formatFunctions && formatFunctions[i])
                        valuesHTML += format("{0}	", formatFloat(formatFunctions[i](dimValue)));
                    else
                        valuesHTML += format("{0}	", formatFloat(dimValue));
                }
            }
        });

        if (rowType != 'row')
            return format("
{1}
", rowType, valuesHTML);
        else
            return format("
{0}
", valuesHTML);
    };

    // private

    // adds click handler to expand/collapse buttons in the table
    function hookupExpandCollapse(domId, reportState)
    {
        $(format("#{0} .expandDimension", domId)).click(function(e) {

            e.preventDefault();

            var td = $(this).closest("td");
            var dimId = reportState.dimensionsY[td[0].cellIndex];

            var expandValue = $(this).data("expandcollapse");

            if (dimId in reportState.expandedCells)
                reportState.expandedCells[dimId].push(expandValue);
            else
                reportState.expandedCells[dimId] = [expandValue];

            // we don't need data-expandcollapse as we draw anyway
            $(this).removeClass("expandDimension").addClass("collapseDimension");

            reportState.drawNewData();
        });

        $(format("#{0} .collapseDimension", domId)).click(function(e) {

            e.preventDefault();

            var td = $(this).closest("td");
            var dimId = reportState.dimensionsY[td[0].cellIndex];

	        // FIXME: we need to use sortValue!
            var value = td.text();

	        // FIXME: maybe get the sort value instead of the display value as td.text() is

            // collapse multiple
            // we have encoded the parent values in the collapse img tag
            // so we need to remove these as well
            var cells = _.map($(format("img.collapseDimension[data-expandcollapse*='{0}']", dimId + ':' + value)), function(e) { return $(e).data('expandcollapse'); });

            _.each(reportState.expandedCells, function(tempValues, expandedDim) {
                _.each(cells, function(cell) {
                    reportState.expandedCells[expandedDim] = _.without(reportState.expandedCells[expandedDim], cell);
                    if (reportState.expandedCells[expandedDim].length == 0) {
                        delete reportState.expandedCells[expandedDim];
                        return false; // break
                    }
                });
            });

            // we don't need data-expandcollapse as we draw anyway
            $(this).removeClass("collapseDimension").addClass("expandDimension");

            reportState.drawNewData();
        });
    };

    function headerRowToHTML(values, top)
    {
        var valuesHTML = "";

        var lastValue = "æøå";
        var valuesInARow = 1;
        var waitingToWrite = "";

        _.each(values, function(e, i) {

            if (lastValue != e) {
                if (waitingToWrite != "") {
                    valuesHTML += format(waitingToWrite, valuesInARow);
                    waitingToWrite = "" + e + "	";
                }
                else
                    waitingToWrite = "" + e + "	";
                valuesInARow = 1;
            } else
                valuesInARow += 1;

            lastValue = e;
        });

        if (waitingToWrite != "")
            valuesHTML += format(waitingToWrite, valuesInARow);

        if (top)
            return format("
{0}
", valuesHTML);
        else
            return format("
{0}
", valuesHTML);
    }
};
<|endoftext|>


<commit_before>/**
* @author Virtuosi Media
* @link http://www.virtuosimedia.com
* @version 1.0
* @copyright Copyright (c) 2011, Virtuosi Media
* @license: MIT License
* @description: Replaces a checkbox with a custom display
* Documentation: http://www.virtuosimedia.com
* Requirements: MooTools 1.4 Core - See http://mootools.net
*/
var CheckboxReplace = new Class({

	Implements: [Events, Options],

	options: {
		activeClass: 'active',
		checkedClass: 'checked',
		uncheckedClass: 'unchecked',
		cloneClasses: false
	},

	/**
	 * @param string - selector - The selector for the checkbox element(s) to be replaced
	 * @param object - options - The options object
	 */
	initialize: function(selector, options){
		this.setOptions(options);
		this.checkboxes = $$(selector);
		this.replaceCheckboxes();
	},

	replaceCheckboxes: function(){
		var self = this;
		Array.each(this.checkboxes, function(box){
			var replaceId = (box.get('id')) ? box.get('id')+'Replace' : box.get('name')+'Replace';
			var replaceClass = (box.checked) ? self.options.checkedClass : self.options.uncheckedClass;
			replaceClass = (self.options.cloneClasses) ? replaceClass+' '+box.get('class') : replaceClass;
			var replacement = new Element('a', {
				'id':replaceId,
				'class':replaceClass,
				'name':box.get('name'),
				'tabindex':box.get('tabindex'),
				events: {
					'click': function(){
						box.checked = (box.checked) ? true : false;
						if (box.getParent('label')){
							$(replaceId).toggleClass(self.options.checkedClass).toggleClass(self.options.uncheckedClass);
						}
						box.fireEvent('change');
					},
					'focus': function(){
						this.toggleClass(self.options.activeClass);
						box.fireEvent('focus');
					},
					'blur': function(){
						this.toggleClass(self.options.activeClass);
						box.fireEvent('blur');
					},
					'keydown': function(e){
						if (e.key == 'space'){
							box.checked = (box.checked) ? false : true;
							box.fireEvent('change');
						}
					}
				}
			}).inject(box, 'after');
			box.addEvent('change', function(e){
				$(replaceId).toggleClass(self.options.checkedClass).toggleClass(self.options.uncheckedClass);
			}).setStyle('display', 'none');
		});
	}
});<commit_msg>Fixed checkbox state<commit_after>/**
* @author Virtuosi Media
* @link http://www.virtuosimedia.com
* @version 1.0
* @copyright Copyright (c) 2011, Virtuosi Media
* @license: MIT License
* @description: Replaces a checkbox with a custom display
* Documentation: http://www.virtuosimedia.com
* Requirements: MooTools 1.4 Core - See http://mootools.net
*/
var CheckboxReplace = new Class({

	Implements: [Events, Options],

	options: {
		activeClass: 'active',
		checkedClass: 'checked',
		uncheckedClass: 'unchecked',
		cloneClasses: false
	},

	/**
	 * @param string - selector - The selector for the checkbox element(s) to be replaced
	 * @param object - options - The options object
	 */
	initialize: function(selector, options){
		this.setOptions(options);
		this.checkboxes = $$(selector);
		this.replaceCheckboxes();
	},

	replaceCheckboxes: function(){
		var self = this;
		Array.each(this.checkboxes, function(box){
			var replaceId = (box.get('id')) ? box.get('id')+'Replace' : box.get('name')+'Replace';
			var replaceClass = (box.checked) ? self.options.checkedClass : self.options.uncheckedClass;
			replaceClass = (self.options.cloneClasses) ? replaceClass+' '+box.get('class') : replaceClass;
			var replacement = new Element('a', {
				'id':replaceId,
				'class':replaceClass,
				'name':box.get('name'),
				'tabindex':box.get('tabindex'),
				events: {
					'click': function(){
						box.checked = (!box.checked);
						if (box.getParent('label')){
							$(replaceId).toggleClass(self.options.checkedClass).toggleClass(self.options.uncheckedClass);
						}
						box.fireEvent('change');
					},
					'focus': function(){
						this.toggleClass(self.options.activeClass);
						box.fireEvent('focus');
					},
					'blur': function(){
						this.toggleClass(self.options.activeClass);
						box.fireEvent('blur');
					},
					'keydown': function(e){
						if (e.key == 'space'){
							box.checked = (box.checked) ? false : true;
							box.fireEvent('change');
						}
					}
				}
			}).inject(box, 'after');
			box.addEvent('change', function(e){
				$(replaceId).toggleClass(self.options.checkedClass).toggleClass(self.options.uncheckedClass);
			}).setStyle('display', 'none');
		});
	}
});<|endoftext|>