<commit_before>/* eslint-disable no-console */

"use strict";

const fs = require("fs");
//const Promise = require("bluebird");

const ServiceBroker = require("../src/service-broker");
const Transporters = require("../src/transporters");
const Middlewares = require("../src/middlewares");

const someData = JSON.parse(fs.readFileSync("./benchmark/data/10k.json", "utf8"));

function createBrokers(Transporter, opts) {
	const b1 = new ServiceBroker({
		nodeID: "node-1",
		logger: true,
		transporter: new Transporter(opts),
		//internalMiddlewares: false,
		middlewares: [
			//Middlewares.Transmit.Encryption("moleculer"),
			//Middlewares.Transmit.Compression(),
		],
		//Promise
	});

	const b2 = new ServiceBroker({
		nodeID: "node-2",
		logger: true,
		transporter: new Transporter(opts),
		//internalMiddlewares: false,
		middlewares: [
			//Middlewares.Transmit.Encryption("moleculer"),
			//Middlewares.Transmit.Compression(),
		],
		//Promise
	});

	b2.createService({
		name: "echo",
		actions: {
			reply(ctx) {
				return ctx.params;
			},
			big(ctx) {
				return someData;
			}
		}
	});

	return b1.Promise.all([
		b1.start(),
		b2.start(),
	]).then(() => [b1, b2]);
}

createBrokers(Transporters.Fake).then(([b1, b2]) => {

	let count = 0;
	function doRequest() {
		count++;
		return b2.call("echo.reply", { a: count }).then(res => {
			if (count % 10000) {
				// Fast cycle
				doRequest();
			} else {
				// Slow cycle
				setImmediate(() => doRequest());
			}
			return res;

		}).catch(err => {
			throw err;
		});
	}

	setTimeout(() => {
		let startTime = Date.now();

		setInterval(() => {
			let rps = count / ((Date.now() - startTime) / 1000);
			console.log("RPS:", rps.toLocaleString("hu-HU", { maximumFractionDigits: 0 }), "req/s");
			count = 0;
			startTime = Date.now();
		}, 1000);

		b1.waitForServices(["echo"]).then(() => doRequest());

	}, 1000);

});
<commit_msg>add async benchmark result<commit_after>/* eslint-disable no-console */

"use strict";

const fs = require("fs");
//const Promise = require("bluebird");

const ServiceBroker = require("../src/service-broker");
const Transporters = require("../src/transporters");
const Middlewares = require("../src/middlewares");

const { AsyncLocalStorage } = require("async_hooks");

const asyncLocalStorage = new AsyncLocalStorage();

const AsyncLocalStorageMiddleware = {
	localAction(handler) {
		return (ctx) => asyncLocalStorage.run(ctx, () => handler(ctx));
	},
};
/*
const async_hooks = require("async_hooks");
const hook = async_hooks.createHook({
	init(asyncId, type, triggerAsyncId, resource) { },
	before(asyncId) { },
	after(asyncId) { },
	destroy(asyncId) { },
	promiseResolve(asyncId) { },
});
hook.enable();
*/

const someData = JSON.parse(fs.readFileSync("./benchmark/data/10k.json", "utf8"));

function createBrokers(Transporter, opts) {
	const b1 = new ServiceBroker({
		nodeID: "node-1",
		logger: true,
		transporter: new Transporter(opts),
		//internalMiddlewares: false,
		middlewares: [
			//AsyncLocalStorageMiddleware
			//Middlewares.Transmit.Encryption("moleculer"),
			//Middlewares.Transmit.Compression(),
		],
		//Promise
	});

	const b2 = new ServiceBroker({
		nodeID: "node-2",
		logger: true,
		transporter: new Transporter(opts),
		//internalMiddlewares: false,
		middlewares: [
			//AsyncLocalStorageMiddleware
			//Middlewares.Transmit.Encryption("moleculer"),
			//Middlewares.Transmit.Compression(),
		],
		//Promise
	});

	b2.createService({
		name: "echo",
		actions: {
			reply(ctx) {
				return ctx.params;
			},
			big(ctx) {
				return someData;
			}
		}
	});

	return b1.Promise.all([
		b1.start(),
		b2.start(),
	]).then(() => [b1, b2]);
}

createBrokers(Transporters.Fake).then(([b1, b2]) => {

	let count = 0;
	function doRequest() {
		count++;
		return b1.call("echo.reply", { a: count }).then(res => {
			if (count % 10000) {
				// Fast cycle
				doRequest();
			} else {
				// Slow cycle
				setImmediate(() => doRequest());
			}
			return res;

		}).catch(err => {
			throw err;
		});
	}

	setTimeout(() => {
		let startTime = Date.now();

		setInterval(() => {
			let rps = count / ((Date.now() - startTime) / 1000);
			console.log("RPS:", rps.toLocaleString("hu-HU", { maximumFractionDigits: 0 }), "req/s");
			count = 0;
			startTime = Date.now();
		}, 1000);

		b1.waitForServices(["echo"]).then(() => doRequest());

	}, 1000);

});

/*
Local calls
============

No async:  			1 523 904 req/s
AsyncLocalStorage: 	  185 005 req/s
Async hooks:		  108 803 req/s

Remote calls
=============

No async: 			   57 197 req/s
AsyncLocalStorage:	   28 791 req/s
Async hooks:		   20 790 req/s

*/
<|endoftext|>


<commit_before>'use strict';

var createScatterLine = require('./line-with-markers'),
    str2RgbaArray = require('./str2rgbarray'),
    calculateError = require('./calc-errors');

function Scatter3D (config) {

    this.config = config;

}

module.exports = Scatter3D;

var proto = Scatter3D.prototype;

proto.attributes = {
    x: {type: 'data_array'},
    y: {type: 'data_array'},
    z: {type: 'data_array'},
    text: {from: 'Scatter'},
    surfaceaxis: {
        type: 'enumerated',
        values: [-1,0,1,2],
        dflt: -1
    },
    surfacecolor: {
        type: 'color'
    },
    projection: {
        x: {
            show: {
                type: 'boolean',
                dflt: false
            },
            opacity: {
                type: 'number',
                min: 0,
                max: 1,
                dflt: 1
            },
            scale: {
                type: 'number',
                min: 0,
                max: 10,
                dflt: 2/3
            }
        },
        y: {
            show: {
                type: 'boolean',
                dflt: false
            },
            opacity: {
                type: 'number',
                min: 0,
                max: 1,
                dflt: 1
            },
            scale: {
                type: 'number',
                min: 0,
                max: 10,
                dflt: 2/3
            }
        },
        z: {
            show: {
                type: 'boolean',
                dflt: false
            },
            opacity: {
                type: 'number',
                min: 0,
                max: 1,
                dflt: 1
            },
            scale: {
                type: 'number',
                min: 0,
                max: 10,
                dflt: 2/3
            }
        }
    },
    mode: {from: 'Scatter'},
    line: {
        color: {from: 'Scatter'},
        width: {from: 'Scatter'},
        dash: {from: 'Scatter'}
    },
    marker: {
        symbol: {from: 'Scatter'},
        size: {from: 'Scatter'},
        opacity: {from: 'Scatter'},
        line: {
            color: {from: 'Scatter'},
            width: {from: 'Scatter'}
        }

    },
    textposition: {from: 'Scatter'},
    textfont: {from: 'Scatter'}
};

proto.supplyXYZ = function (traceIn, traceOut) {
    var _this = this;
    var Plotly = this.config.Plotly;

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt);
    }

    var len = 0,
        x = coerce('x'),
        y = coerce('y'),
        z = coerce('z');

    if (x && y && z) {
        len = Math.min(x.length, y.length, z.length);
        if(len<x.length) traceOut.x = x.slice(0, len);
        if(len<y.length) traceOut.y = y.slice(0, len);
        if(len<z.length) traceOut.z = z.slice(0, len);
    }

    return len;
};

proto.supplyDefaults = function (traceIn, traceOut, defaultColor, layout) {
    var _this = this;
    var Plotly = this.config.Plotly;
    var Scatter = Plotly.Scatter;
    var linecolor, markercolor;

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt);
    }

    function coerceScatter(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, Scatter.attributes, attr, dflt);
    }

    this.supplyXYZ(traceIn, traceOut);

    if (coerceScatter('text')) coerceScatter('mode', 'lines+markers+text');
    else coerceScatter('mode', 'lines+markers');

    if (Scatter.hasLines(traceOut)) {
        linecolor = coerceScatter('line.color', (traceIn.marker||{}).color || defaultColor);
        coerceScatter('line.width');
        coerceScatter('line.dash');
    }

    if (Scatter.hasMarkers(traceOut)) {
        markercolor = coerceScatter('marker.color', defaultColor);
        coerceScatter('marker.symbol');
        coerceScatter('marker.size', 8);
        coerceScatter('marker.opacity', 1);
        coerceScatter('marker.line.width', 0);
        // TODO parity with scatter.js
        coerceScatter('marker.line.color', 'rgb(0,0,0)');
    }

    if (Scatter.hasText(traceOut)) {
        coerceScatter('textposition', 'top center');
        coerceScatter('textfont', layout.font);
    }

    if (coerce('surfaceaxis') >= 0) coerce('surfacecolor', linecolor || markercolor);

    var dims = ['x','y','z'];
    for (var i = 0; i < 3; ++i) {
        var projection = 'projection.' + dims[i];
        if (coerce(projection+'.show')) {
            // adaptor until Mikola makes axes independent projection configs
            coerce('projection.x.opacity');
            coerce('projection.x.scale');
            //
            coerce(projection+'.opacity');
            coerce(projection+'.scale');
        }
    }

    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'});
    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'});
    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'});

};

function isTrue (bool) {
    return bool;
}

