<commit_before>var url = require('url');

var webdriver	= require('wd'),
	promises	= require('q');

var Runner = new Class( /** @lends Runner# */ {

	Extends: require('events').EventEmitter,

	/** The promise object for results, resolved when all features of this Runner have been evaluated.
	*@type	{Promise}
	*/
	promise: null,

	/** A hash mapping all failed features to their reasons for rejection.
	*If empty, the run was successful.
	*@type	{Object.<Feature, String>}
	*@private
	*/
	failures: Object.create(null),

	/** The list of all features to evaluate with this configuration.
	*@type	{Array.<Feature>}
	*@private
	*/
	features: [],

	/** Index of the currently evaluated feature.
	*@type	{integer}
	*@private
	*/
	currentFeature: 0,

	/** Promise for the driver to be initialized.
	*
	*@type	{Promise}
	*@private
	*/
	initialized: null,

	/** Promise for the baseURL to be loaded.
	*
	*@type	{Promise}
	*@private
	*/
	baseUrlLoaded: null,

	/** The promise controller (deferred object) for results, resolved when all features of this Runner have been evaluated.
	*@type	{q.deferred}
	*@private
	*/
	deferred: null,

	/**@class	Manages a set of features and the driver in which they are run.
	*
	* A `Runner` is mostly set up through a configuration object.
	* Such an object MUST contain the following items:
	*	- `baseURL`: the URL at which the driver should start;
	* It SHOULD contain:
	*	- `driverCapabilities`: an object that will be passed straight to the WebDriver instance, that describes the browser on which the tests should be run.
	* It MAY contain:
	*	- `name`: the name of the suite. Defaults to the name of the containing folder.
	*
	*@constructs
	*@param	{Object}	config	A configuration object, as defined above.
	*/
	initialize: function init(config) {
		this.config = this.parseConfig(config);

		this.initDriver();
	},

	/** Checks the passed configuration hash for any missing mandatory definitions.
	*
	*@param		{Object}	config	The configuration object to check (may not be defined, which will return an error).
	*@throws	{Error}	An error object describing the encountered problem.
	*@see	#initialize	For details on the configuration object.
	*/
	parseConfig: function parseConfig(config) {
		if (! config)
			throw new Error('You need to provide a configuration to create a Runner!');

		config.baseURL			 = this.formatURL(config, 'baseURL');
		config.seleniumServerURL = this.formatURL(config, 'seleniumServerURL');

		return config;
	},

	/** Checks the presence and formats the element stored in the given config, expecting an URL.
	*
	*@param		{Object}	config	The configuration to examine.
	*@param		{String}	key		The configuration key that holds the URL to check and reformat.
	*@throws	{Error}		If the given value is not a valid URL.
	*@returns	{String}	The URL stored in the given config, normalized.
	*/
	formatURL: function formatURL(config, key) {
		if (! config[key]) {
			throw new Error('No ' + key + ' was found in the given config')
		}

		try {
			return url.format(config[key]);	// allow taking objects describing the URL
		} catch (err) {
			throw new Error('The given ' + key + ' ("' + config[key] + '") is unreadable (' + err.message + ')');
		}
	},

	/** Initializes the underlying driver of this Runner.
	* Emits "driverInit".
	*
	*@return	{Promise}	A promise for the driver to be initialized.
	*@private
	*/
	initDriver: function initDriver() {
		if (! this.driver) {
			this.emit('driverInit', this);
			this.initialized = this.buildDriverFrom(this.config);
		}

		return this.initialized;
	},

	/** Navigates to the base page for this runner.
	*
	*@return	{Promise}	A promise for the base page to be loaded.
	*@private
	*/
	loadBaseURL: function loadBaseURL() {
		this.loaded = this.driver.get(this.config.baseURL);

		this.loaded.then(this.onReady.bind(this));

		return this.loaded;
	},

	/** Constructs a new WebDriver instance based on the given configuration.
	*
	*@param		{Object}	config	The configuration object based on which the driver will be built.
	*@return	{Promise}	The promise for the `driver` instance variable to be ready.
	*@see		#initialize	For details on the configuration object.
	*@private
	*/
	buildDriverFrom: function buildDriverFrom(config) {
		var seleniumServer = url.parse(config.seleniumServerURL);	// TODO: get the URL already parsed from the config instead of serializing it at each step

		this.driver = webdriver.promiseRemote({
			host		: seleniumServer.hostname,
			port		: seleniumServer.port,
			username	: (seleniumServer.auth ? seleniumServer.auth.split(':')[0] : undefined),	// TODO: this is a workaround WD's lack of `url.parse` compatibility. This code is to be refactored once <https://github.com/admc/wd/pull/143> is published. // replace with: this.driver = webdriver.promiseRemote(seleniumServer);
			accessKey	: (seleniumServer.auth ? seleniumServer.auth.split(':')[1] : undefined)
		});

		return this.driver.init(Object.merge(config.driverCapabilities, {
			name	: this.config.name,	// TODO: find a better way to pass config elements instead of whitelisting
			tags	: this.config.tags,
			build	: this.config.build
		}));
	},

	/** Tells whether the underlying driver of this Runner has loaded the base page or not.
	*
	*@return	{Boolean}	`true` if the page has been loaded, `false` otherwise.
	*/
	isReady: function isReady() {
		return !! (this.loaded && this.loaded.isFulfilled());
	},

	/** Emits the "ready" event.
	*
	*@private
	*/
	onReady: function onReady() {
		this.emit('ready', this);
	},

	/** Adds the given Feature to the list of those that this Runner will evaluate.
	*
	*@param	{Feature}	feature	A Feature for this Runner to evaluate.
	*@return	This Runner, for chaining.
	*/
	addFeature: function addFeature(feature) {
		this.features.push(feature);

		return this;
	},

	/** Returns the WebDriver instance this Runner created for the current run.
	*
	*@returns	WebDriver
	*/
	getDriver: function getDriver() {
		return this.driver;
	},

	/** Evaluates all features added to this Runner.
	*
	*@returns	{Promise}	A promise for results, resolved if all features pass (param: this Runner), rejected otherwise (param: hash mapping failed features to their reasons for rejection, or an Error if an error appeared in the runner itself or the evaluation was cancelled).
	*@see	#addFeature
	*/
	test: function test() {
		this.deferred = promises.defer();
		this.promise = this.deferred.promise;

		this.initDriver()
			.then(this.loadBaseURL.bind(this))
			.then(this.start.bind(this));

		return this.promise;
	},

	/** Actually starts the evaluation process.
	* Emits "start".
	*
	*@private
	*/
	start: function start() {
		this.failures = Object.create(null);
		this.currentFeature = -1;

		this.emit('start', this);

		this.startNextFeature();
	},

	/** Increments the feature index, starts evaluation of the next feature, and quits the driver if all features were evaluated.
	*
	*@private
	*/
	startNextFeature: function startNextFeature() {
		this.currentFeature++;

		if (this.currentFeature < this.features.length)
			this.evaluateFeature(this.features[this.currentFeature]);
		else
			this.finish();
	},

	/** Prepares and triggers the evaluation of the given feature.
	* Emits "feature".
	*
	*@private
	*/
	evaluateFeature: function evaluateFeature(feature) {
		this.emit('feature', feature);

		feature.test().then(this.startNextFeature.bind(this),
							this.handleFeatureFailure.bind(this, feature)); // leave last arg to pass failure description
	},

	/** Callback handler upon feature evaluation. Flags failures and calls the `startNextFeature` handler.
	*
	*@private
	*@see	#startNextFeature
	*/
	handleFeatureFailure: function handleFeatureFailure(feature, message) {
		this.failures[feature] = message;
		this.failed = true;

		this.startNextFeature();
	},

	/** Informs of the end result and cleans the instance up after tests runs.
	*
	*@private
	*/
	finish: function finish() {
		var resolve			= this.deferred.resolve.bind(this.deferred, this),
			reject			= this.deferred.reject.bind(this.deferred, this.failures),
			fulfill			= resolve,
			killDriver		= this.killDriver.bind(this),
			precondition	= (this.config.quit == 'always'
								? killDriver
								: promises);

		if (Object.getLength(this.failures) > 0) {
			fulfill = reject;
		} else {
			if (this.config.quit == 'on success')
				precondition = killDriver;
		}

		precondition().then(fulfill, reject);
	},

	/** Quits the managed browser.
	*
	*@return	{Promise}	A promise resolved once the browser has been properly quit.
	*/
	killDriver: function killDriver() {
		var driver = this.driver;
		this.driver = null;	// delete reference, as it won't be usable once quitted

		return (driver	// multiple calls to killDriver() might be issued
				? this.initialized.then(function() { return driver.quit() })
				: promises());	// normalize return type to a promise, so that it can safely be called even if the driver had already been quit
	},

	toString: function toString() {
		return this.config.name;
	}
});

module.exports = Runner;	// CommonJS export
<commit_msg>Remove workaround obsoleted by WD update<commit_after>var url = require('url');

var webdriver	= require('wd'),
	promises	= require('q');