function calculateErrorParams(errors) {
    /*jshint camelcase: false */
    var capSize = [0.0, 0.0, 0.0], i, e;
    var color = [[0,0,0],[0,0,0],[0,0,0]];
    var lineWidth = [0.0, 0.0, 0.0];
    for(i=0; i<3; ++i) {
        e = errors[i];
        if (e && e.copy_zstyle !== false) {
            e = errors[2];
        }
        if(!e) continue;
        capSize[i] = e.width / 100.0;  //Ballpark rescaling, attempt to make consistent with plot.ly
        color[i] = str2RgbaArray(e.color);
        lineWidth = e.thickness;

    }
    return {capSize: capSize, color: color, lineWidth: lineWidth};
}

function calculateTextOffset(textposition) {
    //Read out text properties
    var textOffset = [0,0];
    if (textposition.indexOf('bottom') >= 0) {
        textOffset[1] += 1;
    }
    if (textposition.indexOf('top') >= 0) {
        textOffset[1] -= 1;
    }
    if (textposition.indexOf('left') >= 0) {
        textOffset[0] -= 1;
    }
    if (textposition.indexOf('right') >= 0) {
        textOffset[0] += 1;
    }
    return textOffset;
}

function colorFormatter(colorIn, opacityIn) {
    var colorOut = null,
        colorLength = colorIn.length;
    if (Array.isArray(colorIn)) {
        colorOut = [];
        for (var i = 0; i < colorLength; ++i) {
            colorOut[i]     = str2RgbaArray(colorIn[i]);
            colorOut[i][3] *= opacityIn;
        }
    } else {
            colorOut     = str2RgbaArray(colorIn);
            colorOut[3] *= opacityIn;
    }
    return colorOut;
}

function sizeScaler(sizeIn) {
    var sizeOut = null;
    // rough parity with Plotly 2D markers
    function scale(size) { return size * 2; }
    if (Array.isArray(sizeIn)) {
        sizeOut = sizeIn.map(scale);
    } else {
        sizeOut = scale(sizeIn);
    }
    return sizeOut;
}

proto.update = function update (scene, sceneLayout, data, scatter) {
    /*jshint camelcase: false */
    // handle visible trace cases

    var params, i,
        points = [],
        xaxis = sceneLayout.xaxis,
        yaxis = sceneLayout.yaxis,
        zaxis = sceneLayout.zaxis,
        errorParams = calculateErrorParams([ data.error_x, data.error_y, data.error_z ]),
        xc, x = data.x,
        yc, y = data.y,
        zc, z = data.z,
        len = x.length;

    //Convert points
    for (i = 0; i < len; i++) {
        // sanitize numbers and apply transforms based on axes.type
        xc = xaxis.d2l(x[i]);
        yc = yaxis.d2l(y[i]);
        zc = zaxis.d2l(z[i]);

        points[i] = [xc, yc, zc];
    }

    //Build object parameters
    params = {
        position: points,
        mode:     data.mode
    };

    if ('line' in data) {
        params.lineColor     = str2RgbaArray(data.line.color);
        params.lineWidth     = data.line.width;
        params.lineDashes    = data.line.dash;
    }

    if ('marker' in data) {
        params.scatterColor         = colorFormatter(data.marker.color, data.marker.opacity);
        params.scatterSize          = sizeScaler(data.marker.size);
        params.scatterMarker        = this.markerSymbols[data.marker.symbol];
        params.scatterLineWidth     = data.marker.line.width;
        params.scatterLineColor     = str2RgbaArray(data.marker.line.color);
        params.scatterLineColor[3] *= data.marker.opacity;
        params.scatterAngle         = 0;
    }

    if ('textposition' in data) {
        params.text           = data.text;
        params.textOffset     = calculateTextOffset(data.textposition);
        params.textColor      = str2RgbaArray(data.textfont.color);
        params.textSize       = data.textfont.size;
        params.textFont       = data.textfont.family;
        params.textAngle      = 0;
    }

    var dims = ['x', 'y', 'z'];
    params.project = [];
    for (i = 0; i < 3; ++i) {
        var projection = data.projection[dims[i]];
        if ((params.project[i] = projection.show)) {
            // Mikolas API doesn't current support axes dependent
            // configuration. Its coming though.
            params.projectOpacity = data.projection.x.opacity;
            params.projectScale = data.projection.x.scale;
        }
    }

    params.errorBounds    = calculateError(data);
    params.errorColor     = errorParams.color;
    params.errorLineWidth = errorParams.lineWidth;
    params.errorCapSize   = errorParams.capSize;

    params.delaunayAxis       = data.surfaceaxis;
    params.delaunayColor      = str2RgbaArray(data.surfacecolor);

    if (scatter) scatter.update(params);
    else {
        var pickIds = scene.allocIds(4);

        params.pickId0   = pickIds.ids[0];
        params.pickId1   = pickIds.ids[1];
        params.pickId2   = pickIds.ids[2];
        params.pickId3   = pickIds.ids[3];
        scatter          = createScatterLine(scene.shell.gl, params);
        scatter.groupId  = pickIds.group;
        scatter.plotlyType  = data.type;
    }

    scatter.uid = data.uid;

    return scatter;
};

proto.markerSymbols = {
    'circle': '●',
    'circle-open': '○',
    'square': '■',
    'square-open': '□',
    'diamond': '◆',
    'diamond-open': '◇',
    'cross': '+',
    'x': '❌'
};
<commit_msg>rm logic around coerce('text'), let .hasText handle it.<commit_after>'use strict';

var createScatterLine = require('./line-with-markers'),
    str2RgbaArray = require('./str2rgbarray'),
    calculateError = require('./calc-errors');

function Scatter3D (config) {

    this.config = config;

}

module.exports = Scatter3D;

var proto = Scatter3D.prototype;

proto.attributes = {
    x: {type: 'data_array'},
    y: {type: 'data_array'},
    z: {type: 'data_array'},
    text: {from: 'Scatter'},
    surfaceaxis: {
        type: 'enumerated',
        values: [-1,0,1,2],
        dflt: -1
    },
    surfacecolor: {
        type: 'color'
    },
    projection: {
        x: {
            show: {
                type: 'boolean',
                dflt: false
            },
            opacity: {
                type: 'number',
                min: 0,
                max: 1,
                dflt: 1
            },
            scale: {
                type: 'number',
                min: 0,
                max: 10,
                dflt: 2/3
            }
        },
        y: {
            show: {
                type: 'boolean',
                dflt: false
            },
            opacity: {
                type: 'number',
                min: 0,
                max: 1,
                dflt: 1
            },
            scale: {
                type: 'number',
                min: 0,
                max: 10,
                dflt: 2/3
            }
        },
        z: {
            show: {
                type: 'boolean',
                dflt: false
            },
            opacity: {
                type: 'number',
                min: 0,
                max: 1,
                dflt: 1
            },
            scale: {
                type: 'number',
                min: 0,
                max: 10,
                dflt: 2/3
            }
        }
    },
    mode: {from: 'Scatter'},
    line: {
        color: {from: 'Scatter'},
        width: {from: 'Scatter'},
        dash: {from: 'Scatter'}
    },
    marker: {
        symbol: {from: 'Scatter'},
        size: {from: 'Scatter'},
        opacity: {from: 'Scatter'},
        line: {
            color: {from: 'Scatter'},
            width: {from: 'Scatter'}
        }

    },
    textposition: {from: 'Scatter'},
    textfont: {from: 'Scatter'}
};

proto.supplyXYZ = function (traceIn, traceOut) {
    var _this = this;
    var Plotly = this.config.Plotly;

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt);
    }

    var len = 0,
        x = coerce('x'),
        y = coerce('y'),
        z = coerce('z');

    if (x && y && z) {
        len = Math.min(x.length, y.length, z.length);
        if(len<x.length) traceOut.x = x.slice(0, len);
        if(len<y.length) traceOut.y = y.slice(0, len);
        if(len<z.length) traceOut.z = z.slice(0, len);
    }

    return len;
};

proto.supplyDefaults = function (traceIn, traceOut, defaultColor, layout) {
    var _this = this;
    var Plotly = this.config.Plotly;
    var Scatter = Plotly.Scatter;
    var linecolor, markercolor;

    function coerce(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, _this.attributes, attr, dflt);
    }

    function coerceScatter(attr, dflt) {
        return Plotly.Lib.coerce(traceIn, traceOut, Scatter.attributes, attr, dflt);
    }

    this.supplyXYZ(traceIn, traceOut);

    coerceScatter('text');
    coerceScatter('mode', 'lines+markers');

    if (Scatter.hasLines(traceOut)) {
        linecolor = coerceScatter('line.color', (traceIn.marker||{}).color || defaultColor);
        coerceScatter('line.width');
        coerceScatter('line.dash');
    }

    if (Scatter.hasMarkers(traceOut)) {
        markercolor = coerceScatter('marker.color', defaultColor);
        coerceScatter('marker.symbol');
        coerceScatter('marker.size', 8);
        coerceScatter('marker.opacity', 1);
        coerceScatter('marker.line.width', 0);
        // TODO parity with scatter.js
        coerceScatter('marker.line.color', 'rgb(0,0,0)');
    }

    if (Scatter.hasText(traceOut)) {
        coerceScatter('textposition', 'top center');
        coerceScatter('textfont', layout.font);
    }

    if (coerce('surfaceaxis') >= 0) coerce('surfacecolor', linecolor || markercolor);

    var dims = ['x','y','z'];
    for (var i = 0; i < 3; ++i) {
        var projection = 'projection.' + dims[i];
        if (coerce(projection+'.show')) {
            // adaptor until Mikola makes axes independent projection configs
            coerce('projection.x.opacity');
            coerce('projection.x.scale');
            //
            coerce(projection+'.opacity');
            coerce(projection+'.scale');
        }
    }

    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'z'});
    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y', inherit: 'z'});
    Plotly.ErrorBars.supplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'});

};