var Runner = new Class( /** @lends Runner# */ {

	Extends: require('events').EventEmitter,

	/** The promise object for results, resolved when all features of this Runner have been evaluated.
	*@type	{Promise}
	*/
	promise: null,

	/** A hash mapping all failed features to their reasons for rejection.
	*If empty, the run was successful.
	*@type	{Object.<Feature, String>}
	*@private
	*/
	failures: Object.create(null),

	/** The list of all features to evaluate with this configuration.
	*@type	{Array.<Feature>}
	*@private
	*/
	features: [],

	/** Index of the currently evaluated feature.
	*@type	{integer}
	*@private
	*/
	currentFeature: 0,

	/** Promise for the driver to be initialized.
	*
	*@type	{Promise}
	*@private
	*/
	initialized: null,

	/** Promise for the baseURL to be loaded.
	*
	*@type	{Promise}
	*@private
	*/
	baseUrlLoaded: null,

	/** The promise controller (deferred object) for results, resolved when all features of this Runner have been evaluated.
	*@type	{q.deferred}
	*@private
	*/
	deferred: null,

	/**@class	Manages a set of features and the driver in which they are run.
	*
	* A `Runner` is mostly set up through a configuration object.
	* Such an object MUST contain the following items:
	*	- `baseURL`: the URL at which the driver should start;
	* It SHOULD contain:
	*	- `driverCapabilities`: an object that will be passed straight to the WebDriver instance, that describes the browser on which the tests should be run.
	* It MAY contain:
	*	- `name`: the name of the suite. Defaults to the name of the containing folder.
	*
	*@constructs
	*@param	{Object}	config	A configuration object, as defined above.
	*/
	initialize: function init(config) {
		this.config = this.parseConfig(config);

		this.initDriver();
	},

	/** Checks the passed configuration hash for any missing mandatory definitions.
	*
	*@param		{Object}	config	The configuration object to check (may not be defined, which will return an error).
	*@throws	{Error}	An error object describing the encountered problem.
	*@see	#initialize	For details on the configuration object.
	*/
	parseConfig: function parseConfig(config) {
		if (! config)
			throw new Error('You need to provide a configuration to create a Runner!');

		config.baseURL			 = this.formatURL(config, 'baseURL');
		config.seleniumServerURL = this.formatURL(config, 'seleniumServerURL');

		return config;
	},

	/** Checks the presence and formats the element stored in the given config, expecting an URL.
	*
	*@param		{Object}	config	The configuration to examine.
	*@param		{String}	key		The configuration key that holds the URL to check and reformat.
	*@throws	{Error}		If the given value is not a valid URL.
	*@returns	{String}	The URL stored in the given config, normalized.
	*/
	formatURL: function formatURL(config, key) {
		if (! config[key]) {
			throw new Error('No ' + key + ' was found in the given config')
		}

		try {
			return url.format(config[key]);	// allow taking objects describing the URL
		} catch (err) {
			throw new Error('The given ' + key + ' ("' + config[key] + '") is unreadable (' + err.message + ')');
		}
	},

	/** Initializes the underlying driver of this Runner.
	* Emits "driverInit".
	*
	*@return	{Promise}	A promise for the driver to be initialized.
	*@private
	*/
	initDriver: function initDriver() {
		if (! this.driver) {
			this.emit('driverInit', this);
			this.initialized = this.buildDriverFrom(this.config);
		}

		return this.initialized;
	},

	/** Navigates to the base page for this runner.
	*
	*@return	{Promise}	A promise for the base page to be loaded.
	*@private
	*/
	loadBaseURL: function loadBaseURL() {
		this.loaded = this.driver.get(this.config.baseURL);

		this.loaded.then(this.onReady.bind(this));

		return this.loaded;
	},

	/** Constructs a new WebDriver instance based on the given configuration.
	*
	*@param		{Object}	config	The configuration object based on which the driver will be built.
	*@return	{Promise}	The promise for the `driver` instance variable to be ready.
	*@see		#initialize	For details on the configuration object.
	*@private
	*/
	buildDriverFrom: function buildDriverFrom(config) {
		this.driver = webdriver.promiseRemote(url.parse(config.seleniumServerURL));	// TODO: get the URL already parsed from the config instead of serializing it at each step

		return this.driver.init(Object.merge(config.driverCapabilities, {
			name	: this.config.name,	// TODO: find a better way to pass config elements instead of whitelisting
			tags	: this.config.tags,
			build	: this.config.build
		}));
	},

	/** Tells whether the underlying driver of this Runner has loaded the base page or not.
	*
	*@return	{Boolean}	`true` if the page has been loaded, `false` otherwise.
	*/
	isReady: function isReady() {
		return !! (this.loaded && this.loaded.isFulfilled());
	},

	/** Emits the "ready" event.
	*
	*@private
	*/
	onReady: function onReady() {
		this.emit('ready', this);
	},

	/** Adds the given Feature to the list of those that this Runner will evaluate.
	*
	*@param	{Feature}	feature	A Feature for this Runner to evaluate.
	*@return	This Runner, for chaining.
	*/
	addFeature: function addFeature(feature) {
		this.features.push(feature);

		return this;
	},

	/** Returns the WebDriver instance this Runner created for the current run.
	*
	*@returns	WebDriver
	*/
	getDriver: function getDriver() {
		return this.driver;
	},

	/** Evaluates all features added to this Runner.
	*
	*@returns	{Promise}	A promise for results, resolved if all features pass (param: this Runner), rejected otherwise (param: hash mapping failed features to their reasons for rejection, or an Error if an error appeared in the runner itself or the evaluation was cancelled).
	*@see	#addFeature
	*/
	test: function test() {
		this.deferred = promises.defer();
		this.promise = this.deferred.promise;

		this.initDriver()
			.then(this.loadBaseURL.bind(this))
			.then(this.start.bind(this));

		return this.promise;
	},

	/** Actually starts the evaluation process.
	* Emits "start".
	*
	*@private
	*/
	start: function start() {
		this.failures = Object.create(null);
		this.currentFeature = -1;

		this.emit('start', this);

		this.startNextFeature();
	},

	/** Increments the feature index, starts evaluation of the next feature, and quits the driver if all features were evaluated.
	*
	*@private
	*/
	startNextFeature: function startNextFeature() {
		this.currentFeature++;

		if (this.currentFeature < this.features.length)
			this.evaluateFeature(this.features[this.currentFeature]);
		else
			this.finish();
	},

	/** Prepares and triggers the evaluation of the given feature.
	* Emits "feature".
	*
	*@private
	*/
	evaluateFeature: function evaluateFeature(feature) {
		this.emit('feature', feature);

		feature.test().then(this.startNextFeature.bind(this),
							this.handleFeatureFailure.bind(this, feature)); // leave last arg to pass failure description
	},

	/** Callback handler upon feature evaluation. Flags failures and calls the `startNextFeature` handler.
	*
	*@private
	*@see	#startNextFeature
	*/
	handleFeatureFailure: function handleFeatureFailure(feature, message) {
		this.failures[feature] = message;
		this.failed = true;

		this.startNextFeature();
	},

	/** Informs of the end result and cleans the instance up after tests runs.
	*
	*@private
	*/
	finish: function finish() {
		var resolve			= this.deferred.resolve.bind(this.deferred, this),
			reject			= this.deferred.reject.bind(this.deferred, this.failures),
			fulfill			= resolve,
			killDriver		= this.killDriver.bind(this),
			precondition	= (this.config.quit == 'always'
								? killDriver
								: promises);

		if (Object.getLength(this.failures) > 0) {
			fulfill = reject;
		} else {
			if (this.config.quit == 'on success')
				precondition = killDriver;
		}

		precondition().then(fulfill, reject);
	},

	/** Quits the managed browser.
	*
	*@return	{Promise}	A promise resolved once the browser has been properly quit.
	*/
	killDriver: function killDriver() {
		var driver = this.driver;
		this.driver = null;	// delete reference, as it won't be usable once quitted

		return (driver	// multiple calls to killDriver() might be issued
				? this.initialized.then(function() { return driver.quit() })
				: promises());	// normalize return type to a promise, so that it can safely be called even if the driver had already been quit
	},

	toString: function toString() {
		return this.config.name;
	}
});

module.exports = Runner;	// CommonJS export
<|endoftext|>


<commit_before>var path = require('path'); //path is a built-in node library to handle file system paths
var models = require('../models'); //pull in our models. This will automatically load the index.js from that folder

//get the Cat model
var Cat = models.Cat.CatModel;
//get the dog model
var Dog = models.Dog.DogModel;

//default fake data so that we have something to work with until we make a real Cat
var defaultDataCat = {
    name: "unknown",
    bedsOwned: 0
};

var defaultDataDog = {
	name: "unknown",
	breed: "unknown",
	age: 0
};

//object for us to keep track of the last Cat we made and dynamically update it sometimes
var lastAddedCat = new Cat(defaultDataCat);
var lastAddedDog = new Dog(defaultDataDog);

//function to handle requests to the main page
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var hostIndex = function(req, res){
    //res.render takes a name of a page to render. These must be in the folder you specified as views in your main app.js file
    //Additionally, you don't need .jade because you registered the file type in the app.js as jade. Calling res.render('index')
    //actually calls index.jade. A second parameter of JSON can be passed into the jade to be used as variables with #{varName}
    res.render('index',{
        currentName: lastAddedCat.name,
        title: "Home",
        pageName: "Home Page"
    });
};

//function to find all cats on request. Express functions always receive the request and the response.
var readAllCats = function(req, res, callback) {
    //Call the model's built in find function and provide it a callback to run when the query is complete
    //Find has several versions
    //one parameter is just the callback
    //two parameters is JSON of search criteria and callback. That limits your search to only things that match the criteria
    //The find function returns an array of matching objects
    Cat.find(callback);
};

var readAllDogs = function(req, res, callback) {
    //Call the model's built in find function and provide it a callback to run when the query is complete
    //Find has several versions
    //one parameter is just the callback
    //two parameters is JSON of search criteria and callback. That limits your search to only things that match the criteria
    //The find function returns an array of matching objects
    Dog.find(callback);
};

//function to find a specific cat on request. Express functions always receive the request and the response.
var readCat = function(req, res) {

    //function to call when we get objects back from the database.
    //With Mongoose's find functions, you will get an err and doc(s) back
    var callback = function(err, doc) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //return success
        return res.json(doc);
    };

    //Call the static function attached to CatModels. This was defined in the Schema in the Model file.
    //This is a custom static function added to the CatModel
    //Behind the scenes this runs the findOne method. You can find the findByName function in the model file.
    Cat.findByNameCat(name1, callback);
};

var readDog = function(req, res) {

    //function to call when we get objects back from the database.
    //With Mongoose's find functions, you will get an err and doc(s) back
    var callback = function(err, doc) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //return success
        return res.json(doc);
    };

    //Call the static function attached to CatModels. This was defined in the Schema in the Model file.
    //This is a custom static function added to the CatModel
    //Behind the scenes this runs the findOne method. You can find the findByName function in the model file.
    Dog.findByNameDog(name1, callback);
};

//function to handle requests to the page1 page
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var hostPage1 = function(req, res) {

    //function to call when we get objects back from the database.
    //With Mongoose's find functions, you will get an err and doc(s) back
    var callback = function(err, docs) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //return success
        return res.render('page1', {cats:docs});
    };

    readAllCats(req, res, callback);
};

//function to handle requests to the page2 page
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var hostPage2 = function(req, res) {
    //res.render takes a name of a page to render. These must be in the folder you specified as views in your main app.js file
    //Additionally, you don't need .jade because you registered the file type in the app.js as jade. Calling res.render('index')
    //actually calls index.jade. A second parameter of JSON can be passed into the jade to be used as variables with #{varName}
    res.render('page2');
};

//function to handle requests to the page3 page
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var hostPage3 = function(req, res) {
    //res.render takes a name of a page to render. These must be in the folder you specified as views in your main app.js file
    //Additionally, you don't need .jade because you registered the file type in the app.js as jade. Calling res.render('index')
    //actually calls index.jade. A second parameter of JSON can be passed into the jade to be used as variables with #{varName}
    res.render('page3');
};

//function to handle get request to send the name
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var getNameCat = function(req, res) {
    //res.json returns json to the page. Since this sends back the data through HTTP
    //you can't send any more data to this user until the next response
    res.json({name: lastAddedCat.name});
};

var getNameDog = function(req, res) {
    //res.json returns json to the page. Since this sends back the data through HTTP
    //you can't send any more data to this user until the next response
    res.json({name: lastAddedDog.name});
};

//function to handle a request to set the name
//controller functions in Express receive the full HTTP request
//and get a pre-filled out response object to send
//ADDITIONALLY, with body-parser we will get the body/form/POST data in the request as req.body
var setNameCat = function(req, res) {

    //check if the required fields exist
    //normally you would also perform validation to know if the data they sent you was real
    if(!req.body.firstname || !req.body.lastname || !req.body.beds) {
        //if not respond with a 400 error (either through json or a web page depending on the client dev)
        return res.status(400).json({error: "firstname,lastname and beds are all required"});
    }

    //if required fields are good, then set name
    var name = req.body.firstname + " " + req.body.lastname;

        //dummy JSON to insert into database
    var catData = {
        name: name,
        bedsOwned: req.body.beds
    };

    //create a new object of CatModel with the object to save
    var newCat = new Cat(catData);

    //Save the newCat object to the database
    newCat.save(function(err) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //set the lastAddedCat cat to our newest cat object. This way we can update it dynamically
        lastAddedCat = newCat;

        //return success
        return res.json({name: name});
    });
};

var setNameDog = function(req, res) {

    //check if the required fields exist
    //normally you would also perform validation to know if the data they sent you was real
    if(!req.body.firstname || !req.body.lastname || !req.body.breed || !req.body.age) {
        //if not respond with a 400 error (either through json or a web page depending on the client dev)
        return res.status(400).json({error: "firstname,lastname, breed and age are all required"});
    }

    //if required fields are good, then set name
    var name = req.body.firstname + " " + req.body.lastname;

        //dummy JSON to insert into database
    var dogData = {
        name: name,
        breed: req.body.breed,
		age: reg.body.age
    };

    //create a new object of CatModel with the object to save
    var newDog = new Dog(dogData);

    //Save the newCat object to the database
    newDog.save(function(err) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //set the lastAddedCat cat to our newest cat object. This way we can update it dynamically
        lastAddedDog = newDog;

        //return success
        return res.json({name: name});
    });
};

//function to handle requests search for a name and return the object
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var searchNameCat = function(req,res) {

    //check if there is a query parameter for name
    //BUT WAIT!!?!
    //Why is this req.query and not req.body like the others
    //This is a GET request. Those come as query parameters in the URL
    //For POST requests like the other ones in here, those come in a request body because they aren't a query
    //POSTS send data to add while GETS query for a page or data (such as a search)
    if(!req.query.name) {
        return res.json({error: "Name is required to perform a search"});
    }

    //Call our Cat's static findByName function. Since this is a static function, we can just call it without an object
    //Methods like sayName are attached only to each object instance (not static)
    //pass in a callback (like we specified in the Cat model
    //Normally would you break this code up, but I'm trying to keep it together so it's easier to see how the system works
    //For that reason, I gave it an anonymous callback instead of a named function you'd have to go find
    Cat.findByNameCat(req.query.name, function(err, doc) {
        //errs, handle them
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //if no matches, let them know (does not necessarily have to be an error since technically it worked correctly)
        if(!doc) {
            return res.json({error: "No cats found"});
        }

        //if a match, send the match back
        return res.json({name: doc.name, beds: doc.bedsOwned});
    });

};

var searchNameDog = function(req,res) {

    //check if there is a query parameter for name
    //BUT WAIT!!?!
    //Why is this req.query and not req.body like the others
    //This is a GET request. Those come as query parameters in the URL
    //For POST requests like the other ones in here, those come in a request body because they aren't a query
    //POSTS send data to add while GETS query for a page or data (such as a search)
    if(!req.query.name) {
        return res.json({error: "Name is required to perform a search"});
    }

    //Call our Cat's static findByName function. Since this is a static function, we can just call it without an object
    //Methods like sayName are attached only to each object instance (not static)
    //pass in a callback (like we specified in the Cat model
    //Normally would you break this code up, but I'm trying to keep it together so it's easier to see how the system works
    //For that reason, I gave it an anonymous callback instead of a named function you'd have to go find

	Dog.findByNameDog(req.query.name, function(err, doc) {
        //errs, handle them
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //if no matches, let them know (does not necessarily have to be an error since technically it worked correctly)
        if(!doc) {
            return res.json({error: "No dogs found"});
        }

        //if a match, send the match back
        return res.json({name: doc.name, breed: doc.breed, age: doc.age});
    });

};

//function to handle a request to update the last added object
//this PURELY exists to show you how to update a model object
//Normally for an update, you'd get data from the client, search for an object, update the object and put it back
//We will skip straight to updating an object (that we stored as last added) and putting it back
var updateLastCat = function(req, res) {

    //Your model is JSON, so just change a value in it. This is the benefit of ORM (mongoose) and/or object documents (Mongo NoSQL)
    //You can treat objects just like that - objects.
    //Normally you'd find a specific object, but we will only give the user the ability to update our last object
    lastAddedCat.bedsOwned++;

    //once you change all the object properties you want, then just call the Model object's save function
    lastAddedCat.save(function(err) {
        //if save error, just return an error for now
        if(err) {
            return res.json({err:err});
        }

        //otherwise just send back the name as a success
        return res.json({name:lastAddedCat.name, beds: lastAddedCat.bedsOwned});
    });
};

var updateLastDog = function(req, res) {

    //Your model is JSON, so just change a value in it. This is the benefit of ORM (mongoose) and/or object documents (Mongo NoSQL)
    //You can treat objects just like that - objects.
    //Normally you'd find a specific object, but we will only give the user the ability to update our last object
    lastAddedDog.age++;

    //once you change all the object properties you want, then just call the Model object's save function
    lastAddedDog.save(function(err) {
        //if save error, just return an error for now
        if(err) {
            return res.json({err:err});
        }

        //otherwise just send back the name as a success
        return res.json({name:lastAddedDog.name, breed:lastAddedDog.breed, age:lastAddedDog.age});
    });
};

//function to handle a request to any non-real resources (404)
//controller functions in Express receive the full HTTP request
//and get a pre-filled out response object to send
var notFound = function(req, res) {
    //res.render takes a name of a page to render. These must be in the folder you specified as views in your main app.js file
    //Additionally, you don't need .jade because you registered the file type in the app.js as jade. Calling res.render('index')
    //actually calls index.jade. A second parameter of JSON can be passed into the jade to be used as variables with #{varName}
    res.status(404).render('notFound',{
        page: req.url
    });
};