function isTrue (bool) {
    return bool;
}

function calculateErrorParams(errors) {
    /*jshint camelcase: false */
    var capSize = [0.0, 0.0, 0.0], i, e;
    var color = [[0,0,0],[0,0,0],[0,0,0]];
    var lineWidth = [0.0, 0.0, 0.0];
    for(i=0; i<3; ++i) {
        e = errors[i];
        if (e && e.copy_zstyle !== false) {
            e = errors[2];
        }
        if(!e) continue;
        capSize[i] = e.width / 100.0;  //Ballpark rescaling, attempt to make consistent with plot.ly
        color[i] = str2RgbaArray(e.color);
        lineWidth = e.thickness;

    }
    return {capSize: capSize, color: color, lineWidth: lineWidth};
}

function calculateTextOffset(textposition) {
    //Read out text properties
    var textOffset = [0,0];
    if (textposition.indexOf('bottom') >= 0) {
        textOffset[1] += 1;
    }
    if (textposition.indexOf('top') >= 0) {
        textOffset[1] -= 1;
    }
    if (textposition.indexOf('left') >= 0) {
        textOffset[0] -= 1;
    }
    if (textposition.indexOf('right') >= 0) {
        textOffset[0] += 1;
    }
    return textOffset;
}

function colorFormatter(colorIn, opacityIn) {
    var colorOut = null,
        colorLength = colorIn.length;
    if (Array.isArray(colorIn)) {
        colorOut = [];
        for (var i = 0; i < colorLength; ++i) {
            colorOut[i]     = str2RgbaArray(colorIn[i]);
            colorOut[i][3] *= opacityIn;
        }
    } else {
            colorOut     = str2RgbaArray(colorIn);
            colorOut[3] *= opacityIn;
    }
    return colorOut;
}

function sizeScaler(sizeIn) {
    var sizeOut = null;
    // rough parity with Plotly 2D markers
    function scale(size) { return size * 2; }
    if (Array.isArray(sizeIn)) {
        sizeOut = sizeIn.map(scale);
    } else {
        sizeOut = scale(sizeIn);
    }
    return sizeOut;
}

proto.update = function update (scene, sceneLayout, data, scatter) {
    /*jshint camelcase: false */
    // handle visible trace cases

    var params, i,
        points = [],
        xaxis = sceneLayout.xaxis,
        yaxis = sceneLayout.yaxis,
        zaxis = sceneLayout.zaxis,
        errorParams = calculateErrorParams([ data.error_x, data.error_y, data.error_z ]),
        xc, x = data.x,
        yc, y = data.y,
        zc, z = data.z,
        len = x.length;

    //Convert points
    for (i = 0; i < len; i++) {
        // sanitize numbers and apply transforms based on axes.type
        xc = xaxis.d2l(x[i]);
        yc = yaxis.d2l(y[i]);
        zc = zaxis.d2l(z[i]);

        points[i] = [xc, yc, zc];
    }

    //Build object parameters
    params = {
        position: points,
        mode:     data.mode
    };

    if ('line' in data) {
        params.lineColor     = str2RgbaArray(data.line.color);
        params.lineWidth     = data.line.width;
        params.lineDashes    = data.line.dash;
    }

    if ('marker' in data) {
        params.scatterColor         = colorFormatter(data.marker.color, data.marker.opacity);
        params.scatterSize          = sizeScaler(data.marker.size);
        params.scatterMarker        = this.markerSymbols[data.marker.symbol];
        params.scatterLineWidth     = data.marker.line.width;
        params.scatterLineColor     = str2RgbaArray(data.marker.line.color);
        params.scatterLineColor[3] *= data.marker.opacity;
        params.scatterAngle         = 0;
    }

    if ('textposition' in data) {
        params.text           = data.text;
        params.textOffset     = calculateTextOffset(data.textposition);
        params.textColor      = str2RgbaArray(data.textfont.color);
        params.textSize       = data.textfont.size;
        params.textFont       = data.textfont.family;
        params.textAngle      = 0;
    }

    var dims = ['x', 'y', 'z'];
    params.project = [];
    for (i = 0; i < 3; ++i) {
        var projection = data.projection[dims[i]];
        if ((params.project[i] = projection.show)) {
            // Mikolas API doesn't current support axes dependent
            // configuration. Its coming though.
            params.projectOpacity = data.projection.x.opacity;
            params.projectScale = data.projection.x.scale;
        }
    }

    params.errorBounds    = calculateError(data);
    params.errorColor     = errorParams.color;
    params.errorLineWidth = errorParams.lineWidth;
    params.errorCapSize   = errorParams.capSize;

    params.delaunayAxis       = data.surfaceaxis;
    params.delaunayColor      = str2RgbaArray(data.surfacecolor);

    if (scatter) scatter.update(params);
    else {
        var pickIds = scene.allocIds(4);

        params.pickId0   = pickIds.ids[0];
        params.pickId1   = pickIds.ids[1];
        params.pickId2   = pickIds.ids[2];
        params.pickId3   = pickIds.ids[3];
        scatter          = createScatterLine(scene.shell.gl, params);
        scatter.groupId  = pickIds.group;
        scatter.plotlyType  = data.type;
    }

    scatter.uid = data.uid;

    return scatter;
};

proto.markerSymbols = {
    'circle': '●',
    'circle-open': '○',
    'square': '■',
    'square-open': '□',
    'diamond': '◆',
    'diamond-open': '◇',
    'cross': '+',
    'x': '❌'
};
<|endoftext|>


<commit_before>var iconv = require('iconv-lite');
var zlib = require('zlib');
var MAX_REQ_SIZE = 256 * 1024;
var MAX_RES_SIZE = 512 * 1024;
var TIMEOUT = 36000;
var CLEAR_INTERVAL = 5000;
var CACHE_TIME = CLEAR_INTERVAL * 2;
var MAX_LENGTH = 512;
var MIN_LENGTH = 412;
var COUNT = 100;
var count = 0;
var ids = [];
var data = {};
var proxy, binded, timeout, interval, util;

function disable() {
	proxy.removeListener('request', handleRequest);
	proxy.removeListener('tunnel', handleTunnel);
	proxy.removeListener('tunnelProxy', handleTunnelProxy);
	proxy.removeListener('websocket', handleWebsocket);

	ids = [];
	data = {};
	interval && clearInterval(interval);
	interval = null;
	binded = false;
}

function enable() {
	if (!binded) {
		proxy.on('request', handleRequest);
		proxy.on('tunnel', handleTunnel);
		proxy.on('tunnelProxy', handleTunnelProxy);
		proxy.on('websocket', handleWebsocket);
	}

	binded = true;
	clearTimeout(timeout);
	timeout = setTimeout(disable, TIMEOUT);
	if (!interval) {
		interval = setInterval(clearCache, CLEAR_INTERVAL);
	}
}

/**
 * 如果超过最大缓存数，清理如下请求数据：
 * 1. 已经请求结束且结束时间超过10秒
 * 2. 请求#1前面的未结束且未被ui读取过的请求
 */
function clearCache() {
	var len = ids.length;
	if (len <= MAX_LENGTH) {
		return;
	}

	var index = -1; //已经完成，且缓存超过10s的最后一个请求
	var now = Date.now();
	for (var i = len - 1; i >= 0; i--) {
		var curData = data[ids[i]];
		if (curData.endTime && now - curData.endTime > TIMEOUT) {
			index = i;
			break;
		}
	}

	if (index < 0) {
		return;
	}

	var _ids = [];
	var end;
	++index;
	for (var i = 0; i < index; i++) {
		var id = ids[i];
		var curData = data[id];
		if (curData.read && (!curData.endTime || now - curData.endTime < CACHE_TIME
				|| now - curData.startTime < TIMEOUT)) {
			_ids.push(id);
		} else {
			delete data[id];
			if (--len <= MIN_LENGTH) {
				_ids.push.apply(_ids, ids.slice(i + 1, index));
				break;
			}
		}
	}
	ids = _ids.concat(ids.slice(index));
}

function passThrough(chunk, encoding, callback) {
	callback(null, chunk);
}

function decode(body) {
	if (body) {
		var _body = body + '';
		if (_body.indexOf('�') != -1) {
			_body = iconv.decode(body, 'gbk');
		}
		body = _body;
	}

	return body;
}

function get(options) {
	enable();
	options = options || {};
	var data = {};
	var newIds = options.startTime == -1 ? [] : getIds(options.startTime, options.count);
	var list = getList(newIds).concat(getList(options.ids));
	for (var i = 0, len = list.length; i < len; i++) {
		var item = list[i];
		if (item) {
			data[item.id] = item;
		}
	}

	return {
		ids: options.ids || [],
		newIds: newIds,
		data: data
	};
}

function getIds(startTime, count) {
	var len = ids.length;
	if (!len) {
		return [];
	}

	startTime = ((startTime || Date.now() - 6000) + '').split('-');
	count = Math.min(count || COUNT, len);

	startTime[0] = parseInt(startTime[0], 10) || 0;
	startTime[1] = parseInt(startTime[1], 10) || 0;

	if (compareId(ids[0], startTime)) {

		return ids.slice(0, count);
	}

	var end = len - 1;
	if (!end || !compareId(ids[end], startTime)) {

		return  [];
	}

	var index = getIndex(startTime, 0, end);
	return ids.slice(index, index + count);
}

function compareId(curId, refId) {
	curId = curId.split('-');
	return curId[0] > refId[0] || (curId[0] == refId[0] && curId[1] > refId[1]);
}

function getIndex(startTime, start, end) {
	if (end - start <= 1) {
		return compareId(ids[start], startTime) ? start : end;
	}

	var mid = Math.floor((start + end) / 2);
	var id = ids[mid];
	return compareId(id, startTime) ? getIndex(startTime, start, mid) : getIndex(startTime, mid + 1, end);
}