//export the relevant public controller functions
module.exports = {
    index: hostIndex,
    page1: hostPage1,
    page2: hostPage2,
    page3: hostPage3,
    getNameCat: getNameCat,
	getNameDog: getNameDog,
    setNameCat: setNameCat,
	setNameDog: setNameDog,
    updateLastCat: updateLastCat,
	updateLastDog: updateLastDog,
    searchNameCat: searchNameCat,
	searchNameDog: searchNameDog,
    notFound: notFound
};<commit_msg>code13<commit_after>var path = require('path'); //path is a built-in node library to handle file system paths
var models = require('../models'); //pull in our models. This will automatically load the index.js from that folder

//get the Cat model
var Cat = models.Cat.CatModel;
//get the dog model
var Dog = models.Dog.DogModel;

//default fake data so that we have something to work with until we make a real Cat
var defaultDataCat = {
    name: "unknown",
    bedsOwned: 0
};

var defaultDataDog = {
	name: "unknown",
	breed: "unknown",
	age: 0
};

//object for us to keep track of the last Cat we made and dynamically update it sometimes
var lastAddedCat = new Cat(defaultDataCat);
var lastAddedDog = new Dog(defaultDataDog);

//function to handle requests to the main page
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var hostIndex = function(req, res){
    //res.render takes a name of a page to render. These must be in the folder you specified as views in your main app.js file
    //Additionally, you don't need .jade because you registered the file type in the app.js as jade. Calling res.render('index')
    //actually calls index.jade. A second parameter of JSON can be passed into the jade to be used as variables with #{varName}
    res.render('index',{
        currentName: lastAddedCat.name,
        title: "Home",
        pageName: "Home Page"
    });
};

//function to find all cats on request. Express functions always receive the request and the response.
var readAllCats = function(req, res, callback) {
    //Call the model's built in find function and provide it a callback to run when the query is complete
    //Find has several versions
    //one parameter is just the callback
    //two parameters is JSON of search criteria and callback. That limits your search to only things that match the criteria
    //The find function returns an array of matching objects
    Cat.find(callback);
};

var readAllDogs = function(req, res, callback) {
    //Call the model's built in find function and provide it a callback to run when the query is complete
    //Find has several versions
    //one parameter is just the callback
    //two parameters is JSON of search criteria and callback. That limits your search to only things that match the criteria
    //The find function returns an array of matching objects
    Dog.find(callback);
};

//function to find a specific cat on request. Express functions always receive the request and the response.
var readCat = function(req, res) {

    //function to call when we get objects back from the database.
    //With Mongoose's find functions, you will get an err and doc(s) back
    var callback = function(err, doc) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //return success
        return res.json(doc);
    };

    //Call the static function attached to CatModels. This was defined in the Schema in the Model file.
    //This is a custom static function added to the CatModel
    //Behind the scenes this runs the findOne method. You can find the findByName function in the model file.
    Cat.findByNameCat(name1, callback);
};

var readDog = function(req, res) {

    //function to call when we get objects back from the database.
    //With Mongoose's find functions, you will get an err and doc(s) back
    var callback = function(err, doc) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //return success
        return res.json(doc);
    };

    //Call the static function attached to CatModels. This was defined in the Schema in the Model file.
    //This is a custom static function added to the CatModel
    //Behind the scenes this runs the findOne method. You can find the findByName function in the model file.
    Dog.findByNameDog(name1, callback);
};

//function to handle requests to the page1 page
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var hostPage1 = function(req, res) {

    //function to call when we get objects back from the database.
    //With Mongoose's find functions, you will get an err and doc(s) back
    var callback = function(err, docs) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //return success
        return res.render('page1', {cats:docs});
    };

    readAllCats(req, res, callback);
};

//function to handle requests to the page2 page
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var hostPage2 = function(req, res) {
    //res.render takes a name of a page to render. These must be in the folder you specified as views in your main app.js file
    //Additionally, you don't need .jade because you registered the file type in the app.js as jade. Calling res.render('index')
    //actually calls index.jade. A second parameter of JSON can be passed into the jade to be used as variables with #{varName}
    res.render('page2');
};

//function to handle requests to the page3 page
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var hostPage3 = function(req, res) {
    //res.render takes a name of a page to render. These must be in the folder you specified as views in your main app.js file
    //Additionally, you don't need .jade because you registered the file type in the app.js as jade. Calling res.render('index')
    //actually calls index.jade. A second parameter of JSON can be passed into the jade to be used as variables with #{varName}
    res.render('page3');
};

//function to handle get request to send the name
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var getNameCat = function(req, res) {
    //res.json returns json to the page. Since this sends back the data through HTTP
    //you can't send any more data to this user until the next response
    res.json({name: lastAddedCat.name});
};

var getNameDog = function(req, res) {
    //res.json returns json to the page. Since this sends back the data through HTTP
    //you can't send any more data to this user until the next response
    res.json({name: lastAddedDog.name});
};

//function to handle a request to set the name
//controller functions in Express receive the full HTTP request
//and get a pre-filled out response object to send
//ADDITIONALLY, with body-parser we will get the body/form/POST data in the request as req.body
var setNameCat = function(req, res) {

    //check if the required fields exist
    //normally you would also perform validation to know if the data they sent you was real
    if(!req.body.firstname || !req.body.lastname || !req.body.beds) {
        //if not respond with a 400 error (either through json or a web page depending on the client dev)
        return res.status(400).json({error: "firstname,lastname and beds are all required"});
    }

    //if required fields are good, then set name
    var name = req.body.firstname + " " + req.body.lastname;

        //dummy JSON to insert into database
    var catData = {
        name: name,
        bedsOwned: req.body.beds
    };

    //create a new object of CatModel with the object to save
    var newCat = new Cat(catData);

    //Save the newCat object to the database
    newCat.save(function(err) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //set the lastAddedCat cat to our newest cat object. This way we can update it dynamically
        lastAddedCat = newCat;

        //return success
        return res.json({name: name});
    });
};

var setNameDog = function(req, res) {

    //check if the required fields exist
    //normally you would also perform validation to know if the data they sent you was real
    if(!req.body.firstname || !req.body.lastname || !req.body.breed || !req.body.age) {
        //if not respond with a 400 error (either through json or a web page depending on the client dev)
        return res.status(400).json({error: "firstname,lastname, breed and age are all required"});
    }

    //if required fields are good, then set name
    var name = req.body.firstname + " " + req.body.lastname;

        //dummy JSON to insert into database
    var dogData = {
        name: name,
        breed: req.body.breed,
		age: req.body.age
    };

    //create a new object of CatModel with the object to save
    var newDog = new Dog(dogData);

    //Save the newCat object to the database
    newDog.save(function(err) {
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //set the lastAddedCat cat to our newest cat object. This way we can update it dynamically
        lastAddedDog = newDog;

        //return success
        return res.json({name: name});
    });
};

//function to handle requests search for a name and return the object
//controller functions in Express receive the full HTTP request
//and a pre-filled out response object to send
var searchNameCat = function(req,res) {

    //check if there is a query parameter for name
    //BUT WAIT!!?!
    //Why is this req.query and not req.body like the others
    //This is a GET request. Those come as query parameters in the URL
    //For POST requests like the other ones in here, those come in a request body because they aren't a query
    //POSTS send data to add while GETS query for a page or data (such as a search)
    if(!req.query.name) {
        return res.json({error: "Name is required to perform a search"});
    }

    //Call our Cat's static findByName function. Since this is a static function, we can just call it without an object
    //Methods like sayName are attached only to each object instance (not static)
    //pass in a callback (like we specified in the Cat model
    //Normally would you break this code up, but I'm trying to keep it together so it's easier to see how the system works
    //For that reason, I gave it an anonymous callback instead of a named function you'd have to go find
    Cat.findByNameCat(req.query.name, function(err, doc) {
        //errs, handle them
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //if no matches, let them know (does not necessarily have to be an error since technically it worked correctly)
        if(!doc) {
            return res.json({error: "No cats found"});
        }

        //if a match, send the match back
        return res.json({name: doc.name, beds: doc.bedsOwned});
    });

};

var searchNameDog = function(req,res) {

    //check if there is a query parameter for name
    //BUT WAIT!!?!
    //Why is this req.query and not req.body like the others
    //This is a GET request. Those come as query parameters in the URL
    //For POST requests like the other ones in here, those come in a request body because they aren't a query
    //POSTS send data to add while GETS query for a page or data (such as a search)
    if(!req.query.name) {
        return res.json({error: "Name is required to perform a search"});
    }

    //Call our Cat's static findByName function. Since this is a static function, we can just call it without an object
    //Methods like sayName are attached only to each object instance (not static)
    //pass in a callback (like we specified in the Cat model
    //Normally would you break this code up, but I'm trying to keep it together so it's easier to see how the system works
    //For that reason, I gave it an anonymous callback instead of a named function you'd have to go find

	Dog.findByNameDog(req.query.name, function(err, doc) {
        //errs, handle them
        if(err) {
            return res.json({err:err}); //if error, return it
        }

        //if no matches, let them know (does not necessarily have to be an error since technically it worked correctly)
        if(!doc) {
            return res.json({error: "No dogs found"});
        }

        //if a match, send the match back
        return res.json({name: doc.name, breed: doc.breed, age: doc.age});
    });

};

//function to handle a request to update the last added object
//this PURELY exists to show you how to update a model object
//Normally for an update, you'd get data from the client, search for an object, update the object and put it back
//We will skip straight to updating an object (that we stored as last added) and putting it back
var updateLastCat = function(req, res) {

    //Your model is JSON, so just change a value in it. This is the benefit of ORM (mongoose) and/or object documents (Mongo NoSQL)
    //You can treat objects just like that - objects.
    //Normally you'd find a specific object, but we will only give the user the ability to update our last object
    lastAddedCat.bedsOwned++;

    //once you change all the object properties you want, then just call the Model object's save function
    lastAddedCat.save(function(err) {
        //if save error, just return an error for now
        if(err) {
            return res.json({err:err});
        }

        //otherwise just send back the name as a success
        return res.json({name:lastAddedCat.name, beds: lastAddedCat.bedsOwned});
    });
};

var updateLastDog = function(req, res) {

    //Your model is JSON, so just change a value in it. This is the benefit of ORM (mongoose) and/or object documents (Mongo NoSQL)
    //You can treat objects just like that - objects.
    //Normally you'd find a specific object, but we will only give the user the ability to update our last object
    lastAddedDog.age++;

    //once you change all the object properties you want, then just call the Model object's save function
    lastAddedDog.save(function(err) {
        //if save error, just return an error for now
        if(err) {
            return res.json({err:err});
        }

        //otherwise just send back the name as a success
        return res.json({name:lastAddedDog.name, breed:lastAddedDog.breed, age:lastAddedDog.age});
    });
};

//function to handle a request to any non-real resources (404)
//controller functions in Express receive the full HTTP request
//and get a pre-filled out response object to send
var notFound = function(req, res) {
    //res.render takes a name of a page to render. These must be in the folder you specified as views in your main app.js file
    //Additionally, you don't need .jade because you registered the file type in the app.js as jade. Calling res.render('index')
    //actually calls index.jade. A second parameter of JSON can be passed into the jade to be used as variables with #{varName}
    res.status(404).render('notFound',{
        page: req.url
    });
};

//export the relevant public controller functions
module.exports = {
    index: hostIndex,
    page1: hostPage1,
    page2: hostPage2,
    page3: hostPage3,
    getNameCat: getNameCat,
	getNameDog: getNameDog,
    setNameCat: setNameCat,
	setNameDog: setNameDog,
    updateLastCat: updateLastCat,
	updateLastDog: updateLastDog,
    searchNameCat: searchNameCat,
	searchNameDog: searchNameDog,
    notFound: notFound
};<|endoftext|>


<commit_before>import express from 'express';
import bodyParser from 'body-parser';

import * as repoModel from '../models/repo';
import * as loginModel from '../models/login';
import * as tokenModel from '../models/token';
import * as reportModel from '../models/report';

const jsonParser = bodyParser.json();
const router = new express.Router();
export default router;

router.get('/', (req, res) =>
  res.render('index'));

router.get('/login/:provider', (req, res) =>
  loginModel.getLoginUrl(req.params)
  .then(url => res.redirect(url)));

router.get('/login/:provider/callback', (req, res) =>
  loginModel.callback(req.params, req.query, req.session)
  .then(url => res.redirect(url)));

router.get('/:provider/:user', loginModel.validate, (req, res) =>
  repoModel.query(req.params)
  .then(result => res.render('repo-list', result),
    error => res.send(error)));

router.route('/token/:provider/:user/:repo')
  .all(loginModel.validate)
  .get((req, res) =>
    tokenModel.get(req.params)
    .then(token => res.json(token),
      error => res.send(error)))
  .post((req, res) =>
    tokenModel.create(req.params)
    .then(() => res.redirect(`/${req.params.provider}/${req.params.user}`),
      error => res.send(error.toString())));

router.route('/:provider/:user/:repo')
  .all(loginModel.validate)
  .get((req, res) =>
    reportModel.query(req.params)
    .then(result => res.render('repo', result),
      error => res.send(error)))
  .post(jsonParser, (req, res) =>
    tokenModel.validate({ ...req.params, token: req.body.token })
    .then(() => reportModel.upsert({ ...req.params, ...req.body }))
    .then(entity => res.json(entity),
      error => res.send(error.toString())));
<commit_msg>Only validate login for get repo request.<commit_after>import express from 'express';
import bodyParser from 'body-parser';

import * as repoModel from '../models/repo';
import * as loginModel from '../models/login';
import * as tokenModel from '../models/token';
import * as reportModel from '../models/report';

const jsonParser = bodyParser.json();
const router = new express.Router();
export default router;

router.get('/', (req, res) =>
  res.render('index'));

router.get('/login/:provider', (req, res) =>
  loginModel.getLoginUrl(req.params)
  .then(url => res.redirect(url)));

router.get('/login/:provider/callback', (req, res) =>
  loginModel.callback(req.params, req.query, req.session)
  .then(url => res.redirect(url)));

router.get('/:provider/:user', loginModel.validate, (req, res) =>
  repoModel.query(req.params)
  .then(result => res.render('repo-list', result),
    error => res.send(error)));

router.route('/token/:provider/:user/:repo')
  .all(loginModel.validate)
  .get((req, res) =>
    tokenModel.get(req.params)
    .then(token => res.json(token),
      error => res.send(error)))
  .post((req, res) =>
    tokenModel.create(req.params)
    .then(() => res.redirect(`/${req.params.provider}/${req.params.user}`),
      error => res.send(error.toString())));

router.route('/:provider/:user/:repo')
  .get(loginModel.validate, (req, res) =>
    reportModel.query(req.params)
    .then(result => res.render('repo', result),
      error => res.send(error)))
  .post(jsonParser, (req, res) =>
    tokenModel.validate({ ...req.params, token: req.body.token })
    .then(() => reportModel.upsert({ ...req.params, ...req.body }))
    .then(entity => res.json(entity),
      error => res.send(error.toString())));
<|endoftext|>


<commit_before>'use strict';

// Play instances (free/all/one by id/src) with @options.
// Usages:
// .play() - play all free sounds with group options.
// .play(id) - play only one instance (if exists) from group by id or src with group options.
// .play(id, options) - play only one instance (if exists) from group by id or src with custom @options extended by group options.
// .play(options, true) - play all (of group) sounds with @options extened by gropu options; @options can contain .id or .src for play only defined sound.
// .play(options) - play all free sounds with @options extended by group options; @options can contain .id or .src for play only defined sound; @options can contain .all property for select playing sounds from all group sounds (not only free sounds).
SoundJSDirectorGroupProto.play = function playGroup () {

  var arg0 = arguments[0], id, options, all = false;

  switch (arguments.length) {

    default:
    case 2: {
      var arg1 = arguments[1];
      if ('object' === typeof arg1) {
        id = arg0;
        options = arg1;
        all = false;
      }
      else if ('object' !== typeof arg0) {
        throw 'SoundJSDirector.Group.play() given bad arguments';
      }
      else {
        options = arg0;
        all = arg1;
      }
    } break;

    case 1: {
      switch (typeof arg0) {
        case 'string': id = arg0; break;
        case 'object': options = arg0; id = options.id || options.src; break;
        case 'boolean': all = arg0; break;
        default: throw 'SoundJSDirector.Group.play() given bad arguments';
      }
    } break;

    case 0: break;
  }

  var _options = SoundJSDirector.extend(
    SoundJSDirector.parseOptions(options),
    this.options
  );

  if (id) {
    this.playSound(id, _options);
  }

  else if (!_options.collapsed) {
    this._playSounds(_options, all);
  }

  else {
    this._playCollapsed(_options, all);
  }

  return this;

};

// Play sound by @id (id/src of sound instance) with @options.
SoundJSDirectorGroupProto.playSound = function playGroupSound (id, options) {

  var sounds = this.sounds,
      sound = sounds[id];

  if (!sound && isNaN(parseInt(id))) {
    this.eachSound(function (_sound) {
      if (id === _sound.id || id === _sound.src) {
        sound = _sound;
        return SoundJSDirector.OUT_EACH;
      }
    });
  }

  if (sound) {
    this._playSoundWithOptions(sound, options);
  }

  return this;
};

// Play @sound instance with @options.
SoundJSDirectorGroupProto._playSoundWithOptions =
function playSoundWithOptions (sound, options) {
  var _options = {};
  SoundJSDirector.each(options, function (value, name) {
    switch (name) {
      default: _options[name] = value; break;
      case 'pan': _options[name] = this.getSoundPan(sound, value); break;
      case 'volume': _options[name] = this.getSoundVolume(sound, value); break;
    }
  }, this);
  sound.play(_options);
  return this;
};

// Play free/all sounds in group.
SoundJSDirectorGroupProto._playSounds = function playSounds (options, all) {
  SoundJSDirector.each(
    all ? this.sounds : this._wait,
    function (sound) {
      this._playSoundWithOptions(sound, options);
    },
    this
  );
  return this;
};

// Play first of free/all sounds.
SoundJSDirectorGroupProto._playCollapsed = function playCollapsed (options, all) {
  var sound = SoundJSDirector.randomItem(all ? this.sounds : this._wait);
  if (sound) {
    this._playSoundWithOptions(sound, options);
  }
  return this;
};

// Pause all playing sounds.
SoundJSDirectorGroupProto.pause = function pauseGroup () {
  this.eachPlayingSound(function (sound) {
    sound.pause();
  });
  return this;
};

// Resume all paused group sounds.
SoundJSDirectorGroupProto.resume = function resumeGroup () {
  this.eachPlayingSound(function (sound) {
    sound.resume();
  });
  return this;
};

// Stop all playing group sounds.
SoundJSDirectorGroupProto.stop = function stopGroup () {
  this.eachPlayingSound(function (sound) {
    sound.stop();
  });
  return this;
};