function getList(ids) {
	var result = [];
	for (var i = 0, len = ids && ids.length; i < len; i++) {
		var id = ids[i];
		var curData = data[id];
		if (curData) {
			curData.read = true;
			result[i] = curData;
		}
	}

	return result;
}

function handleTunnel(req) {
	handleTunnelRequest(req, true);
}

function handleTunnelProxy(req) {
	handleTunnelRequest(req);
}

function handleTunnelRequest(req, isHttps) {
	var startTime = Date.now();
	var id = startTime + '-' + ++count;

	ids.push(id);

	var curData = data[id] = {
			id: id,
			url: util.removeProtocol(req.url, true),
			isHttps: true,
			isHttpsProxy: !isHttps,
			startTime: startTime,
			req: {
				method: req.method && req.method.toUpperCase() || 'CONNECT',
				httpVersion: req.httpVersion || '1.1',
	            ip: util.getClientIp(req) || '::ffff:127.0.0.1',
	            headers: req.headers
			},
			res: {
				headers: {}
			},
			rules: req.rules
	};

	req.on('error', handleError);
	req.on('send', update);
	req.on('response', handleResponse);

	function handleError(err) {
		curData.reqError = true;
		curData.res.ip = req.host || '127.0.0.1';
		curData.res.statusCode = 502;
		curData.req.body = util.getErrorStack(err);
	}

	function update() {
		curData.res.ip = req.host || '127.0.0.1';
		curData.customHost = req.customHost;
		curData.realUrl = req.realUrl;
		curData.requestTime = curData.dnsTime = Date.now();
	}

	function handleResponse() {
		curData.res.statusCode = 200;
		curData.responseTime = curData.endTime = Date.now();
		req.removeListener('response', handleResponse);
		req.removeListener('error', handleError);
		req.removeListener('send', update);
	}
}

function handleRequest(req) {
	var startTime = Date.now();
	var id = startTime + '-' + ++count;
	var reqData = {
			method: req.method && req.method.toUpperCase() || 'GET',
			httpVersion: req.httpVersion || '1.1',
            ip: req.ip || '::ffff:127.0.0.1',
            isWhistleHttps: req.isWhistleHttps,
            headers: req.headers
		};
	var resData = {
			ip: req.host
	};

	var curData = data[id] = {
			id: id,
			url: req.url,
			startTime: startTime,
			customHost: req.customHost,
			req: reqData,
			res: resData,
			rules: req.rules
	};

	ids.push(id);
	req.on('response', handleResponse);
	req.on('error', handleReqError);
	req.on('send', update);

	function update() {
		curData.dnsTime = (req.dnsTime || 0) + startTime;
		curData.customHost = req.customHost;
		resData.ip = req.host;
		if (req.realUrl && req.realUrl != req.url) {
			curData.realUrl = req.realUrl;
		}
	}

	function handleReqError(err) {
		update();
		if (reqData.body == null) {
			reqData.body = util.getErrorStack(err);
		}
		curData.endTime = curData.requestTime = Date.now();
		curData.reqError = true;
		clearReqEvents();
		req._transform = passThrough;
		clearReqEvents();
	}

	var reqBody;
	var reqSize = 0;

	if (util.hasRequestBody(req)) {
		reqBody = false;
	}
	req._transform = function(chunk, encoding, callback) {

		if (chunk) {
			if (reqBody || reqBody === false) {
				reqBody = reqBody ? Buffer.concat([reqBody, chunk]) : chunk;
			}
			reqSize += chunk.length;
		}

		if (reqBody && reqBody.length > MAX_REQ_SIZE) {
			reqBody = null;
		}

		if (!chunk) {
			curData.requestTime = Date.now();
			curData.reqEnd = true;
			reqData.size = reqSize;
			reqData.body = decode(reqBody);
		}

		callback(null, chunk);
	};

	function clearReqEvents() {
		req.removeListener('response', handleResponse);
		req.removeListener('error', handleReqError);
	}

	function handleResponse(res) {
		update();
		curData.responseTime = Date.now();
		resData.headers = res.headers;
		resData.statusCode = res.statusCode;
		res.on('error', handleResError);

		function clear() {
			req.removeListener('send', update);
			res.removeListener('error', handleResError);
			clearReqEvents();
		}

		function handleResError(err) {
			resData.ip = req.host;
			resData.body = util.getErrorStack(err);
			curData.endTime = Date.now();
			curData.resError = true;
			res._transform = passThrough;
			clear();
		}

		var resBody;
		var resSize = 0;
		var contentType = util.getContentType(res.headers);
		if (contentType && contentType != 'IMG' && util.hasBody(res)) {
			resBody = false;
		}

		res._transform = function(chunk, encoding, callback) {
			if (chunk) {
				if (resBody || resBody === false) {
					resBody = resBody ? Buffer.concat([resBody, chunk]) : chunk;
				}

				resSize += chunk.length;
			}

			if (resBody && resBody.length > MAX_RES_SIZE) {
				resBody = null;
			}

			if (!chunk) {
				curData.endTime = Date.now();
				curData.resEnd = true;
				resData.size = resSize;
				clear();
				if (resBody) {
					var unzip;
					switch (util.toLowerCase(res.headers['content-encoding'])) {
					    case 'gzip':
					    	unzip = zlib.gunzip.bind(zlib);
					      break;
					    case 'deflate':
					    	unzip = zlib.inflate.bind(zlib);
					      break;
					}

					if (unzip) {
						var next = function(err, body) {
							resData.body = err ? util.getErrorStack(err) : decode(body);
							callback(null, chunk);
						};
						unzip(resBody, function(err, body) {
							if (err) {
								zlib.inflateRaw(resBody, next);
							} else {
								next(err, body);
							}
						});
						return;
					}
				}
				resData.body = decode(resBody);
			}

			callback(null, chunk);
		};
	}

}

function handleWebsocket(req) {
	var startTime = Date.now();
	var id = startTime + '-' + ++count;
	var reqData = {
			method: req.method && req.method.toUpperCase() || 'GET',
			httpVersion: req.httpVersion || '1.1',
            ip: req.ip || '::ffff:127.0.0.1',
            headers: req.headers
		};
	var resData = {};
	var curData = data[id] = {
			id: id,
			url: req.url,
			customHost: req.customHost,
			startTime: startTime,
			dnsTime: startTime,
			req: reqData,
			res: resData,
			rules: req.rules
	};

	ids.push(id);
	req.on('response', handleResponse);
	req.on('error', handleError);
	req.on('send', update);

	function handleError(err) {
		update();
		resData.statusCode = 502;
		resData.headers = {};
		reqData.body = util.getErrorStack(err);
		curData.resEnd = true;
		curData.endTime = curData.requestTime = Date.now();
		req.removeListener('response', handleResponse);
	}
	function update() {
		curData.customHost = req.customHost;
		curData.dnsTime = (req.dnsTime || 0) + startTime;
		curData.rules = req.rules;
		resData.ip = req.host;
		if (req.realUrl && req.realUrl != req.url) {
			curData.realUrl = req.realUrl;
		}
	}

	function handleResponse(res) {
		update();
		curData.responseTime = Date.now();
		curData.endTime = curData.requestTime = Date.now();
		resData.headers = res.headers;
		resData.statusCode = res.statusCode;
		req.removeListener('response', handleResponse);
		req.removeListener('error', handleError);
		req.removeListener('send', update);
	}
}

module.exports = function init(_proxy) {
	proxy = _proxy;
	util = proxy.util;
	module.exports = get;
};<commit_msg>clear events<commit_after>var iconv = require('iconv-lite');
var zlib = require('zlib');
var MAX_REQ_SIZE = 256 * 1024;
var MAX_RES_SIZE = 512 * 1024;
var TIMEOUT = 36000;
var CLEAR_INTERVAL = 5000;
var CACHE_TIME = CLEAR_INTERVAL * 2;
var MAX_LENGTH = 512;
var MIN_LENGTH = 412;
var COUNT = 100;
var count = 0;
var ids = [];
var data = {};
var proxy, binded, timeout, interval, util;

function disable() {
	proxy.removeListener('request', handleRequest);
	proxy.removeListener('tunnel', handleTunnel);
	proxy.removeListener('tunnelProxy', handleTunnelProxy);
	proxy.removeListener('websocket', handleWebsocket);

	ids = [];
	data = {};
	interval && clearInterval(interval);
	interval = null;
	binded = false;
}

function enable() {
	if (!binded) {
		proxy.on('request', handleRequest);
		proxy.on('tunnel', handleTunnel);
		proxy.on('tunnelProxy', handleTunnelProxy);
		proxy.on('websocket', handleWebsocket);
	}

	binded = true;
	clearTimeout(timeout);
	timeout = setTimeout(disable, TIMEOUT);
	if (!interval) {
		interval = setInterval(clearCache, CLEAR_INTERVAL);
	}
}

/**
 * 如果超过最大缓存数，清理如下请求数据：
 * 1. 已经请求结束且结束时间超过10秒
 * 2. 请求#1前面的未结束且未被ui读取过的请求
 */
function clearCache() {
	var len = ids.length;
	if (len <= MAX_LENGTH) {
		return;
	}

	var index = -1; //已经完成，且缓存超过10s的最后一个请求
	var now = Date.now();
	for (var i = len - 1; i >= 0; i--) {
		var curData = data[ids[i]];
		if (curData.endTime && now - curData.endTime > TIMEOUT) {
			index = i;
			break;
		}
	}

	if (index < 0) {
		return;
	}

	var _ids = [];
	var end;
	++index;
	for (var i = 0; i < index; i++) {
		var id = ids[i];
		var curData = data[id];
		if (curData.read && (!curData.endTime || now - curData.endTime < CACHE_TIME
				|| now - curData.startTime < TIMEOUT)) {
			_ids.push(id);
		} else {
			delete data[id];
			if (--len <= MIN_LENGTH) {
				_ids.push.apply(_ids, ids.slice(i + 1, index));
				break;
			}
		}
	}
	ids = _ids.concat(ids.slice(index));
}