<commit_msg>Added custom 'stopped' event sending in group.stop() method.<commit_after>'use strict';

// Play instances (free/all/one by id/src) with @options.
// Usages:
// .play() - play all free sounds with group options.
// .play(id) - play only one instance (if exists) from group by id or src with group options.
// .play(id, options) - play only one instance (if exists) from group by id or src with custom @options extended by group options.
// .play(options, true) - play all (of group) sounds with @options extened by gropu options; @options can contain .id or .src for play only defined sound.
// .play(options) - play all free sounds with @options extended by group options; @options can contain .id or .src for play only defined sound; @options can contain .all property for select playing sounds from all group sounds (not only free sounds).
SoundJSDirectorGroupProto.play = function playGroup () {

  var arg0 = arguments[0], id, options, all = false;

  switch (arguments.length) {

    default:
    case 2: {
      var arg1 = arguments[1];
      if ('object' === typeof arg1) {
        id = arg0;
        options = arg1;
        all = false;
      }
      else if ('object' !== typeof arg0) {
        throw 'SoundJSDirector.Group.play() given bad arguments';
      }
      else {
        options = arg0;
        all = arg1;
      }
    } break;

    case 1: {
      switch (typeof arg0) {
        case 'string': id = arg0; break;
        case 'object': options = arg0; id = options.id || options.src; break;
        case 'boolean': all = arg0; break;
        default: throw 'SoundJSDirector.Group.play() given bad arguments';
      }
    } break;

    case 0: break;
  }

  var _options = SoundJSDirector.extend(
    SoundJSDirector.parseOptions(options),
    this.options
  );

  if (id) {
    this.playSound(id, _options);
  }

  else if (!_options.collapsed) {
    this._playSounds(_options, all);
  }

  else {
    this._playCollapsed(_options, all);
  }

  return this;

};

// Play sound by @id (id/src of sound instance) with @options.
SoundJSDirectorGroupProto.playSound = function playGroupSound (id, options) {

  var sounds = this.sounds,
      sound = sounds[id];

  if (!sound && isNaN(parseInt(id))) {
    this.eachSound(function (_sound) {
      if (id === _sound.id || id === _sound.src) {
        sound = _sound;
        return SoundJSDirector.OUT_EACH;
      }
    });
  }

  if (sound) {
    this._playSoundWithOptions(sound, options);
  }

  return this;
};

// Play @sound instance with @options.
SoundJSDirectorGroupProto._playSoundWithOptions =
function playSoundWithOptions (sound, options) {
  var _options = {};
  SoundJSDirector.each(options, function (value, name) {
    switch (name) {
      default: _options[name] = value; break;
      case 'pan': _options[name] = this.getSoundPan(sound, value); break;
      case 'volume': _options[name] = this.getSoundVolume(sound, value); break;
    }
  }, this);
  sound.play(_options);
  return this;
};

// Play free/all sounds in group.
SoundJSDirectorGroupProto._playSounds = function playSounds (options, all) {
  SoundJSDirector.each(
    all ? this.sounds : this._wait,
    function (sound) {
      this._playSoundWithOptions(sound, options);
    },
    this
  );
  return this;
};

// Play first of free/all sounds.
SoundJSDirectorGroupProto._playCollapsed = function playCollapsed (options, all) {
  var sound = SoundJSDirector.randomItem(all ? this.sounds : this._wait);
  if (sound) {
    this._playSoundWithOptions(sound, options);
  }
  return this;
};

// Pause all playing sounds.
SoundJSDirectorGroupProto.pause = function pauseGroup () {
  this.eachPlayingSound(function (sound) {
    sound.pause();
  });
  return this;
};

// Resume all paused group sounds.
SoundJSDirectorGroupProto.resume = function resumeGroup () {
  this.eachPlayingSound(function (sound) {
    sound.resume();
  });
  return this;
};

// Stop all playing group sounds.
SoundJSDirectorGroupProto.stop = function stopGroup () {
  this.eachPlayingSound(function (sound) {
    sound.stop();
    sound._sendEvent('stopped');
  });
  return this;
};

<|endoftext|>


<commit_before>'use strict';

var D3 = require('d3');
var MG = require('./metricsgraphics');

exports.flash = function(options) {
    MG.data_graphic({
        title: '',
        data: options.data,
        target: options.target,
        width: 600,
        height: 500,
        left: 100,
        full_width: true,
        // transition_on_update: true,
        show_secondary_x_label: false,
        x_extended_ticks: true,
        xax_count: 5,
        xax_format: D3.time.format('%e %b, %y'),
        decimals: 0,
        baselines: [{
            value: 15,
            label: 'Statutory Limit'
        }],
        max_x : options.max_x || null,
        min_x: options.min_x || null,
        chart_type: options.data.length !== 0 ? 'line' : 'missing-data',
        missing_text: 'No data',
        legend: options.labels,
        legend_target: options.legend_target,
        aggregate_rollover: true,
        show_tooltips: false,
        interplate: 'linear',
        interpolate_tension: .98,
        area: options.area,
        y_label:  options.y_axis_label,
        mouseover: function(d, i) {
            if (!d.values) {
                d.values = [d];
            }
            if (options.data.length) {
                for (i = 1; i <= options.data.length; i++) {
                    var l_span = D3.select(options.legend_target + ' .mg-line' + i + '-legend-color');
                    l_span.text(' ');
                    l_span.text('— ' + options.labels[i - 1]);
                }
            }
            d.values.forEach(function(val, index) {
                var l_span = D3.select(options.legend_target + ' .mg-line' + val.line_id + '-legend-color');
                l_span.text(' ');
                var no_days = d.values[index - 1] ? (val.value - d.values[index - 1].value).toFixed(0) : (val.value).toFixed(0);
                l_span.text('— ' + options.labels[val.line_id - 1] + ' : ' + no_days);
                var format = D3.time.format('%b %d, %Y');
                D3.select(options.target + '_total_trans').text(format(val.date) + ': ' + val.total_trans);
            });
        },
        mouseout: function(d, i) {
            if (!d.values) {
                d.values = [d];
            }
            D3.select(options.target + '_total_trans').text('');
            d.values.forEach(function(val, index) {
                var l_span = D3.select(options.legend_target + ' .mg-line' + val.line_id + '-legend-color');
                l_span.text(' ');
                l_span.text('— ' + options.labels[index]);
            });
        }
    });
};

exports.singular = function(options) {

    MG.data_graphic({
        // title: options.title,
        title: '',
        data: options.data,
        width: 600,
        height: 500,
        left: 100,
        full_width: true,
        decimals: 0,
        target: options.target,
        xax_count: 15,
        max_x : options.max_x || null,
        min_x: options.min_x || null,
        xax_format: D3.time.format('%b, %y'),
        chart_type: options.data.length !== 0 ? 'line' : 'missing-data',
        missing_text: 'No data',
        show_secondary_x_label: false,
        x_extended_ticks: true,
        legend: options.labels,
        legend_target: options.legend_target,
        show_tooltips: false,
        aggregate_rollover: true,
        show_year_markers: true,
        // transition_on_update: true,
        interplate: 'linear',
        interpolate_tension: 1,
        area: options.area,
        y_label:  options.y_axis_label,
        mouseover: function(d, i) {
            if (!d.values) {
                d.values = [d];
            }
            if (options.data.length) {
                for (i = 1; i <= options.data.length; i++) {
                    var l_span = D3.select(options.legend_target + ' .mg-line' + i + '-legend-color');
                    l_span.text(' ');
                    l_span.text('— ' + options.labels[i - 1]);
                }
            }
            d.values.forEach(function(val, index) {
                var prefix = D3.formatPrefix(val.value);
                var l_span = D3.select(options.legend_target + ' .mg-line' + val.line_id + '-legend-color');
                l_span.text(' ');
                l_span.text('— ' + options.labels[val.line_id - 1] + ' : ' + prefix.scale(val.value).toFixed(0));
                var format = D3.time.format('%d, %Y');
                D3.select(options.target + '_total_trans').text(format(val.date) + ': ' + val.total_trans);
            });
        },
        mouseout: function(d, i) {
            if (!d.values) {
                d.values = [d];
            }
            D3.select(options.target + '_total_trans').text('');
            d.values.forEach(function(val, index) {
                var l_span = D3.select(options.legend_target + ' .mg-line' + val.line_id + '-legend-color');
                l_span.text(' ');
                l_span.text('— ' + options.labels[val.line_id - 1]);
            });
        }
    });
};

exports.small = function(options) {
    MG.data_graphic({
        title: options.title,
        data: options.data,
        width: 295,
        height: 300,
        right: 10,
        left: 90,
        small_text: true,
        xax_count: 3,
        decimals: 0,
        xax_format: D3.time.format('%e %b, %y'),
        chart_type: options.data.length !== 0 ? 'line' : 'missing-data',
        missing_text: 'No data',
        target: options.target,
        legend: options.labels,
        legend_target: options.legend_target,
        full_width: true,
        aggregate_rollover: true,
        x_extended_ticks: true,
        transition_on_update: false,
        max_y: options.max_y || undefined,
        interplate: 'linear',
        linked: true,
        interpolate_tension: 1,
        y_label: 'Days to Complete Process',
        area: options.area,
        show_secondary_x_label: false,
        max_x: options.max_x || undefined,
        min_x: options.min_x || undefined
    });
};
<commit_msg>Fix date format on region comparison legend<commit_after>'use strict';

var D3 = require('d3');
var MG = require('./metricsgraphics');

exports.flash = function(options) {
    MG.data_graphic({
        title: '',
        data: options.data,
        target: options.target,
        width: 600,
        height: 500,
        left: 100,
        full_width: true,
        // transition_on_update: true,
        show_secondary_x_label: false,
        x_extended_ticks: true,
        xax_count: 5,
        xax_format: D3.time.format('%e %b, %y'),
        decimals: 0,
        baselines: [{
            value: 15,
            label: 'Statutory Limit'
        }],
        max_x : options.max_x || null,
        min_x: options.min_x || null,
        chart_type: options.data.length !== 0 ? 'line' : 'missing-data',
        missing_text: 'No data',
        legend: options.labels,
        legend_target: options.legend_target,
        aggregate_rollover: true,
        show_tooltips: false,
        interplate: 'linear',
        interpolate_tension: .98,
        area: options.area,
        y_label:  options.y_axis_label,
        mouseover: function(d, i) {
            if (!d.values) {
                d.values = [d];
            }
            if (options.data.length) {
                for (i = 1; i <= options.data.length; i++) {
                    var l_span = D3.select(options.legend_target + ' .mg-line' + i + '-legend-color');
                    l_span.text(' ');
                    l_span.text('— ' + options.labels[i - 1]);
                }
            }
            d.values.forEach(function(val, index) {
                var l_span = D3.select(options.legend_target + ' .mg-line' + val.line_id + '-legend-color');
                l_span.text(' ');
                var no_days = d.values[index - 1] ? (val.value - d.values[index - 1].value).toFixed(0) : (val.value).toFixed(0);
                l_span.text('— ' + options.labels[val.line_id - 1] + ' : ' + no_days);
                var format = D3.time.format('%b %d, %Y');
                D3.select(options.target + '_total_trans').text(format(val.date) + ': ' + val.total_trans);
            });
        },
        mouseout: function(d, i) {
            if (!d.values) {
                d.values = [d];
            }
            D3.select(options.target + '_total_trans').text('');
            d.values.forEach(function(val, index) {
                var l_span = D3.select(options.legend_target + ' .mg-line' + val.line_id + '-legend-color');
                l_span.text(' ');
                l_span.text('— ' + options.labels[index]);
            });
        }
    });
};

exports.singular = function(options) {

    MG.data_graphic({
        // title: options.title,
        title: '',
        data: options.data,
        width: 600,
        height: 500,
        left: 100,
        full_width: true,
        decimals: 0,
        target: options.target,
        xax_count: 15,
        max_x : options.max_x || null,
        min_x: options.min_x || null,
        xax_format: D3.time.format('%b, %y'),
        chart_type: options.data.length !== 0 ? 'line' : 'missing-data',
        missing_text: 'No data',
        show_secondary_x_label: false,
        x_extended_ticks: true,
        legend: options.labels,
        legend_target: options.legend_target,
        show_tooltips: false,
        aggregate_rollover: true,
        show_year_markers: true,
        // transition_on_update: true,
        interplate: 'linear',
        interpolate_tension: 1,
        area: options.area,
        y_label:  options.y_axis_label,
        mouseover: function(d, i) {
            if (!d.values) {
                d.values = [d];
            }
            if (options.data.length) {
                for (i = 1; i <= options.data.length; i++) {
                    var l_span = D3.select(options.legend_target + ' .mg-line' + i + '-legend-color');
                    l_span.text(' ');
                    l_span.text('— ' + options.labels[i - 1]);
                }
            }
            d.values.forEach(function(val, index) {
                var prefix = D3.formatPrefix(val.value);
                var l_span = D3.select(options.legend_target + ' .mg-line' + val.line_id + '-legend-color');
                l_span.text(' ');
                l_span.text('— ' + options.labels[val.line_id - 1] + ' : ' + prefix.scale(val.value).toFixed(0));
                var format = D3.time.format('%b, %Y');
                D3.select(options.target + '_total_trans').text(format(val.date) + ': ' + val.total_trans);
            });
        },
        mouseout: function(d, i) {
            if (!d.values) {
                d.values = [d];
            }
            D3.select(options.target + '_total_trans').text('');
            d.values.forEach(function(val, index) {
                var l_span = D3.select(options.legend_target + ' .mg-line' + val.line_id + '-legend-color');
                l_span.text(' ');
                l_span.text('— ' + options.labels[val.line_id - 1]);
            });
        }
    });
};

exports.small = function(options) {
    MG.data_graphic({
        title: options.title,
        data: options.data,
        width: 295,
        height: 300,
        right: 10,
        left: 90,
        small_text: true,
        xax_count: 3,
        decimals: 0,
        xax_format: D3.time.format('%e %b, %y'),
        chart_type: options.data.length !== 0 ? 'line' : 'missing-data',
        missing_text: 'No data',
        target: options.target,
        legend: options.labels,
        legend_target: options.legend_target,
        full_width: true,
        aggregate_rollover: true,
        x_extended_ticks: true,
        transition_on_update: false,
        max_y: options.max_y || undefined,
        interplate: 'linear',
        linked: true,
        interpolate_tension: 1,
        y_label: 'Days to Complete Process',
        area: options.area,
        show_secondary_x_label: false,
        max_x: options.max_x || undefined,
        min_x: options.min_x || undefined
    });
};
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2011-2012 Jeff Hoefs <soundanalogous@gmail.com>
 * Released under the MIT license. See LICENSE file for details.
 */

JSUTILS.namespace('BO.IOBoardEvent');

BO.IOBoardEvent = (function() {

	var IOBoardEvent;

	// dependencies
	var Event = JSUTILS.Event;

	/**
	 * @exports IOBoardEvent as BO.IOBoardEvent
	 * @class An Event object to be dispatched (fired) by the IOBoard object.
	 * The most important event is the READY event which signifies that the
	 * I/O board is ready to receive commands from the application. Many of the
	 * other IOBoard events are used when creating new io component objects.
	 * @constructor
	 * @augments JSUTILS.Event
	 * @param {String} type The event type
	 */
	IOBoardEvent = function(type) {

		this.name = "IOBoardEvent";

		// call the super class
		// 2nd parameter is passed to EventDispatcher constructor
		Event.call(this, type);
	};

	// events
	/** @constant */
	IOBoardEvent.ANALOG_DATA = "analogData";
	/** @constant */
	IOBoardEvent.DIGITAL_DATA = "digitalData";
	/** @constant */
	IOBoardEvent.FIRMWARE_VERSION = "firmwareVersion";
	/** @constant */
	IOBoardEvent.FIRMWARE_NAME = "firmwareName";
	/** @constant */
	IOBoardEvent.STRING_MESSAGE = "stringMessage";
	/** @constant */
	IOBoardEvent.SYSEX_MESSAGE = "sysexMessage";
	/** @constant */
	IOBoardEvent.PIN_STATE_RESPONSE = "pinStateResponse";
	/** @constant */
	IOBoardEvent.READY = "ioBoardReady";
	/** @constant */
	IOBoardEvent.CONNECTED = "ioBoardConnected";
	/** @constant */
	IOBoardEvent.DISCONNECTED = "ioBoardDisonnected";

	IOBoardEvent.prototype = JSUTILS.inherit(Event.prototype);
	IOBoardEvent.prototype.constructor = IOBoardEvent;

	return IOBoardEvent;

}());<commit_msg>Minor changes<commit_after>/**
 * Copyright (c) 2011-2012 Jeff Hoefs <soundanalogous@gmail.com>
 * Released under the MIT license. See LICENSE file for details.
 */

JSUTILS.namespace('BO.IOBoardEvent');

BO.IOBoardEvent = (function() {

	var IOBoardEvent;

	// Dependencies
	var Event = JSUTILS.Event;

	/**
	 * @exports IOBoardEvent as BO.IOBoardEvent
	 * @class An Event object to be dispatched (fired) by the IOBoard object.
	 * The most important event is the READY event which signifies that the
	 * I/O board is ready to receive commands from the application. Many of the
	 * other IOBoard events are used when creating new io component objects.
	 * @constructor
	 * @augments JSUTILS.Event
	 * @param {String} type The event type
	 */
	IOBoardEvent = function(type) {

		this.name = "IOBoardEvent";

		// Call the super class
		// 2nd parameter is passed to EventDispatcher constructor
		Event.call(this, type);
	};

	// Events
	/** @constant */
	IOBoardEvent.ANALOG_DATA = "analogData";
	/** @constant */
	IOBoardEvent.DIGITAL_DATA = "digitalData";
	/** @constant */
	IOBoardEvent.FIRMWARE_VERSION = "firmwareVersion";
	/** @constant */
	IOBoardEvent.FIRMWARE_NAME = "firmwareName";
	/** @constant */
	IOBoardEvent.STRING_MESSAGE = "stringMessage";
	/** @constant */
	IOBoardEvent.SYSEX_MESSAGE = "sysexMessage";
	/** @constant */
	IOBoardEvent.PIN_STATE_RESPONSE = "pinStateResponse";
	/** @constant */
	IOBoardEvent.READY = "ioBoardReady";
	/** @constant */
	IOBoardEvent.CONNECTED = "ioBoardConnected";
	/** @constant */
	IOBoardEvent.DISCONNECTED = "ioBoardDisonnected";

	IOBoardEvent.prototype = JSUTILS.inherit(Event.prototype);
	IOBoardEvent.prototype.constructor = IOBoardEvent;

	return IOBoardEvent;

}());
<|endoftext|>