function passThrough(chunk, encoding, callback) {
	callback(null, chunk);
}

function decode(body) {
	if (body) {
		var _body = body + '';
		if (_body.indexOf('�') != -1) {
			_body = iconv.decode(body, 'gbk');
		}
		body = _body;
	}

	return body;
}

function get(options) {
	enable();
	options = options || {};
	var data = {};
	var newIds = options.startTime == -1 ? [] : getIds(options.startTime, options.count);
	var list = getList(newIds).concat(getList(options.ids));
	for (var i = 0, len = list.length; i < len; i++) {
		var item = list[i];
		if (item) {
			data[item.id] = item;
		}
	}

	return {
		ids: options.ids || [],
		newIds: newIds,
		data: data
	};
}

function getIds(startTime, count) {
	var len = ids.length;
	if (!len) {
		return [];
	}

	startTime = ((startTime || Date.now() - 6000) + '').split('-');
	count = Math.min(count || COUNT, len);

	startTime[0] = parseInt(startTime[0], 10) || 0;
	startTime[1] = parseInt(startTime[1], 10) || 0;

	if (compareId(ids[0], startTime)) {

		return ids.slice(0, count);
	}

	var end = len - 1;
	if (!end || !compareId(ids[end], startTime)) {

		return  [];
	}

	var index = getIndex(startTime, 0, end);
	return ids.slice(index, index + count);
}

function compareId(curId, refId) {
	curId = curId.split('-');
	return curId[0] > refId[0] || (curId[0] == refId[0] && curId[1] > refId[1]);
}

function getIndex(startTime, start, end) {
	if (end - start <= 1) {
		return compareId(ids[start], startTime) ? start : end;
	}

	var mid = Math.floor((start + end) / 2);
	var id = ids[mid];
	return compareId(id, startTime) ? getIndex(startTime, start, mid) : getIndex(startTime, mid + 1, end);
}

function getList(ids) {
	var result = [];
	for (var i = 0, len = ids && ids.length; i < len; i++) {
		var id = ids[i];
		var curData = data[id];
		if (curData) {
			curData.read = true;
			result[i] = curData;
		}
	}

	return result;
}

function handleTunnel(req) {
	handleTunnelRequest(req, true);
}

function handleTunnelProxy(req) {
	handleTunnelRequest(req);
}

function handleTunnelRequest(req, isHttps) {
	var startTime = Date.now();
	var id = startTime + '-' + ++count;

	ids.push(id);

	var curData = data[id] = {
			id: id,
			url: util.removeProtocol(req.url, true),
			isHttps: true,
			isHttpsProxy: !isHttps,
			startTime: startTime,
			req: {
				method: req.method && req.method.toUpperCase() || 'CONNECT',
				httpVersion: req.httpVersion || '1.1',
	            ip: util.getClientIp(req) || '::ffff:127.0.0.1',
	            headers: req.headers
			},
			res: {
				headers: {}
			},
			rules: req.rules
	};

	req.on('error', handleError);
	req.on('send', update);
	req.on('response', handleResponse);

	function handleError(err) {
		curData.reqError = true;
		curData.res.ip = req.host || '127.0.0.1';
		curData.res.statusCode = 502;
		curData.req.body = util.getErrorStack(err);
	}

	function update() {
		curData.res.ip = req.host || '127.0.0.1';
		curData.customHost = req.customHost;
		curData.realUrl = req.realUrl;
		curData.requestTime = curData.dnsTime = Date.now();
	}

	function handleResponse() {
		curData.res.statusCode = 200;
		curData.responseTime = curData.endTime = Date.now();
		req.removeListener('response', handleResponse);
		req.removeListener('error', handleError);
		req.removeListener('send', update);
	}
}

function handleRequest(req) {
	var startTime = Date.now();
	var id = startTime + '-' + ++count;
	var reqData = {
			method: req.method && req.method.toUpperCase() || 'GET',
			httpVersion: req.httpVersion || '1.1',
            ip: req.ip || '::ffff:127.0.0.1',
            isWhistleHttps: req.isWhistleHttps,
            headers: req.headers
		};
	var resData = {
			ip: req.host
	};

	var curData = data[id] = {
			id: id,
			url: req.url,
			startTime: startTime,
			customHost: req.customHost,
			req: reqData,
			res: resData,
			rules: req.rules
	};

	ids.push(id);
	req.on('response', handleResponse);
	req.on('error', handleReqError);
	req.on('send', update);

	function update() {
		curData.dnsTime = (req.dnsTime || 0) + startTime;
		curData.customHost = req.customHost;
		resData.ip = req.host;
		if (req.realUrl && req.realUrl != req.url) {
			curData.realUrl = req.realUrl;
		}
	}

	function handleReqError(err) {
		update();
		if (reqData.body == null) {
			reqData.body = util.getErrorStack(err);
		}
		curData.endTime = curData.requestTime = Date.now();
		curData.reqError = true;
		clearReqEvents();
		req._transform = passThrough;
		clearReqEvents();
	}

	var reqBody;
	var reqSize = 0;

	if (util.hasRequestBody(req)) {
		reqBody = false;
	}
	req._transform = function(chunk, encoding, callback) {

		if (chunk) {
			if (reqBody || reqBody === false) {
				reqBody = reqBody ? Buffer.concat([reqBody, chunk]) : chunk;
			}
			reqSize += chunk.length;
		}

		if (reqBody && reqBody.length > MAX_REQ_SIZE) {
			reqBody = null;
		}

		if (!chunk) {
			curData.requestTime = Date.now();
			curData.reqEnd = true;
			reqData.size = reqSize;
			reqData.body = decode(reqBody);
		}

		callback(null, chunk);
	};

	function clearReqEvents() {
		req.removeListener('response', handleResponse);
		req.removeListener('error', handleReqError);
	}

	function handleResponse(res) {
		update();
		curData.responseTime = Date.now();
		resData.headers = res.headers;
		resData.statusCode = res.statusCode;
		res.on('error', handleResError);

		function clear() {
			req.removeListener('send', update);
			res.removeListener('error', handleResError);
			clearReqEvents();
		}

		function handleResError(err) {
			resData.ip = req.host;
			resData.body = util.getErrorStack(err);
			curData.endTime = Date.now();
			curData.resError = true;
			res._transform = passThrough;
			clear();
		}

		var resBody;
		var resSize = 0;
		var contentType = util.getContentType(res.headers);
		if (contentType && contentType != 'IMG' && util.hasBody(res)) {
			resBody = false;
		}

		res._transform = function(chunk, encoding, callback) {
			if (chunk) {
				if (resBody || resBody === false) {
					resBody = resBody ? Buffer.concat([resBody, chunk]) : chunk;
				}

				resSize += chunk.length;
			}

			if (resBody && resBody.length > MAX_RES_SIZE) {
				resBody = null;
			}

			if (!chunk) {
				curData.endTime = Date.now();
				curData.resEnd = true;
				resData.size = resSize;
				clear();
				if (resBody) {
					var unzip;
					switch (util.toLowerCase(res.headers['content-encoding'])) {
					    case 'gzip':
					    	unzip = zlib.gunzip.bind(zlib);
					      break;
					    case 'deflate':
					    	unzip = zlib.inflate.bind(zlib);
					      break;
					}

					if (unzip) {
						var next = function(err, body) {
							resData.body = err ? util.getErrorStack(err) : decode(body);
							callback(null, chunk);
						};
						unzip(resBody, function(err, body) {
							if (err) {
								zlib.inflateRaw(resBody, next);
							} else {
								next(err, body);
							}
						});
						return;
					}
				}
				resData.body = decode(resBody);
			}

			callback(null, chunk);
		};
	}

}

function handleWebsocket(req) {
	var startTime = Date.now();
	var id = startTime + '-' + ++count;
	var reqData = {
			method: req.method && req.method.toUpperCase() || 'GET',
			httpVersion: req.httpVersion || '1.1',
            ip: req.ip || '::ffff:127.0.0.1',
            headers: req.headers
		};
	var resData = {};
	var curData = data[id] = {
			id: id,
			url: req.url,
			customHost: req.customHost,
			startTime: startTime,
			dnsTime: startTime,
			req: reqData,
			res: resData,
			rules: req.rules
	};

	ids.push(id);
	req.on('response', handleResponse);
	req.on('error', handleError);
	req.on('send', update);

	function handleError(err) {
		update();
		resData.statusCode = 502;
		resData.headers = {};
		reqData.body = util.getErrorStack(err);
		curData.resEnd = true;
		curData.endTime = curData.requestTime = Date.now();
		req.removeListener('response', handleResponse);
		clear();
	}
	function update() {
		curData.customHost = req.customHost;
		curData.dnsTime = (req.dnsTime || 0) + startTime;
		curData.rules = req.rules;
		resData.ip = req.host;
		if (req.realUrl && req.realUrl != req.url) {
			curData.realUrl = req.realUrl;
		}
	}

	function handleResponse(res) {
		update();
		curData.responseTime = Date.now();
		curData.endTime = curData.requestTime = Date.now();
		resData.headers = res.headers;
		resData.statusCode = res.statusCode;
		clear();
	}

	function clear() {
		req.removeListener('response', handleResponse);
		req.removeListener('error', handleError);
		req.removeListener('send', update);
	}
}

module.exports = function init(_proxy) {
	proxy = _proxy;
	util = proxy.util;
	module.exports = get;
};<|endoftext|>