<commit_before>define(function (require, exports, module) {
    "use strict";

    // Dependencies
    var $ = require('jquery'),
        _ = require('underscore'),
        Backbone = require('backbone'),
        User = require('models/user'),
        user = new User();

    // Module
    return Backbone.Model.extend({

        initialize: function (options) {

            if (!!this.idAttribute && !!this.get(this.idAttribute)) {
                this.set('id', this.get(this.idAttribute));
            }

            this.listenTo(this, 'change:' + this.idAttribute, this.onIdChange);

            if (typeof options === 'object' && typeof options._id !== 'undefined') {

            } else {

                var privat = {
                    opprettet_av: {
                        id: user.get('id'),
                        navn: user.get('navn'),
                        epost: user.get('epost')
                    }
                };

                this.set('privat', privat);
            }

            this.set('synced', true);

            if (typeof this.serverAttrs === 'object') {
                for (var i = 0; i < this.serverAttrs.length; i++) {
                    this.listenTo(this, 'change:' + this.serverAttrs[i], this.onChange);
                }
            }

            this.listenTo(this, 'sync', function (model, resp, options) {
                this.set('synced', true);
            });

        },

        onIdChange: function (model, value, options) {
            this.set('id', this.get(this.idAttribute));
        },

        onChange: function (model, options) {

            var previousGeoJson = model.previous('geojson'),
                newGeoJson = model.get('geojson');

            // Prevent setting synced to false on map init, which is setting model
            // attribute geojson from undefined to empty geojson object
            if (previousGeoJson && newGeoJson) {
                this.set('synced', false);

            }

        },

        /* Setters and getters */

        setPublished: function(){
            this.set('status', 'Offentlig');
        },

        setUnpublished: function(){
            this.set('status', 'Kladd');
        },

        getLatLng: function () {
            var geojson = this.get('geojson');

            if (typeof geojson === 'object' && typeof geojson.coordinates === 'object' && geojson.coordinates.length === 2) {
                var lat = geojson.coordinates[1],
                    lng = geojson.coordinates[0];

                return [lat, lng];

            } else {
                return undefined;
            }
        },

        setLatLng: function (latLng) {
            var lat = latLng[0],
                lng = latLng[1],
                geojson = _.clone(this.get('geojson')); // Or else the change event won't fire! o_O

            if (typeof geojson === 'object' && geojson.type === 'Point') {
                geojson.coordinates = [lng, lat];
            } else {
                geojson = {type: 'Point', 'coordinates': [lng, lat], properties: {}};
            }

            this.set('geojson', geojson);

        },

        remove: function () {
            this.set('removed', true);
        },

        hasTag: function (tag) {
            var tags = this.get('tags');
            var hasTag = (typeof tags !== 'undefined') && (tags.indexOf(tag) > -1);
            return hasTag;
        },

        /* Process fields */

        // Recursive method that will remove all object properties
        // and array items that are empty strings.
        // TODO: Also remove empty link objects equal to {tittel: "", url: "http://"}
        removeEmpty: function (input) {

            var output = _.clone(input);

            if (Object.prototype.toString.call(output) === '[object Array]') {

                for (var i = 0; i < output.length; i++) {
                    if (typeof output[i] === 'object') {
                        // if ((output[i].tittel === '' && output[i].url === 'http://') || (typeof output[i].tittel === 'undefined' && output[i].url === 'http://')) { output.splice(i, 1); }
                        output[i] = this.removeEmpty(output[i]);
                    } else if (typeof output[i] === 'string') {
                        if (output[i] === '') {
                            output.splice(i, 1);
                        }
                    }
                }

            } else if (Object.prototype.toString.call(output) === '[object Object]') {

                for (var prop in output) {
                    if (typeof output[prop] === 'object') {
                        output[prop] = this.removeEmpty(output[prop]);
                    } else if (typeof output[prop] === 'string') {
                        if (output[prop] === '') {
                            delete output[prop];
                        }
                    }

                }

            }

            return output;

        },

        /* Server interactions */

        save: function (attrs, options) {

            attrs = attrs || this.toJSON();
            options = options || {};

            // If model defines serverAttrs, replace attrs with trimmed version
            if (this.serverAttrs) {
                attrs = _.pick(attrs, this.serverAttrs);
            }

            attrs = this.removeEmpty(attrs);

            // Move attrs to options
            options.attrs = attrs;

            // Call super with attrs moved to options
            return Backbone.Model.prototype.save.call(this, attrs, options);
        }

    });

});
<commit_msg>Add gruppe id to grupper property to any ntb model being created by a group user / Innholdspartner<commit_after>define(function (require, exports, module) {
    "use strict";

    // Dependencies
    var $ = require('jquery'),
        _ = require('underscore'),
        Backbone = require('backbone'),
        User = require('models/user'),
        user = new User();

    // Module
    return Backbone.Model.extend({

        initialize: function (options) {

            if (!!this.idAttribute && !!this.get(this.idAttribute)) {
                this.set('id', this.get(this.idAttribute));
            }

            this.listenTo(this, 'change:' + this.idAttribute, this.onIdChange);

            if (typeof options === 'object' && typeof options._id !== 'undefined') {

            } else {
                // New model, just being created

                // Set created by private property
                var privat = {
                    opprettet_av: {
                        id: user.get('id'),
                        navn: user.get('navn'),
                        epost: user.get('epost')
                    }
                };

                this.set('privat', privat);

                // Set groups property if user has single group property defined
                var userGroup = user.get('gruppe');
                var groupsData = this.get('grupper') || [];

                if (!!userGroup) {
                    groupsData.push(userGroup);
                    this.set('grupper', groupsData);
                }
            }

            this.set('synced', true);

            if (typeof this.serverAttrs === 'object') {
                for (var i = 0; i < this.serverAttrs.length; i++) {
                    this.listenTo(this, 'change:' + this.serverAttrs[i], this.onChange);
                }
            }

            this.listenTo(this, 'sync', function (model, resp, options) {
                this.set('synced', true);
            });

        },

        onIdChange: function (model, value, options) {
            this.set('id', this.get(this.idAttribute));
        },

        onChange: function (model, options) {

            var previousGeoJson = model.previous('geojson'),
                newGeoJson = model.get('geojson');

            // Prevent setting synced to false on map init, which is setting model
            // attribute geojson from undefined to empty geojson object
            if (previousGeoJson && newGeoJson) {
                this.set('synced', false);

            }

        },

        /* Setters and getters */

        setPublished: function(){
            this.set('status', 'Offentlig');
        },

        setUnpublished: function(){
            this.set('status', 'Kladd');
        },

        getLatLng: function () {
            var geojson = this.get('geojson');

            if (typeof geojson === 'object' && typeof geojson.coordinates === 'object' && geojson.coordinates.length === 2) {
                var lat = geojson.coordinates[1],
                    lng = geojson.coordinates[0];

                return [lat, lng];

            } else {
                return undefined;
            }
        },

        setLatLng: function (latLng) {
            var lat = latLng[0],
                lng = latLng[1],
                geojson = _.clone(this.get('geojson')); // Or else the change event won't fire! o_O

            if (typeof geojson === 'object' && geojson.type === 'Point') {
                geojson.coordinates = [lng, lat];
            } else {
                geojson = {type: 'Point', 'coordinates': [lng, lat], properties: {}};
            }

            this.set('geojson', geojson);

        },

        remove: function () {
            this.set('removed', true);
        },

        hasTag: function (tag) {
            var tags = this.get('tags');
            var hasTag = (typeof tags !== 'undefined') && (tags.indexOf(tag) > -1);
            return hasTag;
        },

        /* Process fields */

        // Recursive method that will remove all object properties
        // and array items that are empty strings.
        // TODO: Also remove empty link objects equal to {tittel: "", url: "http://"}
        removeEmpty: function (input) {

            var output = _.clone(input);

            if (Object.prototype.toString.call(output) === '[object Array]') {

                for (var i = 0; i < output.length; i++) {
                    if (typeof output[i] === 'object') {
                        // if ((output[i].tittel === '' && output[i].url === 'http://') || (typeof output[i].tittel === 'undefined' && output[i].url === 'http://')) { output.splice(i, 1); }
                        output[i] = this.removeEmpty(output[i]);
                    } else if (typeof output[i] === 'string') {
                        if (output[i] === '') {
                            output.splice(i, 1);
                        }
                    }
                }

            } else if (Object.prototype.toString.call(output) === '[object Object]') {

                for (var prop in output) {
                    if (typeof output[prop] === 'object') {
                        output[prop] = this.removeEmpty(output[prop]);
                    } else if (typeof output[prop] === 'string') {
                        if (output[prop] === '') {
                            delete output[prop];
                        }
                    }

                }

            }

            return output;

        },

        /* Server interactions */

        save: function (attrs, options) {

            attrs = attrs || this.toJSON();
            options = options || {};

            // If model defines serverAttrs, replace attrs with trimmed version
            if (this.serverAttrs) {
                attrs = _.pick(attrs, this.serverAttrs);
            }

            attrs = this.removeEmpty(attrs);

            // Move attrs to options
            options.attrs = attrs;

            // Call super with attrs moved to options
            return Backbone.Model.prototype.save.call(this, attrs, options);
        }

    });

});
<|endoftext|>