<commit_before>(function () {

    var root = this;
    var ArrayProto = Array.prototype;
    var slice = ArrayProto.slice;

    function argsToArray(args) {
        return slice.call(args, 0);
    }

    var thumbs;
    if (typeof exports !== 'undefined') {
        thumbs = exports;
    } else {
        thumbs = root.thumbs = {};
    }

    // Require Underscore, if we're on the server, and it's not already present.
    var _ = root._;
    if (!_ && ('undefined' !== typeof require )) {
        _ = require('underscore');
    }
    // Require Backbone, if we're on the server, and it's not already present.
    var Backbone = root.Backbone;
    if (!Backbone && ('undefined' !== typeof require)) {
        Backbone = require('underscore');
    }

    _.extend(thumbs, Backbone);

    // Current version of the library. Keep in sync with `package.json`.
    thumbs.VERSION = '0.0.0';
    thumbs.MULTI_ARG_TOKEN = / +/;
    thumbs.KEY_VALUE_TOKEN = ":";

    //copy over Backbones $ for dom
    var Model = thumbs.Model, View = thumbs.View, Collection = thumbs.Collection, Router = thumbs.Router, History = thumbs.History;

    function splitParts(m, cb) {
        return _.each(m.split(thumbs.MULTI_ARG_TOKEN), function (m) {
            cb(_.map(m.split(thumbs.KEY_VALUE_TOKEN), function (m) {
                return $.trim(m);
            }));
        });
    }

    var _super = {
        _super: (function _super() {

            function findSuper(methodName, childObject) {
                var object = childObject;
                while (object[methodName] === childObject[methodName]) {
                    var constructor = object.__getConstructor();
                    object = constructor['__super__'];
                }

                return object;
            }

            return function __super(methodName, args) {

                // keep track of how far up the prototype chain we have traversed
                if (!this._superCallObjects) {
                    this._superCallObjects = {};
                }

                var currentObject = this._superCallObjects[methodName] || this,
                    parentObject = findSuper(methodName, currentObject);
                this._superCallObjects[methodName] = parentObject;

                var result = parentObject[methodName].apply(this, args || {});
                delete this._superCallObjects[methodName];
                return result;
            };
        }())
    };

    // this allows access to prototype.constructor.__super__
    // when _.bindAll is used
    var _extend = thumbs.Model.extend;

    function extend(protoProps, staticProps) {
        var child = _extend.apply(this, arguments);
        child.prototype.__getConstructor = function () {
            return child;
        };
        return child;
    }

    function Class(options) {
        this.cid = _.uniqueId('class');
        this.initialize.apply(this, arguments);
    }

    _.extend(Class.prototype, thumbs.Events, _super, {
        initialize: function initialize() {
        }
    });

    thumbs.Class = Class;

    Class.extend = Model.extend = View.extend = Collection.extend = Router.extend = History.extend = extend;
    Model = thumbs.Model = Model.extend(_super);
    View = thumbs.View = View.extend(_super);
    Collection = thumbs.Collection = Collection.extend(_super);
    Router = thumbs.Router = Router.extend(_super);

    var EventDelegator = {
        render: function render() {
            this._super('render', arguments);
            this.checkForEvents();
            return this;
        },

        checkForEvents: function checkForEvents() {
            var self = this;
            this.events = this.events || {};
            this.$('[data-thumbs-delegate]').each(function () {
                var $this = $(this), id = _.uniqueId('thumbs_');
                $this.addClass(id);
                splitParts($this.data('thumbs-delegate'), function (data) {
                    var event = data[0], func = data[1];
                    self.events[event + ' .' + id] = func;
                });
            });
            this.delegateEvents();
            return this;
        }
    };

    var Identifier = {
        __identifiers: null,

        initialize: function () {
            this.__identifiers = [];
            this._super('initialize', arguments);
        },

        render: function render() {
            this._super('render', arguments);
            this.checkForIdentifiers();
            return this;
        },

        remove: function () {
            _.each(this.__identifiers, function (id) {
                this[id] = this['$' + id] = null;
            }, this);
            this.__identifiers = [];
            return this._super('remove', arguments);
        },

        checkForIdentifiers: function checkForIdentifiers() {
            var self = this;
            this.$('[data-thumbs-id]').each(function (el) {
                var $this = $(this);
                var id = $this.data('thumbs-id');
                self[id] = this;
                self['$' + id] = $this;
                self.__identifiers.push(id);
            });
            return this;
        }
    };

    var Binder = {

        __monitors: null,

        __events: null,

        initialize: function initialize() {
            this._super("initialize", arguments);
            this.__monitors = {};
            this.__events = {};
        },

        __updateValues: function setValues() {
            if (this.model && this.model instanceof Model) {
                return this.__setValues(this.model.changedAttributes());
            }
        },

        __setValues: function __setValues(values) {
            var monitors = this.__monitors;
            if (monitors) {
                _.each(values, function changedEach(val, key) {
                    var mon = monitors[key];
                    if (mon) {
                        _.each(mon, function (fn) {
                            fn(val);
                        });
                    }
                });
            }
            return this;
        },

        setElData: function setElData(el, data, type, attribute) {
            if (this.checkFormatting) {
                this.checkFormatting(el, data, type);
            } else {
                data = (data !== null && "undefined" !== typeof data) ? data : "";
                var $el = this.$(el);
                if ("function" === typeof $el[type]) {
                    $el[type](data)
                } else {
                    $el.attr(type, data);
                }
            }
        },

        findThumbsBind: function findThumbsBind() {
            var monitors = this.__monitors, events = this.__events, setElData = _.bind(this.setElData, this);

            function setupType(m, el, type) {
                if (!(m in monitors)) {
                    monitors[m] = [];
                }
                monitors[m].push(function monitor(data) {
                    if ("function" === typeof el) {
                        el(data, m);
                    } else {
                        setElData(el, data, type, m);
                    }
                });
            }

            function setupBind(el) {
                var $el = $(el);
                splitParts($el.data("thumbs-bind"), function (mParts) {
                    if (mParts.length === 1) {
                        setupType(mParts[0], el, "text");
                    } else if (mParts.length === 2) {
                        setupType(mParts[1], el, mParts[0]);
                    } else {
                        throw new TypeError("Invalid data-thumbs-bind definition");
                    }
                });
            }

            function setupClassBind(el) {
                var $el = $(el);
                splitParts($el.data("thumbs-bind-class"), function (mParts) {
                    if (mParts.length === 2) {
                        var clazz = mParts[0];
                        setupType(mParts[1], function (data) {
                            $el.toggleClass(clazz, data);
                        });
                    } else {
                        throw new TypeError("Invalid data-thumbs-bind-class definition");
                    }
                });
            }

            var view = this;

            function setupEventBind(el) {
                var $el = $(el);
                splitParts($el.data("thumbs-bind-event"), function (mParts) {
                    if (mParts.length === 2) {
                        var event = mParts[0], eventListeners = events[event];
                        if (!eventListeners) {
                            eventListeners = events[event] = [];
                        }
                        eventListeners.push(view[mParts[1]]);
                    } else {
                        throw new TypeError("Invalid data-thumbs-bind-class definition");
                    }
                });
            }

            this.$("[data-thumbs-bind]").each(function () {
                setupBind(this);
            });
            this.$("[data-thumbs-bind-class]").each(function () {
                setupClassBind(this);
            });
            if (this.$el.is("[data-thumbs-bind]")) {
                setupBind(this.el);
            }
            if (this.$el.is("[data-thumbs-bind-class]")) {
                setupClassBind(this.el);
            }
            if (this.$el.is("[data-thumbs-bind-event]")) {
                setupEventBind(this.el);
            }
            return this;
        },

        turnOnModelListeners: function turnOnModelListeners() {
            var model = this.model;
            if (model) {
                model.on("change", this.__updateValues, this);
                _.each(this.__events, function (eventListeners, event) {
                    eventListeners.fn = function eventListenersFn() {
                        var args = arguments;
                        _.each(eventListeners, function (l) {
                            l.apply(this, args);
                        }, this);
                    };
                    model.on(event, eventListeners.fn, this)
                }, this);
            }
            return this;
        },

        turnOffModelListeners: function turnOnModelListeners() {
            var model = this.model;
            if (model) {
                model.off("change", this.__updateValues, this);
                _.each(this.__events, function (eventListeners, event) {
                    model.off(event, eventListeners.fn, this)
                });
            }
            return this;
        },

        setupBinders: function setUpMonitors() {
            this.findThumbsBind().turnOnModelListeners();
            this.__setValues(this.model.attributes);
            return this;
        },

        render: function render() {
            this._super("render", arguments);
            if (this.model) {
                this.setupBinders();
            }
            return this;
        },

        remove: function remove() {
            this.turnOffModelListeners();
            this.__monitors = this.__events = null;
            return this._super("remove", arguments);
        }

    };

    var Formatter = {

        checkFormatting: function checkFormatting(el, data, type) {
            type = type || "text";
            var $el = this.$(el), args = argsToArray(arguments);
            data = args.length === 3 ? data : $el.text();
            data = (data !== null && "undefined" !== typeof data) ? data : "";

            var formatter = $el.data("thumbs-format");
            data = (data !== null && "undefined" !== typeof data) ? data : "";
            if (formatter && "function" === typeof this[formatter]) {
                data = this[formatter](data);
            }
            if ("function" === typeof $el[type]) {
                $el[type](data)
            } else {
                $el.attr(type, data);
            }
        },

        renderFormatters: function renderFormatters() {
            var checkFormatting = _.bind(this.checkFormatting, this);
            this.$("[data-thumbs-format]").each(function () {
                checkFormatting(this);
            });
            return this;
        },

        render: function render() {
            this._super("render", arguments);
            return this.renderFormatters();
        }

    };

    var ElFinder = {

        findEl: function () {
            var setElement = _.bind(this.setElement, this);
            var el = this.$("[data-thumbs-el]").first().get();
            if (el.length) {
                setElement(el[0]);
            }
            return this;
        },

        render: function () {
            return this.findEl()._super("render", arguments);
        }

    };

    thumbs.templater = (function _templater() {
        var templater = _.template;
        return function __templater(tmplr) {
            if (tmplr) {
                return (templater = tmplr);
            } else {
                return templater;
            }
        };
    }());

    thumbs.Router = Router.extend({
        route: function route(route, name, callback) {
            this._super("route", arguments);
            thumbs.history = Backbone.history;
            return this;
        }
    });

    View = thumbs.View = View.extend(ElFinder).extend(Formatter).extend(Identifier).extend(Binder).extend(EventDelegator).extend({
        _subviews: null,

        initialize: function initialize(options) {
            this._super("initialize", arguments);
            this._subviews = {};
        },

        addSubView: function addSubView(selector, view) {
            this.removeSubView(selector);
            this._subviews[selector] = view;
            view.setElement(this.$(selector)).render();
            //this.assign(this._subviews);
            return this;
        },

        removeSubView: function removeSubView(selector) {
            var view = this._subviews[selector];
            if (view) {
                // undelegate events, but we won't remove the element
                view.setElement(null);
                view.remove();
                this.$(selector).empty();
                delete this._subviews[selector];
            }
            return this;
        },

        removeSubViews: function removeSubViews() {
            _.each(this._subviews, function (view, selector) {
                this.removeSubView(selector);
            }, this);
            return this;
        },

        // default render functionality is to set the el html to the
        // compiled template run with the model data
        // override if that's not what's needed
        render: function render() {
            this._super("render", arguments);
            this.assign(this._subviews);
            return this;
        },

        assign: function assign(selector, view) {
            var selectors;
            if (_.isObject(selector)) {
                selectors = selector;
            } else if (selector) {
                selectors = {};
                selectors[selector] = view;
            }
            if (selectors) {
                _.each(selectors, function (view, selector) {
                    view.setElement(this.$(selector)).render();
                }, this);
            }

            return this;
        },

        // when a view is removed, remove all event bindings
        remove: function remove() {
            this.undelegateEvents();
            this.removeSubViews();
            return this._super('remove', arguments);
        }
    });

    thumbs.TemplateView = View.extend({
        /**@lends thumbs.TemplateView.prototype*/

        /**
         * The the `templater` to use to compile this view template. If not specified them {@link thumbs.templater}
         * will be used.
         *
         * @example
         *
         * var MyView = thumbs.TemplateView.extend({
         *     templater : function(template){
         *          return Handlebars.compile(template);
         *     },
         *
         *     template : "
{{firstName}}
"
         * })
         *
         * @function
         */
        templater: thumbs.templater(),

        /**
         * The template string for this view.
         *
         * @example
         * var MyView = thumbs.TemplateView.extend({
         *     template : "
{{firstName}}
"
         * });
         *
         */
        template: null,

        initialize: function (options) {
            this._super("initialize", arguments);
            if (this.template) {
                this._template = this.templater(this.template);
            }
        },

        /**
         * Gathers data to be interpolated into the template. By default serializes the model to json.
         *
         * @example
         * var MyView = thumbs.TemplateView.extend({
         *     template : "

{{i18n.hello}}

{{firstName}}

",
         *     getTemplateData : function(){
         *         var data = this._super("getTemplateData", arguments);
         *         data.i18n = {hello : "Hello"};
         *         return data;
         *     }
         * });
         *
         * @return {Object} Object with key value pairs to be interpolated into the View.
         */
        getTemplateData: function () {
            return this.model ? this.model.toJSON() : {};
        },

        /**
         * Fills the template with the data gathered from {@link thumbs.TemplateView#getTemplateData}
         * @return {thumbs.TemplateView} this for chaining.
         */
        fillTemplate: function fillTemplate() {
            if (this._template) {
                return this._template(this.getTemplateData());
            } else {
                return null;
            }
        },

        /**
         * Renders the the template.
         *
         * @return this
         */
        renderTemplate: function renderTemplate() {
            if (this._template) {
                var template = this.fillTemplate();
                if (template) {
                    this.$el.html(template);
                }
            }
            return this;
        },

        render: function () {
            //call render template
            return this.renderTemplate()._super("render", arguments);
        }
    });

    if ("undefined" !== typeof exports) {
        if ("undefined" !== typeof module && module.exports) {
            module.exports = thumbs;
        }
    } else if ("function" === typeof define) {
        define(function () {
            return thumbs;
        });
    } else {
        this.thumbs = thumbs;
    }

}).call(this);
<commit_msg>added more intelligent value setting<commit_after>(function () {

    var root = this;
    var ArrayProto = Array.prototype;
    var slice = ArrayProto.slice;

    function argsToArray(args) {
        return slice.call(args, 0);
    }

    var thumbs;
    if (typeof exports !== 'undefined') {
        thumbs = exports;
    } else {
        thumbs = root.thumbs = {};
    }

    // Require Underscore, if we're on the server, and it's not already present.
    var _ = root._;
    if (!_ && ('undefined' !== typeof require )) {
        _ = require('underscore');
    }
    // Require Backbone, if we're on the server, and it's not already present.
    var Backbone = root.Backbone;
    if (!Backbone && ('undefined' !== typeof require)) {
        Backbone = require('underscore');
    }

    _.extend(thumbs, Backbone);

    // Current version of the library. Keep in sync with `package.json`.
    thumbs.VERSION = '0.0.0';
    thumbs.MULTI_ARG_TOKEN = / +/;
    thumbs.KEY_VALUE_TOKEN = ":";

    //copy over Backbones $ for dom
    var Model = thumbs.Model, View = thumbs.View, Collection = thumbs.Collection, Router = thumbs.Router, History = thumbs.History;

    function splitParts(m, cb) {
        return _.each(m.split(thumbs.MULTI_ARG_TOKEN), function (m) {
            cb(_.map(m.split(thumbs.KEY_VALUE_TOKEN), function (m) {
                return $.trim(m);
            }));
        });
    }

    var _super = {
        _super: (function _super() {

            function findSuper(methodName, childObject) {
                var object = childObject;
                while (object[methodName] === childObject[methodName]) {
                    var constructor = object.__getConstructor();
                    object = constructor['__super__'];
                }

                return object;
            }

            return function __super(methodName, args) {

                // keep track of how far up the prototype chain we have traversed
                if (!this._superCallObjects) {
                    this._superCallObjects = {};
                }

                var currentObject = this._superCallObjects[methodName] || this,
                    parentObject = findSuper(methodName, currentObject);
                this._superCallObjects[methodName] = parentObject;

                var result = parentObject[methodName].apply(this, args || {});
                delete this._superCallObjects[methodName];
                return result;
            };
        }())
    };

    // this allows access to prototype.constructor.__super__
    // when _.bindAll is used
    var _extend = thumbs.Model.extend;

    function extend(protoProps, staticProps) {
        var child = _extend.apply(this, arguments);
        child.prototype.__getConstructor = function () {
            return child;
        };
        return child;
    }

    function Class(options) {
        this.cid = _.uniqueId('class');
        this.initialize.apply(this, arguments);
    }

    _.extend(Class.prototype, thumbs.Events, _super, {
        initialize: function initialize() {
        }
    });

    thumbs.Class = Class;

    Class.extend = Model.extend = View.extend = Collection.extend = Router.extend = History.extend = extend;
    Model = thumbs.Model = Model.extend(_super);
    View = thumbs.View = View.extend(_super);
    Collection = thumbs.Collection = Collection.extend(_super);
    Router = thumbs.Router = Router.extend(_super);

    var EventDelegator = {
        render: function render() {
            this._super('render', arguments);
            this.checkForEvents();
            return this;
        },

        checkForEvents: function checkForEvents() {
            var self = this;
            this.events = this.events || {};
            this.$('[data-thumbs-delegate]').each(function () {
                var $this = $(this), id = _.uniqueId('thumbs_');
                $this.addClass(id);
                splitParts($this.data('thumbs-delegate'), function (data) {
                    var event = data[0], func = data[1];
                    self.events[event + ' .' + id] = func;
                });
            });
            this.delegateEvents();
            return this;
        }
    };

    var Identifier = {
        __identifiers: null,

        initialize: function () {
            this.__identifiers = [];
            this._super('initialize', arguments);
        },

        render: function render() {
            this._super('render', arguments);
            this.checkForIdentifiers();
            return this;
        },

        remove: function () {
            _.each(this.__identifiers, function (id) {
                this[id] = this['$' + id] = null;
            }, this);
            this.__identifiers = [];
            return this._super('remove', arguments);
        },

        checkForIdentifiers: function checkForIdentifiers() {
            var self = this;
            this.$('[data-thumbs-id]').each(function (el) {
                var $this = $(this);
                var id = $this.data('thumbs-id');
                self[id] = this;
                self['$' + id] = $this;
                self.__identifiers.push(id);
            });
            return this;
        }
    };

    var Binder = {

        __monitors: null,

        __events: null,

        initialize: function initialize() {
            this._super("initialize", arguments);
            this.__monitors = {};
            this.__events = {};
        },

        __updateValues: function setValues() {
            if (this.model && this.model instanceof Model) {
                return this.__setValues(this.model.changedAttributes());
            }
        },

        __setValues: function __setValues(values) {
            var monitors = this.__monitors;
            if (monitors) {
                _.each(values, function changedEach(val, key) {
                    var mon = monitors[key];
                    if (mon) {
                        _.each(mon, function (fn) {
                            fn(val);
                        });
                    }
                });
            }
            return this;
        },

        setElData: function setElData(el, data, type, attribute) {
            if (this.checkFormatting) {
                this.checkFormatting(el, data, type);
            } else {
                data = (data !== null && "undefined" !== typeof data) ? data : "";
                var $el = this.$(el);
                if (type) {
                    if ("function" === typeof $el[type]) {
                        $el[type](data);
                    } else {
                        $el.attr(type, data);
                    }
                } else {
                    if ($el.is("input")) {
                        if ($el.is("[type=checkbox], [type=radio]")) {
                            $el.attr("checked", data);
                        } else {
                            $el.val(data);
                        }
                    } else {
                        $el.text(data);
                    }
                }
            }
        },

        findThumbsBind: function findThumbsBind() {
            var monitors = this.__monitors, events = this.__events, setElData = _.bind(this.setElData, this);

            function setupType(m, el, type) {
                if (!(m in monitors)) {
                    monitors[m] = [];
                }
                monitors[m].push(function monitor(data) {
                    if ("function" === typeof el) {
                        el(data, m);
                    } else {
                        setElData(el, data, type, m);
                    }
                });
            }

            function setupBind(el) {
                var $el = $(el);
                splitParts($el.data("thumbs-bind"), function (mParts) {
                    if (mParts.length === 1) {
                        setupType(mParts[0], el);
                    } else if (mParts.length === 2) {
                        setupType(mParts[1], el, mParts[0]);
                    } else {
                        throw new TypeError("Invalid data-thumbs-bind definition");
                    }
                });
            }

            function setupClassBind(el) {
                var $el = $(el);
                splitParts($el.data("thumbs-bind-class"), function (mParts) {
                    if (mParts.length === 2) {
                        var clazz = mParts[0];
                        setupType(mParts[1], function (data) {
                            $el.toggleClass(clazz, data);
                        });
                    } else {
                        throw new TypeError("Invalid data-thumbs-bind-class definition");
                    }
                });
            }

            var view = this;

            function setupEventBind(el) {
                var $el = $(el);
                splitParts($el.data("thumbs-bind-event"), function (mParts) {
                    if (mParts.length === 2) {
                        var event = mParts[0], eventListeners = events[event];
                        if (!eventListeners) {
                            eventListeners = events[event] = [];
                        }
                        eventListeners.push(view[mParts[1]]);
                    } else {
                        throw new TypeError("Invalid data-thumbs-bind-class definition");
                    }
                });
            }

            this.$("[data-thumbs-bind]").each(function () {
                setupBind(this);
            });
            this.$("[data-thumbs-bind-event]").each(function () {
                setupEventBind(this);
            });

            this.$("[data-thumbs-bind-class]").each(function () {
                setupClassBind(this);
            });
            if (this.$el.is("[data-thumbs-bind]")) {
                setupBind(this.el);
            }
            if (this.$el.is("[data-thumbs-bind-class]")) {
                setupClassBind(this.el);
            }
            if (this.$el.is("[data-thumbs-bind-event]")) {
                setupEventBind(this.el);
            }
            return this;
        },

        turnOnModelListeners: function turnOnModelListeners() {
            var model = this.model;
            if (model) {
                model.on("change", this.__updateValues, this);
                _.each(this.__events, function (eventListeners, event) {
                    eventListeners.fn = function eventListenersFn() {
                        var args = arguments;
                        _.each(eventListeners, function (l) {
                            l.apply(this, args);
                        }, this);
                    };
                    model.on(event, eventListeners.fn, this)
                }, this);
            }
            return this;
        },

        turnOffModelListeners: function turnOnModelListeners() {
            var model = this.model;
            if (model) {
                model.off("change", this.__updateValues, this);
                _.each(this.__events, function (eventListeners, event) {
                    model.off(event, eventListeners.fn, this)
                });
            }
            return this;
        },

        setupBinders: function setUpMonitors() {
            this.findThumbsBind().turnOnModelListeners();
            this.__setValues(this.model.attributes);
            return this;
        },

        render: function render() {
            this._super("render", arguments);
            if (this.model) {
                this.setupBinders();
            }
            return this;
        },

        remove: function remove() {
            this.turnOffModelListeners();
            this.__monitors = this.__events = null;
            return this._super("remove", arguments);
        }

    };

    var Formatter = {

        checkFormatting: function checkFormatting(el, data, type) {
            var $el = this.$(el), args = argsToArray(arguments);
            data = args.length === 3 ? data : $el.text();
            data = (data !== null && "undefined" !== typeof data) ? data : "";

            var formatter = $el.data("thumbs-format");
            data = (data !== null && "undefined" !== typeof data) ? data : "";
            if (formatter && "function" === typeof this[formatter]) {
                data = this[formatter](data);
            }
            if (type) {
                if ("function" === typeof $el[type]) {
                    $el[type](data);
                } else {
                    $el.attr(type, data);
                }
            } else {
                if ($el.is("input")) {
                    if ($el.is("[type=checkbox], [type=radio]")) {
                        $el.attr("checked", data);
                    } else {
                        $el.val(data);
                    }
                } else {
                    $el.text(data);
                }
            }
        },

        renderFormatters: function renderFormatters() {
            var checkFormatting = _.bind(this.checkFormatting, this);
            this.$("[data-thumbs-format]").each(function () {
                checkFormatting(this);
            });
            return this;
        },

        render: function render() {
            this._super("render", arguments);
            return this.renderFormatters();
        }

    };

    var ElFinder = {

        findEl: function () {
            var setElement = _.bind(this.setElement, this);
            var el = this.$("[data-thumbs-el]").first().get();
            if (el.length) {
                setElement(el[0]);
            }
            return this;
        },

        render: function () {
            return this.findEl()._super("render", arguments);
        }

    };

    thumbs.templater = (function _templater() {
        var templater = _.template;
        return function __templater(tmplr) {
            if (tmplr) {
                return (templater = tmplr);
            } else {
                return templater;
            }
        };
    }());

    thumbs.Router = Router.extend({
        route: function route(route, name, callback) {
            this._super("route", arguments);
            thumbs.history = Backbone.history;
            return this;
        }
    });

    View = thumbs.View = View.extend(ElFinder).extend(Formatter).extend(Identifier).extend(Binder).extend(EventDelegator).extend({
        _subviews: null,

        initialize: function initialize(options) {
            this._super("initialize", arguments);
            this._subviews = {};
        },

        addSubView: function addSubView(selector, view) {
            this.removeSubView(selector);
            this._subviews[selector] = view;
            view.setElement(this.$(selector)).render();
            //this.assign(this._subviews);
            return this;
        },

        removeSubView: function removeSubView(selector) {
            var view = this._subviews[selector];
            if (view) {
                // undelegate events, but we won't remove the element
                view.setElement(null);
                view.remove();
                this.$(selector).empty();
                delete this._subviews[selector];
            }
            return this;
        },

        removeSubViews: function removeSubViews() {
            _.each(this._subviews, function (view, selector) {
                this.removeSubView(selector);
            }, this);
            return this;
        },

        // default render functionality is to set the el html to the
        // compiled template run with the model data
        // override if that's not what's needed
        render: function render() {
            this._super("render", arguments);
            this.assign(this._subviews);
            return this;
        },

        assign: function assign(selector, view) {
            var selectors;
            if (_.isObject(selector)) {
                selectors = selector;
            } else if (selector) {
                selectors = {};
                selectors[selector] = view;
            }
            if (selectors) {
                _.each(selectors, function (view, selector) {
                    view.setElement(this.$(selector)).render();
                }, this);
            }

            return this;
        },

        // when a view is removed, remove all event bindings
        remove: function remove() {
            this.undelegateEvents();
            this.removeSubViews();
            return this._super('remove', arguments);
        }
    });

    thumbs.TemplateView = View.extend({
        /**@lends thumbs.TemplateView.prototype*/

        /**
         * The the `templater` to use to compile this view template. If not specified them {@link thumbs.templater}
         * will be used.
         *
         * @example
         *
         * var MyView = thumbs.TemplateView.extend({
         *     templater : function(template){
         *          return Handlebars.compile(template);
         *     },
         *
         *     template : "
{{firstName}}
"
         * })
         *
         * @function
         */
        templater: thumbs.templater(),

        /**
         * The template string for this view.
         *
         * @example
         * var MyView = thumbs.TemplateView.extend({
         *     template : "
{{firstName}}
"
         * });
         *
         */
        template: null,

        initialize: function (options) {
            this._super("initialize", arguments);
            if (this.template) {
                this._template = this.templater(this.template);
            }
        },

        /**
         * Gathers data to be interpolated into the template. By default serializes the model to json.
         *
         * @example
         * var MyView = thumbs.TemplateView.extend({
         *     template : "

{{i18n.hello}}

{{firstName}}

",
         *     getTemplateData : function(){
         *         var data = this._super("getTemplateData", arguments);
         *         data.i18n = {hello : "Hello"};
         *         return data;
         *     }
         * });
         *
         * @return {Object} Object with key value pairs to be interpolated into the View.
         */
        getTemplateData: function () {
            return this.model ? this.model.toJSON() : {};
        },

        /**
         * Fills the template with the data gathered from {@link thumbs.TemplateView#getTemplateData}
         * @return {thumbs.TemplateView} this for chaining.
         */
        fillTemplate: function fillTemplate() {
            if (this._template) {
                return this._template(this.getTemplateData());
            } else {
                return null;
            }
        },

        /**
         * Renders the the template.
         *
         * @return this
         */
        renderTemplate: function renderTemplate() {
            if (this._template) {
                var template = this.fillTemplate();
                if (template) {
                    this.$el.html(template);
                }
            }
            return this;
        },

        render: function () {
            //call render template
            return this.renderTemplate()._super("render", arguments);
        }
    });

    if ("undefined" !== typeof exports) {
        if ("undefined" !== typeof module && module.exports) {
            module.exports = thumbs;
        }
    } else if ("function" === typeof define) {
        define(function () {
            return thumbs;
        });
    } else {
        this.thumbs = thumbs;
    }

}).call(this);
<|endoftext|>